Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
3.4 kB · 100 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101import { useEffect, useRef, useState, type CSSProperties } from 'react';import { WORKSPACE_DESCRIPTORS, type WorkspaceKind,} from '../workspaces/workspace';
type WorkspaceMenuProps = { value: WorkspaceKind; onChange: (kind: WorkspaceKind) => void;};
const noDrag: CSSProperties & { WebkitAppRegion?: 'no-drag' } = { WebkitAppRegion: 'no-drag',};
export function WorkspaceMenu({ value, onChange }: WorkspaceMenuProps) { const [open, setOpen] = useState(false); const rootRef = useRef<HTMLDivElement>(null); const current = WORKSPACE_DESCRIPTORS.find((d) => d.kind === value);
useEffect(() => { if (!open) return; const onDocMouseDown = (event: MouseEvent) => { if (!rootRef.current) return; if (!rootRef.current.contains(event.target as Node)) { setOpen(false); } }; const onKey = (event: KeyboardEvent) => { if (event.key === 'Escape') setOpen(false); }; document.addEventListener('mousedown', onDocMouseDown); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDocMouseDown); document.removeEventListener('keydown', onKey); }; }, [open]);
return ( <div ref={rootRef} className="relative" style={noDrag}> <button type="button" aria-haspopup="menu" aria-expanded={open} aria-label="Workspace" onClick={() => setOpen((o) => !o)} className="text-foreground/90 hover:bg-border/40 focus-visible:ring-accent/50 inline-flex h-5 cursor-pointer items-center gap-1 rounded px-2 text-xs outline-none focus-visible:ring-2" > <span className="text-foreground/60">Workspace:</span> <span className="font-medium">{current?.label ?? value}</span> <svg className="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden > <path d="M6 9l6 6 6-6" /> </svg> </button> {open ? ( <div role="menu" aria-label="Switch workspace" className="border-border bg-background absolute top-full right-0 z-50 mt-1 w-64 rounded-md border p-1 shadow-xl" > {WORKSPACE_DESCRIPTORS.map((descriptor) => { const selected = descriptor.kind === value; return ( <button key={descriptor.kind} type="button" role="menuitemradio" aria-checked={selected} onClick={() => { onChange(descriptor.kind); setOpen(false); }} className={`focus-visible:ring-accent/50 flex w-full flex-col items-start gap-0.5 rounded px-2 py-1.5 text-left outline-none focus-visible:ring-2 ${ selected ? 'bg-border/50 text-foreground' : 'text-foreground/90 hover:bg-border/40' }`} > <span className="text-xs font-medium">{descriptor.label}</span> <span className="text-foreground/60 text-[11px] leading-snug"> {descriptor.description} </span> </button> ); })} </div> ) : null} </div> );}