swarm-ui: drop the redundant outer dialog card, move close into the panel header

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).
This commit is contained in:
iris 2026-09-13 00:58:53 +02:00
commit 8a4c613e4e
8 changed files with 158 additions and 18 deletions

View file

@ -20,14 +20,43 @@
line-height: 1;
}
/* `margin-left: auto` (not `justify-content: space-between` on the
header) so actions still land at the right edge even on the rare
panel that has actions but no title. */
.ui-panel-actions {
header) so this cluster still lands at the right edge even on the
rare panel that has actions/onClose but no title. `actions` and the
close button (`onClose`) share this one wrapper rather than each
getting their own `margin-left: auto` slot see Panel.tsx's file-top
comment. */
.ui-panel-end {
display: flex;
align-items: center;
gap: 0.5em;
margin-left: auto;
}
.ui-panel-actions {
display: flex;
align-items: center;
gap: 0.5em;
}
/* Same interactive treatment as `Dialog`'s own `.ui-dialog-close`
(border/background on hover), scaled down to fit a header row instead
of floating absolutely over the whole card. */
.ui-panel-close {
display: flex;
align-items: center;
justify-content: center;
width: 1.8em;
height: 1.8em;
border: 1px solid transparent;
border-radius: 0.4em;
background: none;
color: var(--fg);
font-size: 1em;
line-height: 1;
cursor: pointer;
}
.ui-panel-close:hover {
border-color: var(--border);
background: var(--bg);
}
.ui-panel-body {
padding: 1em;
}

View file

@ -12,6 +12,8 @@
// 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
@ -33,12 +35,16 @@ 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
@ -47,7 +53,7 @@ export function Panel({
}) {
return (
<section class={extraClass ? `ui-panel ${extraClass}` : "ui-panel"}>
{title || icon || actions ? (
{title || icon || actions || onClose ? (
<div class="ui-panel-header">
{icon ? (
<span class="ui-panel-icon" aria-hidden="true">
@ -55,7 +61,21 @@ export function Panel({
</span>
) : null}
{title ? <h2 class="ui-panel-title">{title}</h2> : null}
{actions ? <div class="ui-panel-actions">{actions}</div> : 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>