Dialog and Panel both drew their own bordered/backgrounded card in the same --bg-elev, so a Panel-based dialog (create-agent, link-matrix-account) rendered as two concentric cards with a floating close button on the outer one and no purpose to it. Give Dialog a "plain" mode that drops its own card chrome (border, background, padding) and floating close button, and give Panel an optional onClose that renders a close button at the end of its own header row instead. AgentsPage's two Panel-backed dialogs now use plain + Panel's onClose, so the Panel is the dialog's only visible card. ConfirmDialog (no Panel of its own) is unaffected — plain defaults to false, unchanged card + floating close button. Added a ComponentsPage sample demonstrating the plain + onClose pairing. Verified both dialog modes via a real headless-chromium screenshot (plain dialog: single card, close button in the header bar; default dialog: unchanged floating close button).
84 lines
3.4 KiB
TypeScript
84 lines
3.4 KiB
TypeScript
// <Panel> — 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 `<section>` — 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 (
|
|
<section class={extraClass ? `ui-panel ${extraClass}` : "ui-panel"}>
|
|
{title || icon || actions || onClose ? (
|
|
<div class="ui-panel-header">
|
|
{icon ? (
|
|
<span class="ui-panel-icon" aria-hidden="true">
|
|
{icon}
|
|
</span>
|
|
) : null}
|
|
{title ? <h2 class="ui-panel-title">{title}</h2> : null}
|
|
{actions || onClose ? (
|
|
<div class="ui-panel-end">
|
|
{actions ? <div class="ui-panel-actions">{actions}</div> : null}
|
|
{onClose ? (
|
|
<button
|
|
type="button"
|
|
class="ui-panel-close"
|
|
aria-label="close"
|
|
onClick={onClose}
|
|
>
|
|
✕
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
<div class="ui-panel-body">{children}</div>
|
|
</section>
|
|
);
|
|
}
|