+
+ {open && createPortal(
+ <>
+
setOpen(false)} />
+
+ {DISPLAY_MODE_OPTIONS.map(opt => {
+ const OptIcon = DISPLAY_MODE_ICONS[opt.value]
+ return (
+
+ )
+ })}
+
+ >,
+ document.body
+ )}
+
+ )
+}
+
+function toBooleanValue(value: FrontmatterValue): boolean {
+ if (typeof value === 'boolean') return value
+ if (typeof value === 'string') return value.toLowerCase() === 'true'
+ return false
+}
+
+function autoDetectFromValue(value: FrontmatterValue): PropertyDisplayMode {
+ if (typeof value === 'boolean') return 'boolean'
+ if (typeof value === 'string' && isUrlValue(value)) return 'url'
+ if (typeof value === 'string' && isValidCssColor(value) && value.startsWith('#')) return 'color'
+ return 'text'
+}
+
+type SmartCellProps = {
+ propKey: string; value: FrontmatterValue; displayMode: PropertyDisplayMode; isEditing: boolean
+ vaultStatuses: string[]; vaultTags: string[]
+ onStartEdit: (key: string | null) => void; onSave: (key: string, value: string) => void
+ onSaveList: (key: string, items: string[]) => void; onUpdate?: (key: string, value: FrontmatterValue) => void
+}
+
+function ScalarValueCell({ propKey, value, displayMode, isEditing, vaultStatuses, vaultTags, onStartEdit, onSave, onSaveList, onUpdate }: SmartCellProps) {
+ const editProps = { value: String(value ?? ''), isEditing, onStartEdit: () => onStartEdit(propKey), onSave: (v: string) => onSave(propKey, v), onCancel: () => onStartEdit(null) }
+ const resolvedMode = displayMode === 'text' ? autoDetectFromValue(value) : displayMode
+ switch (resolvedMode) {
+ case 'status':
+ return
+ case 'tags':
+ return
+ case 'date':
+ return
onSave(propKey, v)} />
+ case 'boolean': {
+ const boolVal = toBooleanValue(value)
+ return onUpdate?.(propKey, !boolVal)} />
+ }
+ case 'url':
+ return
+ case 'color':
+ return
+ default:
+ return
+ }
+}
+
+export function SmartPropertyValueCell(props: SmartCellProps) {
+ const { propKey, value, displayMode, isEditing, vaultTags, onSaveList, onStartEdit } = props
+ if (Array.isArray(value)) {
+ if (displayMode === 'tags') {
+ return
+ }
+ return onSaveList(propKey, items)} label={propKey} />
+ }
+ return
+}
+
diff --git a/src/components/TypeSelector.tsx b/src/components/TypeSelector.tsx
new file mode 100644
index 00000000..52af9434
--- /dev/null
+++ b/src/components/TypeSelector.tsx
@@ -0,0 +1,77 @@
+import type { FrontmatterValue } from './Inspector'
+import { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from '@/components/ui/select'
+import { getTypeColor, getTypeLightColor } from '../utils/typeColors'
+import { getTypeIcon } from './NoteItem'
+
+const TYPE_NONE = '__none__'
+
+function TypeSelectorItem({ type, typeColorKeys, typeIconKeys }: {
+ type: string; typeColorKeys: Record; typeIconKeys: Record
+}) {
+ const Icon = getTypeIcon(type, typeIconKeys[type])
+ const color = getTypeColor(type, typeColorKeys[type])
+ return (
+ <>
+ {/* eslint-disable-next-line react-hooks/static-components -- icon from static map lookup */}
+
+ {type}
+ >
+ )
+}
+
+function ReadOnlyType({ isA, customColorKey, onNavigate }: { isA?: string | null; customColorKey?: string | null; onNavigate?: (target: string) => void }) {
+ if (!isA) return null
+ return (
+
+ Type
+ {onNavigate ? (
+
+ ) : (
+ {isA}
+ )}
+
+ )
+}
+
+export function TypeSelector({ isA, customColorKey, availableTypes, typeColorKeys, typeIconKeys, onUpdateProperty, onNavigate }: {
+ isA?: string | null; customColorKey?: string | null; availableTypes: string[]
+ typeColorKeys: Record
+ typeIconKeys: Record
+ onUpdateProperty?: (key: string, value: FrontmatterValue) => void
+ onNavigate?: (target: string) => void
+}) {
+ if (!onUpdateProperty) return
+
+ const currentValue = isA || TYPE_NONE
+ const options = isA && !availableTypes.includes(isA)
+ ? [...availableTypes, isA].sort((a, b) => a.localeCompare(b))
+ : availableTypes
+
+ return (
+
+ Type
+
+
+ )
+}
diff --git a/src/utils/propertyTypes.ts b/src/utils/propertyTypes.ts
index d888cc2a..a1b68698 100644
--- a/src/utils/propertyTypes.ts
+++ b/src/utils/propertyTypes.ts
@@ -1,6 +1,7 @@
import type { FrontmatterValue } from '../components/Inspector'
import { isValidCssColor, isColorKeyName } from './colorUtils'
import { updateVaultConfigField } from './vaultConfigStore'
+import { CalendarIcon, Type, ToggleLeft, Circle, Link, Tag, Palette } from 'lucide-react'
export type PropertyDisplayMode = 'text' | 'date' | 'boolean' | 'status' | 'url' | 'tags' | 'color'
@@ -112,3 +113,17 @@ export function toISODate(value: string): string {
}
return value
}
+
+export const DISPLAY_MODE_ICONS: Record = {
+ text: Type, date: CalendarIcon, boolean: ToggleLeft, status: Circle, url: Link, tags: Tag, color: Palette,
+}
+
+export const DISPLAY_MODE_OPTIONS: { value: PropertyDisplayMode; label: string }[] = [
+ { value: 'text', label: 'Text' },
+ { value: 'date', label: 'Date' },
+ { value: 'boolean', label: 'Boolean' },
+ { value: 'status', label: 'Status' },
+ { value: 'url', label: 'URL' },
+ { value: 'tags', label: 'Tags' },
+ { value: 'color', label: 'Color' },
+]