"use client"; import { useEffect, useId, useRef, useState, type FormEvent } from "react"; import "./glass-lid.css"; /** * Glass Lid * Read-only settings sit under a pane of glass: you can read them, not touch * them. Edit lifts the lid on its top edge and the values become fields in * place; Save lowers it again. A section managed by someone else keeps its lid * shut and says who holds the key. */ export type LidField = { id: string; label: string; value: string; type?: "text" | "email" | "tel"; autoComplete?: string; /** Shown under the value in both modes. */ hint?: string; required?: boolean; }; type Props = { title: string; description?: string; fields: LidField[]; /** Resolve to close the lid; throw an Error to keep it open with the message. */ onSave?: (values: Record) => Promise | void; /** When set, the lid stays shut and this names who manages the section. */ lockedBy?: string; theme?: "paper" | "night"; motion?: "full" | "reduced"; }; export function GlassLid({ title, description, fields, onSave, lockedBy, theme = "paper", motion = "full" }: Props) { const [open, setOpen] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const [values, setValues] = useState(() => Object.fromEntries(fields.map((f) => [f.id, f.value]))); const [draft, setDraft] = useState(values); const [status, setStatus] = useState(""); const editRef = useRef(null); const firstRef = useRef(null); const uid = useId(); const returnFocus = useRef(false); useEffect(() => { if (open) firstRef.current?.focus({ preventScroll: true }); else if (returnFocus.current) { returnFocus.current = false; editRef.current?.focus({ preventScroll: true }); } }, [open]); const begin = () => { setDraft(values); setError(""); setOpen(true); setStatus(`Editing ${title}.`); }; const cancel = () => { returnFocus.current = true; setOpen(false); setError(""); setStatus("Changes discarded."); }; const save = async (e: FormEvent) => { e.preventDefault(); setSaving(true); setError(""); try { await onSave?.(draft); setValues(draft); returnFocus.current = true; setOpen(false); setStatus(`${title} saved.`); } catch (err) { setError(err instanceof Error ? err.message : "Couldn't save. Try again."); } finally { setSaving(false); } }; const changed = fields.some((f) => draft[f.id] !== values[f.id]); return (
{ if (e.key === "Escape" && open && !saving) { e.preventDefault(); cancel(); } }} >

{title}

{description &&

{description}

}
{lockedBy ? (

Managed by {lockedBy}

) : ( !open && ( ) )}
{fields.map((f, i) => (
{open ? ( setDraft((d) => ({ ...d, [f.id]: e.target.value }))} disabled={saving} aria-describedby={f.hint ? `${uid}-${f.id}-h` : undefined} /> ) : ( {values[f.id] || Not set} )} {f.hint && ( {f.hint} )}
))}
{/* The pane: a hairline edge, a bevel and one reflection. Purely visual. */}
{open && (

{error}

)}

{status}

); }