// — the one generic container primitive every route needs: a // bordered surface with an optional title, no other opinions. Not a // card-with-actions/footer/whatever kit — those get added the first // time a real page actually needs one, not speculatively ahead of it. // // `actions` is that one addition: a slot in the title row, right- // aligned, for a control that belongs next to the panel's own heading // rather than its own row inside the body (design guide: "a control // belongs next to the thing it affects, not tucked into a catch-all // menu" — and per mara's review on the refresh-interval PR, not a // dedicated row stealing vertical space from the panel's actual // content either). `HivesPage`'s refresh-interval picker is the // motivating caller. // // `onClose`: end-of-header close (✕) for `Dialog`'s `plain` mode. // // `icon` is a small header glyph, left of the title — the swarm-ui // design guide's own whimsy reference (before this, the guide only // pointed at the dashboard's matrix-rain background). Grew out of // CreateAgentForm's one-off 🪪 dropped straight into a panel's body // copy: mara's call on review was that a single ad-hoc emoji isn't // whimsy in the guide's sense (small, delightful, *consistent*), it // should be a real theme every panel can opt into the same way. Plain // `string` (an emoji literal), not an icon-library asset — same // lightweight-glyph precedent `RefreshIntervalPicker`'s 🕐 set before // this. Chosen per panel, no default: not every panel needs one, and // there's no single semantic mapping (e.g. "jobs" pages) worth // hardcoding. `aria-hidden` — decorative only, the title text still // carries the actual label, so this never becomes a second source of // truth an assistive-tech user has to parse. import type { ComponentChildren } from "preact"; import "./Panel.css"; export function Panel({ title, icon, actions, onClose, children, class: extraClass, }: { title?: string; icon?: string; actions?: ComponentChildren; /** Renders a close (✕) button at the end of the header — see file-top * comment. Omit for a panel that isn't a dialog's own content. */ onClose?: () => void; children: ComponentChildren; /** Extra class on the outer `
` — e.g. flex-sizing a panel * that's one of several sharing a row (`AgentsPage`'s list/detail * split). Omit for the plain single-panel case every other caller is. */ class?: string; }) { return (
{title || icon || actions || onClose ? (
{icon ? ( ) : null} {title ?

{title}

: null} {actions || onClose ? (
{actions ?
{actions}
: null} {onClose ? ( ) : null}
) : null}
) : null}
{children}
); }