fix: property panel responsive layout + status dropdown click regression

- StatusDropdown: change anchor element from <div> to <span> to fix
  invalid HTML nesting inside <span> parents. Browsers auto-correct
  <div> inside <span> by ripping the element out, breaking
  anchorRef.parentElement positioning. JSDOM doesn't auto-correct,
  so tests passed but the real app's dropdown failed to position.

- StatusValue: revert shrink-0 back to min-w-0 so the status pill
  can truncate in narrow panels instead of overflowing.

- PropertyRow: add min-w-0 and gap-2 for proper flex overflow,
  wrap property key in truncate span.

- AddPropertyForm: add flex-wrap, reduce input widths, set
  min-w-[60px] on value inputs for narrow panel support.

- InfoRow/TypeSelector/ReadOnlyType: add min-w-0 and gap-2.

- Tests: increase timeout for 3 Radix Select interaction tests
  that are slow in JSDOM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Test
2026-02-28 10:54:37 +01:00
parent 1efdbfb978
commit a18a19166b
3 changed files with 20 additions and 20 deletions

View File

@@ -1012,7 +1012,7 @@ describe('DynamicPropertiesPanel', () => {
expect(screen.getByText('\u2713 Yes')).toBeInTheDocument()
})
it('stores actual boolean value when adding boolean property', () => {
it('stores actual boolean value when adding boolean property', { timeout: 15_000 }, () => {
render(
<DynamicPropertiesPanel
entry={makeEntry()}
@@ -1034,7 +1034,7 @@ describe('DynamicPropertiesPanel', () => {
expect(onAddProperty).toHaveBeenCalledWith('published', true)
})
it('shows date picker trigger when date type selected', () => {
it('shows date picker trigger when date type selected', { timeout: 15_000 }, () => {
render(
<DynamicPropertiesPanel
entry={makeEntry()}
@@ -1050,7 +1050,7 @@ describe('DynamicPropertiesPanel', () => {
expect(screen.getByText('Pick a date\u2026')).toBeInTheDocument()
})
it('shows status dropdown when status type selected', () => {
it('shows status dropdown when status type selected', { timeout: 15_000 }, () => {
render(
<DynamicPropertiesPanel
entry={makeEntry()}

View File

@@ -261,13 +261,13 @@ const DISPLAY_MODE_ICONS: Record<PropertyDisplayMode, typeof Type> = {
text: Type, date: CalendarIcon, boolean: ToggleLeft, status: Circle, url: Link,
}
const ADD_INPUT_CLASS = "h-[26px] min-w-0 flex-1 rounded border border-border bg-muted px-1.5 text-[12px] text-foreground outline-none focus:border-primary"
const ADD_INPUT_CLASS = "h-[26px] min-w-[60px] flex-1 rounded border border-border bg-muted px-1.5 text-[12px] text-foreground outline-none focus:border-primary"
function AddBooleanInput({ value, onChange }: { value: string; onChange: (v: string) => void }) {
const boolVal = value.toLowerCase() === 'true'
return (
<button
className="h-[26px] min-w-0 flex-1 rounded border border-border bg-muted px-1.5 text-[12px] text-secondary-foreground transition-colors hover:bg-accent"
className="h-[26px] min-w-[60px] flex-1 rounded border border-border bg-muted px-1.5 text-[12px] text-secondary-foreground transition-colors hover:bg-accent"
onClick={() => onChange(boolVal ? 'false' : 'true')}
data-testid="add-property-boolean-toggle"
>
@@ -283,7 +283,7 @@ function AddDateInput({ value, onChange }: { value: string; onChange: (v: string
<Popover>
<PopoverTrigger asChild>
<button
className="inline-flex h-[26px] min-w-0 flex-1 cursor-pointer items-center gap-1 rounded border border-border bg-muted px-1.5 text-[12px] transition-colors hover:bg-accent"
className="inline-flex h-[26px] min-w-[60px] flex-1 cursor-pointer items-center gap-1 rounded border border-border bg-muted px-1.5 text-[12px] transition-colors hover:bg-accent"
data-testid="add-property-date-trigger"
>
<CalendarIcon className="size-3 shrink-0 text-muted-foreground" />
@@ -307,9 +307,9 @@ function AddDateInput({ value, onChange }: { value: string; onChange: (v: string
function AddStatusInput({ value, onChange, vaultStatuses }: { value: string; onChange: (v: string) => void; vaultStatuses: string[] }) {
const [showDropdown, setShowDropdown] = useState(false)
return (
<span className="relative inline-flex min-w-0 flex-1 items-center">
<span className="relative inline-flex min-w-[60px] flex-1 items-center">
<button
className="inline-flex h-[26px] min-w-0 flex-1 cursor-pointer items-center gap-1 rounded border border-border bg-muted px-1.5 text-[12px] transition-colors hover:bg-accent"
className="inline-flex h-[26px] min-w-[60px] flex-1 cursor-pointer items-center gap-1 rounded border border-border bg-muted px-1.5 text-[12px] transition-colors hover:bg-accent"
onClick={() => setShowDropdown(true)}
data-testid="add-property-status-trigger"
>
@@ -363,16 +363,16 @@ function AddPropertyForm({ onAdd, onCancel, vaultStatuses }: {
}
return (
<div className="mt-1 flex items-center gap-1.5 rounded px-1.5 py-1" data-testid="add-property-form">
<div className="mt-1 flex flex-wrap items-center gap-1.5 rounded px-1.5 py-1" data-testid="add-property-form">
<input
className="h-[26px] w-[90px] shrink-0 rounded border border-border bg-muted px-1.5 text-[12px] text-foreground outline-none focus:border-primary"
className="h-[26px] w-20 shrink-0 rounded border border-border bg-muted px-1.5 text-[12px] text-foreground outline-none focus:border-primary"
type="text" placeholder="Property name" value={newKey}
onChange={(e) => setNewKey(e.target.value)} onKeyDown={handleKeyDown} autoFocus
/>
<Select value={displayMode} onValueChange={(v) => handleModeChange(v as PropertyDisplayMode)}>
<SelectTrigger
size="sm"
className="h-[26px] w-[82px] shrink-0 gap-1 border-border bg-muted px-1.5 py-0 shadow-none"
className="h-[26px] w-[72px] shrink-0 gap-1 border-border bg-muted px-1.5 py-0 shadow-none"
style={{ fontSize: 12, borderRadius: 4 }}
data-testid="add-property-type-trigger"
>
@@ -410,7 +410,7 @@ const TYPE_NONE = '__none__'
function ReadOnlyType({ isA, customColorKey, onNavigate }: { isA?: string | null; customColorKey?: string | null; onNavigate?: (target: string) => void }) {
if (!isA) return null
return (
<div className="flex items-center justify-between px-1.5">
<div className="flex min-w-0 items-center justify-between gap-2 px-1.5">
<span className="font-mono-overline shrink-0 text-muted-foreground">Type</span>
{onNavigate ? (
<button
@@ -439,7 +439,7 @@ function TypeSelector({ isA, customColorKey, availableTypes, typeColorKeys, onUp
: availableTypes
return (
<div className="flex items-center justify-between px-1.5" data-testid="type-selector">
<div className="flex min-w-0 items-center justify-between gap-2 px-1.5" data-testid="type-selector">
<span className="font-mono-overline shrink-0 text-muted-foreground">Type</span>
<Select value={currentValue} onValueChange={v => onUpdateProperty('type', v === TYPE_NONE ? null : v)}>
<SelectTrigger
@@ -513,9 +513,9 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void
}) {
return (
<div className="group/prop flex items-center justify-between rounded px-1.5 py-0.5 transition-colors hover:bg-muted" data-testid="editable-property">
<span className="font-mono-overline flex shrink-0 items-center gap-1 text-muted-foreground">
{propKey}
<div className="group/prop flex min-w-0 items-center justify-between gap-2 rounded px-1.5 py-0.5 transition-colors hover:bg-muted" data-testid="editable-property">
<span className="font-mono-overline flex min-w-0 items-center gap-1 text-muted-foreground">
<span className="truncate">{propKey}</span>
{onDelete && (
<button className="border-none bg-transparent p-0 text-sm leading-none text-muted-foreground opacity-0 transition-all hover:text-destructive group-hover/prop:opacity-100" onClick={() => onDelete(propKey)} title="Delete property">&times;</button>
)}
@@ -528,9 +528,9 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
function InfoRow({ label, value }: { label: string; value: string }) {
return (
<div className="flex items-center justify-between px-1.5" data-testid="readonly-property">
<div className="flex min-w-0 items-center justify-between gap-2 px-1.5" data-testid="readonly-property">
<span className="font-mono-overline shrink-0" style={{ color: 'var(--text-muted)' }}>{label}</span>
<span className="text-right text-[12px]" style={{ color: 'var(--text-muted)' }}>{value}</span>
<span className="min-w-0 truncate text-right text-[12px]" style={{ color: 'var(--text-muted)' }}>{value}</span>
</div>
)
}

View File

@@ -285,7 +285,7 @@ export function StatusDropdown({
}
return (
<div ref={anchorRef} data-testid="status-dropdown">
<span ref={anchorRef} data-testid="status-dropdown">
{createPortal(
<>
<div className="fixed inset-0 z-[12000]" onClick={onCancel} data-testid="status-dropdown-backdrop" />
@@ -331,7 +331,7 @@ export function StatusDropdown({
</>,
document.body
)}
</div>
</span>
)
}