fix: align property panel rows
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { Plus } from '@phosphor-icons/react'
|
||||
import { useMemo, useCallback, useEffect, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { VaultEntry } from '../types'
|
||||
import type { FrontmatterValue } from './Inspector'
|
||||
import type { ParsedFrontmatter } from '../utils/frontmatter'
|
||||
@@ -9,7 +11,14 @@ 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_GRID_STYLE, PROPERTY_PANEL_ROW_STYLE } from './propertyPanelLayout'
|
||||
import {
|
||||
PROPERTY_PANEL_GRID_STYLE,
|
||||
PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME,
|
||||
PROPERTY_PANEL_LABEL_CLASS_NAME,
|
||||
PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME,
|
||||
PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME,
|
||||
PROPERTY_PANEL_ROW_STYLE,
|
||||
} from './propertyPanelLayout'
|
||||
import { humanizePropertyKey } from '../utils/propertyLabels'
|
||||
import { canonicalSystemMetadataKey, hasSystemMetadataKey } from '../utils/systemMetadata'
|
||||
|
||||
@@ -21,8 +30,6 @@ export function containsWikilinks(value: FrontmatterValue): boolean {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -45,7 +52,7 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
|
||||
|
||||
return (
|
||||
<div className={PROPERTY_ROW_CLASS_NAME} style={PROPERTY_PANEL_ROW_STYLE} tabIndex={0} onKeyDown={handleKeyDown} data-testid="editable-property">
|
||||
<span className={PROPERTY_LABEL_CLASS_NAME}>
|
||||
<span className={PROPERTY_PANEL_LABEL_CLASS_NAME}>
|
||||
<DisplayModeSelector propKey={propKey} currentMode={displayMode} autoMode={autoMode} onSelect={onDisplayModeChange} />
|
||||
<span className="min-w-0 flex-1 truncate">{humanizePropertyKey(propKey)}</span>
|
||||
{onDelete && (
|
||||
@@ -61,13 +68,27 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
|
||||
|
||||
function AddPropertyButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) {
|
||||
return (
|
||||
<button
|
||||
className="mt-1 flex w-full cursor-pointer items-center gap-1 border-none bg-transparent px-1.5 text-[12px] text-muted-foreground opacity-50 transition-opacity hover:opacity-100 disabled:cursor-not-allowed"
|
||||
onClick={onClick} disabled={disabled}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME}
|
||||
style={PROPERTY_PANEL_ROW_STYLE}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
data-testid="add-property-row"
|
||||
>
|
||||
<span className="text-[12px] leading-none">+</span>
|
||||
Add property
|
||||
</button>
|
||||
<span className={PROPERTY_PANEL_LABEL_CLASS_NAME}>
|
||||
<span
|
||||
className={PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME}
|
||||
data-testid="add-property-icon-slot"
|
||||
>
|
||||
<Plus className="size-3.5" aria-hidden="true" />
|
||||
</span>
|
||||
<span className="min-w-0 truncate">Add property</span>
|
||||
</span>
|
||||
<span aria-hidden="true" className={PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME} />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -97,20 +118,29 @@ function SuggestedPropertySlot({ label, displayMode, onAdd }: {
|
||||
const SuggestedIcon = DISPLAY_MODE_ICONS[displayMode]
|
||||
|
||||
return (
|
||||
<button
|
||||
className={SUGGESTED_PROPERTY_SLOT_CLASS_NAME}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME}
|
||||
style={PROPERTY_PANEL_ROW_STYLE}
|
||||
tabIndex={0}
|
||||
onClick={onAdd}
|
||||
onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onAdd() } }}
|
||||
data-testid="suggested-property"
|
||||
>
|
||||
<span className="flex min-w-0 max-w-full items-center gap-1 text-[12px] text-muted-foreground/50">
|
||||
<SuggestedIcon className="size-3.5 shrink-0 text-muted-foreground/40" data-testid={`suggested-property-icon-${displayMode}`} />
|
||||
<span className={PROPERTY_PANEL_LABEL_CLASS_NAME}>
|
||||
<span
|
||||
className={PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME}
|
||||
data-testid="suggested-property-icon-slot"
|
||||
>
|
||||
<SuggestedIcon
|
||||
className="size-3.5 shrink-0 text-muted-foreground/40"
|
||||
data-testid={`suggested-property-icon-${displayMode}`}
|
||||
/>
|
||||
</span>
|
||||
<span className="min-w-0 truncate">{label}</span>
|
||||
</span>
|
||||
<span className="min-w-0 truncate text-[12px] text-muted-foreground/30">{'\u2014'}</span>
|
||||
</button>
|
||||
<span className={PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME}>{'\u2014'}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,6 +180,46 @@ function useFocusNoteIconProperty({
|
||||
}, [onAddProperty, setEditingKey, setPendingSuggestedKey])
|
||||
}
|
||||
|
||||
function useSuggestedPropertyActions({
|
||||
onAddProperty,
|
||||
setEditingKey,
|
||||
setPendingSuggestedKey,
|
||||
}: {
|
||||
onAddProperty?: (key: string, value: FrontmatterValue) => void
|
||||
setEditingKey: (key: string | null) => void
|
||||
setPendingSuggestedKey: (key: string | null) => void
|
||||
}) {
|
||||
const handleSuggestedAdd = useCallback((key: string) => {
|
||||
if (!onAddProperty) return
|
||||
setPendingSuggestedKey(key)
|
||||
setEditingKey(key)
|
||||
}, [onAddProperty, setEditingKey, setPendingSuggestedKey])
|
||||
|
||||
const handlePendingSuggestedEdit = useCallback((key: string | null) => {
|
||||
setEditingKey(key)
|
||||
if (key === null) setPendingSuggestedKey(null)
|
||||
}, [setEditingKey, setPendingSuggestedKey])
|
||||
|
||||
const handleSaveSuggestedValue = useCallback((key: string, newValue: string) => {
|
||||
setEditingKey(null)
|
||||
setPendingSuggestedKey(null)
|
||||
if (!onAddProperty) {
|
||||
return
|
||||
}
|
||||
const trimmed = newValue.trim()
|
||||
if (!trimmed) {
|
||||
return
|
||||
}
|
||||
onAddProperty(key === 'icon' ? canonicalSystemMetadataKey(key) : key, trimmed)
|
||||
}, [onAddProperty, setEditingKey, setPendingSuggestedKey])
|
||||
|
||||
return {
|
||||
handlePendingSuggestedEdit,
|
||||
handleSaveSuggestedValue,
|
||||
handleSuggestedAdd,
|
||||
}
|
||||
}
|
||||
|
||||
export function DynamicPropertiesPanel({
|
||||
entry, frontmatter, entries,
|
||||
onUpdateProperty, onDeleteProperty, onAddProperty, onNavigate,
|
||||
@@ -175,30 +245,15 @@ export function DynamicPropertiesPanel({
|
||||
() => getMissingSuggestedProperties(Boolean(onAddProperty), existingKeys, pendingSuggestedKey),
|
||||
[existingKeys, onAddProperty, pendingSuggestedKey],
|
||||
)
|
||||
|
||||
const handleSuggestedAdd = useCallback((key: string) => {
|
||||
if (!onAddProperty) return
|
||||
setPendingSuggestedKey(key)
|
||||
setEditingKey(key)
|
||||
}, [onAddProperty, setEditingKey])
|
||||
|
||||
const handlePendingSuggestedEdit = useCallback((key: string | null) => {
|
||||
setEditingKey(key)
|
||||
if (key === null) setPendingSuggestedKey(null)
|
||||
}, [setEditingKey])
|
||||
|
||||
const handleSaveSuggestedValue = useCallback((key: string, newValue: string) => {
|
||||
setEditingKey(null)
|
||||
setPendingSuggestedKey(null)
|
||||
if (!onAddProperty) {
|
||||
return
|
||||
}
|
||||
const trimmed = newValue.trim()
|
||||
if (!trimmed) {
|
||||
return
|
||||
}
|
||||
onAddProperty(key === 'icon' ? canonicalSystemMetadataKey(key) : key, trimmed)
|
||||
}, [onAddProperty, setEditingKey])
|
||||
const {
|
||||
handlePendingSuggestedEdit,
|
||||
handleSaveSuggestedValue,
|
||||
handleSuggestedAdd,
|
||||
} = useSuggestedPropertyActions({
|
||||
onAddProperty,
|
||||
setEditingKey,
|
||||
setPendingSuggestedKey,
|
||||
})
|
||||
|
||||
useFocusNoteIconProperty({ onAddProperty, setEditingKey, setPendingSuggestedKey })
|
||||
|
||||
@@ -244,11 +299,20 @@ export function DynamicPropertiesPanel({
|
||||
onAdd={() => handleSuggestedAdd(key)}
|
||||
/>
|
||||
))}
|
||||
{!showAddDialog && (
|
||||
<AddPropertyButton
|
||||
onClick={() => setShowAddDialog(true)}
|
||||
disabled={!onAddProperty}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showAddDialog
|
||||
? <AddPropertyForm onAdd={handleAdd} onCancel={() => setShowAddDialog(false)} vaultStatuses={vaultStatuses} />
|
||||
: <AddPropertyButton onClick={() => setShowAddDialog(true)} disabled={!onAddProperty} />
|
||||
}
|
||||
{showAddDialog && (
|
||||
<AddPropertyForm
|
||||
onAdd={handleAdd}
|
||||
onCancel={() => setShowAddDialog(false)}
|
||||
vaultStatuses={vaultStatuses}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { Inspector } from './Inspector'
|
||||
@@ -88,6 +89,20 @@ const defaultProps = {
|
||||
onNavigate: () => {},
|
||||
}
|
||||
|
||||
type InspectorProps = ComponentProps<typeof Inspector>
|
||||
|
||||
function renderInspector(overrides: Partial<InspectorProps> = {}) {
|
||||
return render(<Inspector {...defaultProps} {...overrides} />)
|
||||
}
|
||||
|
||||
function renderSelectedInspector(overrides: Partial<InspectorProps> = {}) {
|
||||
return renderInspector({
|
||||
entry: mockEntry,
|
||||
content: mockContent,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
describe('Inspector', () => {
|
||||
it('renders expanded state with "no note selected"', () => {
|
||||
render(<Inspector {...defaultProps} />)
|
||||
@@ -133,7 +148,7 @@ describe('Inspector', () => {
|
||||
|
||||
it('shows "Add property" button as disabled placeholder', () => {
|
||||
render(<Inspector {...defaultProps} entry={mockEntry} content={mockContent} />)
|
||||
const btn = screen.getByText('Add property')
|
||||
const btn = screen.getByRole('button', { name: 'Add property' })
|
||||
expect(btn).toBeDisabled()
|
||||
})
|
||||
|
||||
@@ -212,41 +227,22 @@ This is a test note with some words to count.
|
||||
})
|
||||
|
||||
it('hides backlinks section when no notes reference the current note', () => {
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={mockEntry}
|
||||
content={mockContent}
|
||||
entries={[mockEntry]}
|
||||
/>
|
||||
)
|
||||
renderSelectedInspector({ entries: [mockEntry] })
|
||||
expect(screen.queryByText('Backlinks')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('navigates when a backlink is clicked', () => {
|
||||
const onNavigate = vi.fn()
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={mockEntry}
|
||||
content={mockContent}
|
||||
entries={[mockEntry, referrerEntry]}
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)
|
||||
renderSelectedInspector({
|
||||
entries: [mockEntry, referrerEntry],
|
||||
onNavigate,
|
||||
})
|
||||
fireEvent.click(screen.getByText('Referrer Note'))
|
||||
expect(onNavigate).toHaveBeenCalledWith('Referrer Note')
|
||||
})
|
||||
|
||||
it('shows git history with commit hashes and messages', () => {
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={mockEntry}
|
||||
content={mockContent}
|
||||
gitHistory={mockGitHistory}
|
||||
/>
|
||||
)
|
||||
renderSelectedInspector({ gitHistory: mockGitHistory })
|
||||
expect(screen.getByText('History')).toBeInTheDocument()
|
||||
expect(screen.getByText('a1b2c3d')).toBeInTheDocument()
|
||||
expect(screen.getByText('e4f5g6h')).toBeInTheDocument()
|
||||
@@ -255,15 +251,10 @@ This is a test note with some words to count.
|
||||
|
||||
it('renders commit entries as clickable buttons', () => {
|
||||
const onViewCommitDiff = vi.fn()
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={mockEntry}
|
||||
content={mockContent}
|
||||
gitHistory={mockGitHistory}
|
||||
onViewCommitDiff={onViewCommitDiff}
|
||||
/>
|
||||
)
|
||||
renderSelectedInspector({
|
||||
gitHistory: mockGitHistory,
|
||||
onViewCommitDiff,
|
||||
})
|
||||
const hashBtn = screen.getByText('a1b2c3d')
|
||||
const button = hashBtn.closest('button')!
|
||||
expect(button.tagName).toBe('BUTTON')
|
||||
@@ -272,25 +263,12 @@ This is a test note with some words to count.
|
||||
})
|
||||
|
||||
it('hides history section when no commits', () => {
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={mockEntry}
|
||||
content={mockContent}
|
||||
gitHistory={[]}
|
||||
/>
|
||||
)
|
||||
renderSelectedInspector({ gitHistory: [] })
|
||||
expect(screen.queryByText('History')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows separate Info section with read-only metadata', () => {
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={mockEntry}
|
||||
content={mockContent}
|
||||
/>
|
||||
)
|
||||
renderSelectedInspector()
|
||||
expect(screen.getByText('Info')).toBeInTheDocument()
|
||||
expect(screen.getByText('Modified')).toBeInTheDocument()
|
||||
expect(screen.getByText('Created')).toBeInTheDocument()
|
||||
@@ -468,31 +446,23 @@ Status: Active
|
||||
})
|
||||
|
||||
it('hides referenced-by section when no entries reference the current note', () => {
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={targetEntry}
|
||||
content={targetContent}
|
||||
entries={[targetEntry]}
|
||||
|
||||
/>
|
||||
)
|
||||
renderInspector({
|
||||
entry: targetEntry,
|
||||
content: targetContent,
|
||||
entries: [targetEntry],
|
||||
})
|
||||
expect(screen.queryByText('No references')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Referenced by')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('navigates when clicking a referenced-by entry', () => {
|
||||
const onNavigate = vi.fn()
|
||||
render(
|
||||
<Inspector
|
||||
{...defaultProps}
|
||||
entry={targetEntry}
|
||||
content={targetContent}
|
||||
entries={[targetEntry, essayEntry]}
|
||||
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)
|
||||
renderInspector({
|
||||
entry: targetEntry,
|
||||
content: targetContent,
|
||||
entries: [targetEntry, essayEntry],
|
||||
onNavigate,
|
||||
})
|
||||
fireEvent.click(screen.getByText('On Writing Well'))
|
||||
expect(onNavigate).toHaveBeenCalledWith('On Writing Well')
|
||||
})
|
||||
|
||||
@@ -8,7 +8,11 @@ import { cn } from '@/lib/utils'
|
||||
import { getTypeColor, getTypeLightColor } from '../utils/typeColors'
|
||||
import { getTypeIcon } from './NoteItem'
|
||||
import { PROPERTY_CHIP_STYLE } from './propertyChipStyles'
|
||||
import { PROPERTY_PANEL_ROW_STYLE } from './propertyPanelLayout'
|
||||
import {
|
||||
PROPERTY_PANEL_LABEL_CLASS_NAME,
|
||||
PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME,
|
||||
PROPERTY_PANEL_ROW_STYLE,
|
||||
} from './propertyPanelLayout'
|
||||
|
||||
const TYPE_NONE = '__none__'
|
||||
const MIN_POPOVER_WIDTH = 220
|
||||
@@ -94,9 +98,14 @@ function TypeSelectorValue({
|
||||
|
||||
function TypeRowLabel() {
|
||||
return (
|
||||
<span className="flex shrink-0 items-center gap-1 text-[12px] text-muted-foreground">
|
||||
<StackSimple size={14} className="shrink-0" data-testid="type-row-icon" />
|
||||
<span>Type</span>
|
||||
<span className={PROPERTY_PANEL_LABEL_CLASS_NAME}>
|
||||
<span
|
||||
className={PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME}
|
||||
data-testid="type-row-icon-slot"
|
||||
>
|
||||
<StackSimple size={14} className="shrink-0" data-testid="type-row-icon" />
|
||||
</span>
|
||||
<span className="min-w-0 truncate">Type</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -104,7 +113,10 @@ function TypeRowLabel() {
|
||||
function ReadOnlyType({ isA, customColorKey, onNavigate }: { isA?: string | null; customColorKey?: string | null; onNavigate?: (target: string) => void }) {
|
||||
if (!isA) return null
|
||||
return (
|
||||
<div className="grid min-w-0 grid-cols-2 items-center gap-2 px-1.5" style={PROPERTY_PANEL_ROW_STYLE}>
|
||||
<div
|
||||
className="grid min-h-7 min-w-0 grid-cols-2 items-center gap-2 px-1.5"
|
||||
style={PROPERTY_PANEL_ROW_STYLE}
|
||||
>
|
||||
<TypeRowLabel />
|
||||
<div className="min-w-0">
|
||||
{onNavigate ? (
|
||||
@@ -261,7 +273,11 @@ function EditableTypeSelector({ isA, customColorKey, availableTypes, typeColorKe
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid min-w-0 grid-cols-2 items-center gap-2 px-1.5" style={PROPERTY_PANEL_ROW_STYLE} data-testid="type-selector">
|
||||
<div
|
||||
className="grid min-h-7 min-w-0 grid-cols-2 items-center gap-2 px-1.5"
|
||||
style={PROPERTY_PANEL_ROW_STYLE}
|
||||
data-testid="type-selector"
|
||||
>
|
||||
<TypeRowLabel />
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
<PopoverAnchor asChild>
|
||||
|
||||
@@ -76,6 +76,37 @@ describe('property panel shared grid layout', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the add-property row on the shared grid', () => {
|
||||
render(
|
||||
<DynamicPropertiesPanel
|
||||
entry={entry}
|
||||
frontmatter={{}}
|
||||
onAddProperty={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
const row = screen.getByTestId('add-property-row')
|
||||
expect(row.style.gridTemplateColumns).toBe('subgrid')
|
||||
expect(row.style.gridColumn).toBe('1 / -1')
|
||||
})
|
||||
|
||||
it('uses the same fixed icon slot size for type, suggested, and add-property rows', () => {
|
||||
render(
|
||||
<DynamicPropertiesPanel
|
||||
entry={entry}
|
||||
frontmatter={{}}
|
||||
onAddProperty={vi.fn()}
|
||||
onUpdateProperty={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('type-row-icon-slot')).toHaveClass('size-5')
|
||||
screen.getAllByTestId('suggested-property-icon-slot').forEach((slot) => {
|
||||
expect(slot).toHaveClass('size-5')
|
||||
})
|
||||
expect(screen.getByTestId('add-property-icon-slot')).toHaveClass('size-5')
|
||||
})
|
||||
|
||||
it('renders plain text values flush with the shared value column', () => {
|
||||
render(
|
||||
<DynamicPropertiesPanel
|
||||
|
||||
@@ -8,3 +8,11 @@ export const PROPERTY_PANEL_ROW_STYLE = {
|
||||
gridColumn: '1 / -1',
|
||||
gridTemplateColumns: 'subgrid',
|
||||
} satisfies CSSProperties
|
||||
|
||||
export const PROPERTY_PANEL_LABEL_CLASS_NAME = 'flex min-w-0 max-w-full items-center gap-1.5 text-[12px] text-muted-foreground'
|
||||
|
||||
export const PROPERTY_PANEL_LABEL_ICON_SLOT_CLASS_NAME = 'flex size-5 shrink-0 items-center justify-center'
|
||||
|
||||
export const PROPERTY_PANEL_INTERACTIVE_ROW_CLASS_NAME = 'grid h-auto min-h-7 w-full min-w-0 grid-cols-2 items-center gap-2 rounded px-1.5 text-left text-[12px] font-normal shadow-none outline-none transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:bg-muted focus-visible:ring-1 focus-visible:ring-primary'
|
||||
|
||||
export const PROPERTY_PANEL_PLACEHOLDER_VALUE_CLASS_NAME = 'min-w-0 truncate text-[12px] text-muted-foreground/30'
|
||||
|
||||
Reference in New Issue
Block a user