// — a generic modal overlay, native `` element rather // than a hand-rolled focus-trap + backdrop: `showModal()` gives focus // trapping, Escape-to-close, and a real `::backdrop` for free, and // `@hive/shared`'s own shadow-DOM `hive-dialog` custom element isn't an // option here — swarm-ui's esbuild config can't consume those custom // elements at all (a separate, already-tracked gap). // No opinion on content beyond a close button; the caller's own markup // supplies whatever heading/layout it needs (its first real caller, // the create-agent form, already has an established two-panel layout // from its former life as a standalone route — reusing that unchanged // is simpler and lower-risk than re-deriving a modal-specific layout // for content mara has already reviewed). // // `plain` (mara, screenshot of the matrix-account dialog: "the outer // card does not serve a purpose. move the close btn into the bar and // drop one layer"): content that's already a `Panel` — a bordered // card with its own header bar — nested inside this dialog's own // bordered card produced two concentric boxes in the same `--bg-elev`, // one serving no purpose. `plain` drops this dialog's own card chrome // (border/background/padding) and its floating close button, so a // `Panel` (with its own `onClose` wired into its header) becomes the // dialog's sole visible card instead of a second one nested inside it. // Escape and backdrop-click still close it either way — those are the // native ``'s own behavior, not this button's. import { useEffect, useRef } from "preact/hooks"; import type { ComponentChildren } from "preact"; import "./Dialog.css"; export function Dialog({ open, onClose, label, children, plain = false, }: { open: boolean; onClose: () => void; // `aria-label` only, no visible title row — see file-top comment on // why content owns its own heading. label: string; children: ComponentChildren; /** Drop this dialog's own card chrome + floating close button — see * file-top comment. Pair with a `Panel` (or similar) that renders its * own header + `onClose` button as the dialog's content. */ plain?: boolean; }) { const ref = useRef(null); // Drives the native open/closed state from the `open` prop rather // than mounting/unmounting the element — `showModal()`/`close()` are // imperative, there's no declarative `` equivalent that // also gets you the backdrop + focus trap. useEffect(() => { const el = ref.current; if (!el) return; if (open && !el.open) el.showModal(); else if (!open && el.open) el.close(); }, [open]); // The dialog's own `close` event fires on Escape (and would fire on // a native
submit, unused here) — syncing it // back to the caller's state keeps `open` truthful even when nothing // in this component's own JS drove the close. useEffect(() => { const el = ref.current; if (!el) return; function handleClose() { onClose(); } el.addEventListener("close", handleClose); return () => el.removeEventListener("close", handleClose); }, [onClose]); return ( ` element itself (not any child) // exactly when it's outside the rendered content box — inside // the box, the click target is always some descendant. Standard // "click the backdrop to close" trick for native ``. onClick={(e) => { if (e.target === ref.current) onClose(); }} > {plain ? null : ( )}
{children}
); }