swarm-ui: extract ConfirmDialog, use it for the destroy confirm
mara, reviewing the swarm-ui destroy-trigger PR: "why the extra styling? shouldnt there be a component that does this already?" There wasnt one -- Dialog is deliberately content-agnostic (see its own file-top comment), so the destroy confirm had grown its own page-scoped .agents-destroy-confirm/-actions CSS for what is really a generic "message + cancel/confirm button row" shape. Extracted ui/confirm-dialog/ConfirmDialog.tsx: wraps Dialog, owns the button row, leaves the message body to the caller via children. AgentsPage now uses it instead of a bare Dialog + bespoke CSS; deleted the now-unused AgentsPage.css.
This commit is contained in:
parent
becc025f13
commit
82a4324b17
4 changed files with 81 additions and 39 deletions
|
|
@ -1,17 +0,0 @@
|
||||||
/* <AgentsPage> — the destroy-confirm dialog's own layout. Everything
|
|
||||||
else on this page (the table, the wanted/destroy badges) draws chrome
|
|
||||||
from the shared `ui/` kit and needs nothing page-scoped; this file
|
|
||||||
exists only because the confirm dialog's copy + button row needed
|
|
||||||
somewhere to live, same reasoning as CreateAgentForm.css owning its
|
|
||||||
own layout next to the shared form-kit chrome it wraps. */
|
|
||||||
.agents-destroy-confirm {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1em;
|
|
||||||
max-width: 28em;
|
|
||||||
}
|
|
||||||
.agents-destroy-confirm-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
@ -33,6 +33,7 @@ import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
||||||
import { Button } from "../ui/button/Button.js";
|
import { Button } from "../ui/button/Button.js";
|
||||||
|
import { ConfirmDialog } from "../ui/confirm-dialog/ConfirmDialog.js";
|
||||||
import { Dialog } from "../ui/dialog/Dialog.js";
|
import { Dialog } from "../ui/dialog/Dialog.js";
|
||||||
import { Panel } from "../ui/panel/Panel.js";
|
import { Panel } from "../ui/panel/Panel.js";
|
||||||
import { RelativeTime } from "../ui/relative-time/RelativeTime.js";
|
import { RelativeTime } from "../ui/relative-time/RelativeTime.js";
|
||||||
|
|
@ -43,7 +44,6 @@ import {
|
||||||
} from "../ui/refresh-interval/RefreshInterval.js";
|
} from "../ui/refresh-interval/RefreshInterval.js";
|
||||||
import { Table, type TableColumn } from "../ui/table/Table.js";
|
import { Table, type TableColumn } from "../ui/table/Table.js";
|
||||||
import { CreateAgentForm } from "./CreateAgentForm.js";
|
import { CreateAgentForm } from "./CreateAgentForm.js";
|
||||||
import "./AgentsPage.css";
|
|
||||||
|
|
||||||
interface ConfigPrStatus {
|
interface ConfigPrStatus {
|
||||||
pr_number: number;
|
pr_number: number;
|
||||||
|
|
@ -415,32 +415,23 @@ export function AgentsPage() {
|
||||||
>
|
>
|
||||||
<CreateAgentForm />
|
<CreateAgentForm />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Dialog
|
<ConfirmDialog
|
||||||
open={destroyTarget !== null}
|
open={destroyTarget !== null}
|
||||||
onClose={() => setDestroyTarget(null)}
|
|
||||||
label="destroy agent"
|
label="destroy agent"
|
||||||
|
onCancel={() => setDestroyTarget(null)}
|
||||||
|
onConfirm={() => destroyTarget && void destroyAgent(destroyTarget)}
|
||||||
|
confirmLabel="destroy"
|
||||||
>
|
>
|
||||||
{destroyTarget ? (
|
{destroyTarget ? (
|
||||||
<div class="agents-destroy-confirm">
|
<p>
|
||||||
<p>
|
Destroy <strong>{destroyTarget.name}</strong>? The hive tears its
|
||||||
Destroy <strong>{destroyTarget.name}</strong>? The hive tears its
|
container down on its next reconcile sweep. This is not reversible
|
||||||
container down on its next reconcile sweep. This is not reversible
|
from here — bringing it back means redeploying via "+ agent", which
|
||||||
from here — bringing it back means redeploying via "+ agent",
|
reuses the agent's existing identity, config repo, and forge
|
||||||
which reuses the agent's existing identity, config repo, and forge
|
collaborator access rather than starting over.
|
||||||
collaborator access rather than starting over.
|
</p>
|
||||||
</p>
|
|
||||||
<div class="agents-destroy-confirm-actions">
|
|
||||||
<Button onClick={() => setDestroyTarget(null)}>cancel</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={() => void destroyAgent(destroyTarget)}
|
|
||||||
>
|
|
||||||
destroy
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</Dialog>
|
</ConfirmDialog>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* <ConfirmDialog> — body + button-row layout only; `Dialog.css` owns the
|
||||||
|
surrounding modal chrome, `Button.css` owns the buttons themselves. */
|
||||||
|
.confirm-dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
max-width: 28em;
|
||||||
|
}
|
||||||
|
.confirm-dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75em;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
// <ConfirmDialog> — a `Dialog` pre-wired for the "message + cancel/confirm
|
||||||
|
// button row" shape every confirm-before-acting flow needs. Extracted out
|
||||||
|
// of AgentsPage's destroy confirmation on mara's review question ("why the
|
||||||
|
// extra styling? shouldnt there be a component that does this already?")
|
||||||
|
// — there wasn't one yet, so that page had grown its own
|
||||||
|
// `.agents-destroy-confirm`/`-actions` layout CSS for what turns out to be
|
||||||
|
// a generic shape. `Dialog` itself stays opinion-free about content (see
|
||||||
|
// its own file-top comment); this is the one layer up that isn't, so a
|
||||||
|
// future confirm flow gets the button row for free instead of another
|
||||||
|
// page-scoped CSS file.
|
||||||
|
//
|
||||||
|
// The message itself stays caller-owned via `children` — only the dialog
|
||||||
|
// wiring, spacing, and button row are shared.
|
||||||
|
import type { ComponentChildren } from "preact";
|
||||||
|
import { Button } from "../button/Button.js";
|
||||||
|
import { Dialog } from "../dialog/Dialog.js";
|
||||||
|
import "./ConfirmDialog.css";
|
||||||
|
|
||||||
|
export function ConfirmDialog({
|
||||||
|
open,
|
||||||
|
label,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
confirmLabel = "confirm",
|
||||||
|
cancelLabel = "cancel",
|
||||||
|
confirmDisabled = false,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
label: string;
|
||||||
|
onCancel: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
confirmLabel?: string;
|
||||||
|
cancelLabel?: string;
|
||||||
|
confirmDisabled?: boolean;
|
||||||
|
children: ComponentChildren;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onCancel} label={label}>
|
||||||
|
<div class="confirm-dialog">
|
||||||
|
<div class="confirm-dialog-body">{children}</div>
|
||||||
|
<div class="confirm-dialog-actions">
|
||||||
|
<Button onClick={onCancel}>{cancelLabel}</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={onConfirm}
|
||||||
|
disabled={confirmDisabled}
|
||||||
|
>
|
||||||
|
{confirmLabel}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue