diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx
index 91ae8f1d..0b8c05c3 100644
--- a/src/components/DynamicPropertiesPanel.tsx
+++ b/src/components/DynamicPropertiesPanel.tsx
@@ -9,6 +9,7 @@ import { TypeSelector } from './TypeSelector'
import { AddPropertyForm } from './AddPropertyForm'
import type { PropertyDisplayMode } from '../utils/propertyTypes'
import { FOCUS_NOTE_ICON_PROPERTY_EVENT } from './noteIconPropertyEvents'
+import { PROPERTY_PANEL_COLUMN_STYLE } from './propertyPanelLayout'
function toSentenceCase(key: string): string {
const spaced = key.replace(/[_-]/g, ' ')
@@ -23,6 +24,10 @@ export function containsWikilinks(value: FrontmatterValue): boolean {
return false
}
+const PROPERTY_ROW_CLASS_NAME = 'group/prop grid min-h-7 min-w-0 grid-cols-2 items-center gap-2 rounded px-1.5 outline-none transition-colors hover:bg-muted focus:bg-muted focus:ring-1 focus:ring-primary'
+const PROPERTY_LABEL_CLASS_NAME = 'flex max-w-full min-w-0 items-center gap-1 text-[12px] text-muted-foreground'
+const SUGGESTED_PROPERTY_SLOT_CLASS_NAME = 'grid min-h-7 min-w-0 grid-cols-2 items-center gap-2 rounded border-none bg-transparent px-1.5 text-left outline-none transition-colors hover:bg-muted focus:bg-muted focus:ring-1 focus:ring-primary cursor-pointer'
+
function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultStatuses, vaultTags, onStartEdit, onSave, onSaveList, onUpdate, onDelete, onDisplayModeChange }: {
propKey: string; value: FrontmatterValue; editingKey: string | null
displayMode: PropertyDisplayMode; autoMode: PropertyDisplayMode
@@ -40,9 +45,9 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
}
return (
-
-
- {toSentenceCase(propKey)}
+
+
+ {toSentenceCase(propKey)}
{onDelete && (
)}
@@ -88,18 +93,63 @@ function getSuggestedDisplayMode(key: string): PropertyDisplayMode {
function SuggestedPropertySlot({ label, onAdd }: { label: string; onAdd: () => void }) {
return (
)
}
+function getExistingPropertyKeys(propertyEntries: [string, FrontmatterValue][], frontmatter: ParsedFrontmatter): Set {
+ const keys = new Set(propertyEntries.map(([key]) => key.toLowerCase()))
+ for (const key of Object.keys(frontmatter)) keys.add(key.toLowerCase())
+ return keys
+}
+
+function getMissingSuggestedProperties(canAddProperty: boolean, existingKeys: Set, pendingSuggestedKey: string | null) {
+ if (!canAddProperty) return []
+
+ return SUGGESTED_PROPERTIES.filter(
+ ({ key }) => !existingKeys.has(key.toLowerCase()) && key !== pendingSuggestedKey,
+ )
+}
+
+function getIconPropertyKey(propertyEntries: [string, FrontmatterValue][]) {
+ return propertyEntries.find(([key]) => key.toLowerCase() === 'icon')?.[0]
+}
+
+function useFocusNoteIconProperty({
+ onAddProperty,
+ propertyEntries,
+ setEditingKey,
+}: {
+ onAddProperty?: (key: string, value: FrontmatterValue) => void
+ propertyEntries: [string, FrontmatterValue][]
+ setEditingKey: (key: string | null) => void
+}) {
+ useEffect(() => {
+ const handleFocusNoteIcon = () => {
+ const existingIconKey = getIconPropertyKey(propertyEntries)
+
+ if (!existingIconKey) {
+ if (!onAddProperty) return
+ onAddProperty('icon', '')
+ }
+
+ setEditingKey(existingIconKey ?? 'icon')
+ }
+
+ window.addEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon)
+ return () => window.removeEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon)
+ }, [onAddProperty, propertyEntries, setEditingKey])
+}
+
export function DynamicPropertiesPanel({
entry, frontmatter, entries,
onUpdateProperty, onDeleteProperty, onAddProperty, onNavigate,
@@ -120,18 +170,11 @@ export function DynamicPropertiesPanel({
} = usePropertyPanelState({ entries, entryIsA: entry.isA, frontmatter, onUpdateProperty, onDeleteProperty, onAddProperty })
const [pendingSuggestedKey, setPendingSuggestedKey] = useState(null)
- const existingKeys = useMemo(() => {
- const keys = new Set(propertyEntries.map(([k]) => k.toLowerCase()))
- // Also check full frontmatter for relationship keys that are filtered out of propertyEntries
- for (const k of Object.keys(frontmatter)) keys.add(k.toLowerCase())
- return keys
- }, [propertyEntries, frontmatter])
-
- const missingSuggested = onAddProperty
- ? SUGGESTED_PROPERTIES.filter(
- p => !existingKeys.has(p.key.toLowerCase()) && p.key !== pendingSuggestedKey,
- )
- : []
+ const existingKeys = useMemo(() => getExistingPropertyKeys(propertyEntries, frontmatter), [propertyEntries, frontmatter])
+ const missingSuggested = useMemo(
+ () => getMissingSuggestedProperties(Boolean(onAddProperty), existingKeys, pendingSuggestedKey),
+ [existingKeys, onAddProperty, pendingSuggestedKey],
+ )
const handleSuggestedAdd = useCallback((key: string) => {
if (!onAddProperty) return
@@ -157,21 +200,7 @@ export function DynamicPropertiesPanel({
onAddProperty(key, trimmed)
}, [onAddProperty, setEditingKey])
- useEffect(() => {
- const handleFocusNoteIcon = () => {
- const existingIconKey = propertyEntries.find(([key]) => key.toLowerCase() === 'icon')?.[0]
-
- if (!existingIconKey) {
- if (!onAddProperty) return
- onAddProperty('icon', '')
- }
-
- setEditingKey(existingIconKey ?? 'icon')
- }
-
- window.addEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon)
- return () => window.removeEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon)
- }, [onAddProperty, propertyEntries, setEditingKey])
+ useFocusNoteIconProperty({ onAddProperty, propertyEntries, setEditingKey })
return (
diff --git a/src/components/TypeSelector.tsx b/src/components/TypeSelector.tsx
index 40bd6459..c28bca1b 100644
--- a/src/components/TypeSelector.tsx
+++ b/src/components/TypeSelector.tsx
@@ -2,6 +2,7 @@ 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'
+import { PROPERTY_PANEL_COLUMN_STYLE } from './propertyPanelLayout'
const TYPE_NONE = '__none__'
@@ -22,7 +23,7 @@ function TypeSelectorItem({ type, typeColorKeys, typeIconKeys }: {
function ReadOnlyType({ isA, customColorKey, onNavigate }: { isA?: string | null; customColorKey?: string | null; onNavigate?: (target: string) => void }) {
if (!isA) return null
return (
-
+
Type
{onNavigate ? (
@@ -57,7 +58,7 @@ export function TypeSelector({ isA, customColorKey, availableTypes, typeColorKey
const typeLightColor = isA ? getTypeLightColor(isA, typeColorKeys[isA] ?? customColorKey) : undefined
return (
-
+
Type