Field editor: palette, select/move/resize/rotate, inspector, optimistic sync (#11) (#30)
Build image / build-and-push (push) Successful in 13s

Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #30.
This commit is contained in:
2026-07-19 01:01:38 +00:00
committed by steve
parent 2119f1a376
commit b79bfcf7a9
13 changed files with 989 additions and 74 deletions
+49
View File
@@ -0,0 +1,49 @@
import { cn } from '@/lib/cn'
import { OBJECT_KINDS, kindDef } from './kinds'
import { useEditorStore } from './store'
/**
* The object-kind palette. Tap a kind to arm it, then tap the canvas to place
* (works on desktop and touch). Tapping the armed kind again disarms it.
*/
export function Palette() {
const armedKind = useEditorStore((s) => s.armedKind)
const setArmedKind = useEditorStore((s) => s.setArmedKind)
const select = useEditorStore((s) => s.select)
return (
<div className="flex flex-col gap-2">
<div className="flex flex-wrap gap-1.5 md:flex-col">
{OBJECT_KINDS.map((k) => {
const active = armedKind === k.kind
return (
<button
key={k.kind}
type="button"
onClick={() => {
select(null)
setArmedKind(active ? null : k.kind)
}}
className={cn(
'flex items-center gap-2 rounded-md border px-2.5 py-1.5 text-sm font-medium outline-none transition-colors',
'focus-visible:ring-2 focus-visible:ring-accent/40',
active
? 'border-accent bg-accent/10 text-accent-strong'
: 'border-border bg-surface text-fg hover:bg-border/50',
)}
aria-pressed={active}
>
<span aria-hidden className="text-base leading-none">
{k.icon}
</span>
<span>{k.label}</span>
</button>
)
})}
</div>
{armedKind && (
<p className="text-xs text-muted">Tap the canvas to place a {kindDef(armedKind)?.label ?? 'object'}.</p>
)}
</div>
)
}