Files
tolaria/src/components/EditableValue.tsx
lucaronin 236ac1c007 fix: use consistent 12px text size for property values in Properties panel
Property values (dates, URLs, text) were inheriting the 14px root font
size instead of using the 12px size already applied in InfoRow and other
inspector sections. Standardize all property value and editing input
text to 12px for visual consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:31:40 +01:00

253 lines
7.1 KiB
TypeScript

import { useState, useCallback } from 'react'
import { normalizeUrl } from '../utils/url'
export function UrlValue({
value,
onSave,
onCancel,
isEditing,
onStartEdit,
}: {
value: string
onSave: (newValue: string) => void
onCancel: () => void
isEditing: boolean
onStartEdit: () => void
}) {
const [editValue, setEditValue] = useState(value)
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
onSave(editValue)
} else if (e.key === 'Escape') {
setEditValue(value)
onCancel()
}
}
const handleOpen = useCallback(() => {
const normalized = normalizeUrl(value)
try {
new URL(normalized)
window.open(normalized, '_blank')
} catch {
// malformed URL — do nothing
}
}, [value])
if (isEditing) {
return (
<input
className="w-full rounded border border-ring bg-muted px-2 py-1 text-[13px] text-foreground outline-none focus:border-primary"
type="text"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => onSave(editValue)}
autoFocus
/>
)
}
return (
<span className="group/url inline-flex min-w-0 items-center gap-1">
<span
className="min-w-0 cursor-pointer truncate rounded px-1 py-0.5 text-right text-secondary-foreground transition-colors hover:text-primary hover:underline"
onClick={handleOpen}
title={value}
data-testid="url-link"
>
{value || '\u2014'}
</span>
<button
className="shrink-0 border-none bg-transparent p-0 text-[12px] leading-none text-muted-foreground opacity-0 transition-all hover:text-foreground group-hover/url:opacity-100"
onClick={onStartEdit}
title="Edit URL"
data-testid="url-edit-btn"
>
&#9998;
</button>
</span>
)
}
export function EditableValue({
value,
onSave,
onCancel,
isEditing,
onStartEdit
}: {
value: string
onSave: (newValue: string) => void
onCancel: () => void
isEditing: boolean
onStartEdit: () => void
}) {
const [editValue, setEditValue] = useState(value)
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
onSave(editValue)
} else if (e.key === 'Escape') {
setEditValue(value)
onCancel()
}
}
if (isEditing) {
return (
<input
className="w-full rounded border border-ring bg-muted px-2 py-1 text-[12px] text-foreground outline-none focus:border-primary"
type="text"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => onSave(editValue)}
autoFocus
/>
)
}
return (
<span
className="min-w-0 cursor-pointer truncate rounded px-1 py-0.5 text-right text-[12px] text-secondary-foreground transition-colors hover:bg-muted"
onClick={onStartEdit}
title={value || 'Click to edit'}
>
{value || '\u2014'}
</span>
)
}
export function TagPillList({
items,
onSave,
label,
}: {
items: string[]
onSave: (newItems: string[]) => void
label: string
}) {
const [editingIndex, setEditingIndex] = useState<number | null>(null)
const [editValue, setEditValue] = useState('')
const [isAddingNew, setIsAddingNew] = useState(false)
const [newValue, setNewValue] = useState('')
const handleStartEdit = (index: number) => {
setEditingIndex(index)
setEditValue(items[index])
}
const handleSaveEdit = () => {
if (editingIndex !== null) {
const newItems = [...items]
if (editValue.trim()) {
newItems[editingIndex] = editValue.trim()
} else {
newItems.splice(editingIndex, 1)
}
onSave(newItems)
setEditingIndex(null)
}
}
const handleDeleteItem = (index: number) => {
const newItems = items.filter((_, i) => i !== index)
onSave(newItems)
}
const handleAddNew = () => {
if (newValue.trim()) {
onSave([...items, newValue.trim()])
setNewValue('')
setIsAddingNew(false)
}
}
const handleKeyDown = (e: React.KeyboardEvent, action: 'edit' | 'add') => {
if (e.key === 'Enter') {
if (action === 'edit') handleSaveEdit()
else handleAddNew()
} else if (e.key === 'Escape') {
if (action === 'edit') {
setEditingIndex(null)
setEditValue('')
} else {
setIsAddingNew(false)
setNewValue('')
}
}
}
return (
<div className="flex flex-wrap items-center gap-1">
{items.map((item, idx) =>
editingIndex === idx ? (
<input
key={idx}
className="rounded-full border border-ring bg-muted px-2 py-0.5 text-[11px] text-foreground outline-none focus:border-primary"
style={{ width: Math.max(60, editValue.length * 7 + 16) }}
type="text"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onKeyDown={(e) => handleKeyDown(e, 'edit')}
onBlur={handleSaveEdit}
autoFocus
/>
) : (
<span
key={idx}
className="group/pill inline-flex cursor-pointer items-center gap-0.5 rounded-full py-0.5 pl-2 pr-1 transition-colors"
style={{
backgroundColor: 'var(--accent-blue-light)',
color: 'var(--accent-blue)',
fontSize: 11,
fontWeight: 500,
}}
onClick={() => handleStartEdit(idx)}
title="Click to edit"
>
{item}
<button
className="ml-0.5 inline-flex h-3.5 w-3.5 items-center justify-center rounded-full border-none bg-transparent p-0 text-[10px] leading-none opacity-0 transition-all hover:bg-[var(--accent-red-light)] hover:text-[var(--accent-red)] group-hover/pill:opacity-100"
style={{ color: 'var(--accent-blue)' }}
onClick={(e) => {
e.stopPropagation()
handleDeleteItem(idx)
}}
title="Remove"
>
&times;
</button>
</span>
)
)}
{isAddingNew ? (
<input
className="rounded-full border border-ring bg-muted px-2 py-0.5 text-[11px] text-foreground outline-none focus:border-primary"
style={{ width: Math.max(60, newValue.length * 7 + 16) }}
type="text"
value={newValue}
onChange={(e) => setNewValue(e.target.value)}
onKeyDown={(e) => handleKeyDown(e, 'add')}
onBlur={() => {
if (newValue.trim()) handleAddNew()
else { setIsAddingNew(false); setNewValue('') }
}}
placeholder={`${label}...`}
autoFocus
/>
) : (
<button
className="inline-flex h-5 w-5 items-center justify-center rounded-full border border-dashed border-[var(--accent-blue)] bg-transparent p-0 text-[12px] leading-none text-[var(--accent-blue)] transition-colors hover:bg-[var(--accent-blue-light)]"
onClick={() => setIsAddingNew(true)}
title={`Add ${label.toLowerCase()}`}
>
+
</button>
)}
</div>
)
}