From acf82beba3e9c376c07e95a85dfd5bf15ac01cc9 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 21 Oct 2026 23:49:57 +0200 Subject: [PATCH] swarm-ui: add CheckboxField and use it for the issue report toggle and the account delete option --- docs/web-ui/design-guide.md | 8 +++-- .../src/agent/detail/LinkedAccounts.tsx | 18 ++++------ .../swarm-ui/src/pages/ComponentsPage.tsx | 28 ++++++++++++++++ .../swarm-ui/src/pages/IssueReportPage.css | 6 ---- .../swarm-ui/src/pages/IssueReportPage.tsx | 17 ++++------ .../src/ui/checkbox-field/CheckboxField.css | 25 ++++++++++++++ .../src/ui/checkbox-field/CheckboxField.tsx | 33 +++++++++++++++++++ 7 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.css create mode 100644 frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.tsx diff --git a/docs/web-ui/design-guide.md b/docs/web-ui/design-guide.md index 654a435e..ea367054 100644 --- a/docs/web-ui/design-guide.md +++ b/docs/web-ui/design-guide.md @@ -150,9 +150,11 @@ that needs it, not after a contributor styles it inline and leaves it for later — a page reaching for a component that's already there has ready-made blocks to build with, instead of the next contributor duplicating ad-hoc styling that someone then has to hunt down and consolidate. -`Panel`/`Badge`/`Table`/`TextField`/`SelectField`/`Button` are the -primitives that exist; `/components` always has the current, complete -list — this doc won't try to keep a duplicate inventory in sync. +`Panel`/`Badge`/`Table`/`TextField`/`SelectField`/`CheckboxField`/`Button` +are the primitives that exist; `/components` always has the current, complete +list — this doc won't try to keep a duplicate inventory in sync. A yes/no +option in a form uses `CheckboxField`, which sizes its row like the other +fields, rather than a bare ``. **Every new `ui/` component gets a demo section on `/components` the same day it lands** — no primitive without a place to see it. One diff --git a/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx b/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx index fa1eaf25..e9cee640 100644 --- a/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx +++ b/frontend/packages/swarm-ui/src/agent/detail/LinkedAccounts.tsx @@ -13,6 +13,7 @@ import { useEffect, useState } from "preact/hooks"; import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js"; import { Badge } from "@hive/shared/badge.js"; +import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js"; import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js"; import { accountKindLabel } from "./account-kind.js"; import "./LinkedAccounts.css"; @@ -136,17 +137,12 @@ export function LinkedAccounts({ removed from the swarm secret store. The agent is not told.

{deleteTarget.kind === "matrix" ? ( - + ) : null} {deleteError ? ( diff --git a/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx b/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx index 984d193e..33202b6f 100644 --- a/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx @@ -27,6 +27,7 @@ import { SelectField, type SelectOption, } from "../ui/select-field/SelectField.js"; +import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js"; import { Button, type ButtonVariant } from "../ui/button/Button.js"; import { Dialog } from "../ui/dialog/Dialog.js"; import { ConfirmDialog } from "../ui/confirm-dialog/ConfirmDialog.js"; @@ -156,6 +157,18 @@ function SelectFieldSample() { ); } +function CheckboxFieldSample() { + const [checked, setChecked] = useState(true); + return ( + + ); +} + function RefreshIntervalPickerSample() { const [value, setValue] = useState(30_000); return ( @@ -517,6 +530,21 @@ export function ComponentsPage() { +
+ + + + + {}} + disabled + /> + +
+
{BUTTON_VARIANTS.map((variant) => ( diff --git a/frontend/packages/swarm-ui/src/pages/IssueReportPage.css b/frontend/packages/swarm-ui/src/pages/IssueReportPage.css index b69e7b20..dd3a227e 100644 --- a/frontend/packages/swarm-ui/src/pages/IssueReportPage.css +++ b/frontend/packages/swarm-ui/src/pages/IssueReportPage.css @@ -15,12 +15,6 @@ gap: 1.25em; margin-bottom: 1em; } -.issue-report-toggle { - display: flex; - align-items: center; - gap: 0.4em; - cursor: pointer; -} .issue-report-sort-btn { background: none; border: none; diff --git a/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx b/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx index 6d98772c..55430d18 100644 --- a/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx @@ -33,6 +33,7 @@ import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js"; import { Badge } from "@hive/shared/badge.js"; import { useLocalSetting } from "@hive/shared/settings-storage.js"; import { Panel } from "../ui/panel/Panel.js"; +import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js"; import { SelectField } from "../ui/select-field/SelectField.js"; import { Table, type TableColumn } from "../ui/table/Table.js"; import { ALL_REPOS, reportUrl, repoOptions } from "./issue-report-repo.js"; @@ -378,16 +379,12 @@ export function IssueReportPage() { onChange={setRepoFilter} options={repoOptions(repos, repoFilter)} /> - +
{reposError ? ( — box and label on one row. `min-height` is the same + touch-target floor `.ui-form-control` sets (FormField.css has the + reasoning), so a checkbox beside a text or select field is as tall as it and + its label is centred on the same line. The box takes the accent colour from + the shared theme instead of the browser's blue. */ +.ui-checkbox-field { + display: flex; + align-items: center; + gap: 0.5em; + min-height: 2.75em; + cursor: pointer; + color: var(--fg); +} +.ui-checkbox-field input { + accent-color: var(--purple); + width: 1.1em; + height: 1.1em; + margin: 0; + flex: none; + cursor: inherit; +} +.ui-checkbox-field:has(input:disabled) { + color: var(--muted); + cursor: not-allowed; +} diff --git a/frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.tsx b/frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.tsx new file mode 100644 index 00000000..791e72e2 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/checkbox-field/CheckboxField.tsx @@ -0,0 +1,33 @@ +// — a labelled checkbox, the on/off sibling of `TextField` and +// `SelectField`. The label sits beside the box, not above it, and the whole +// row is one click target at the kit's touch-target height, so a checkbox next +// to a `SelectField` lines up with it and a bare browser checkbox no longer +// sets the row's size. +import "./CheckboxField.css"; + +export function CheckboxField({ + id, + label, + checked, + onChange, + disabled, +}: { + id: string; + label: string; + checked: boolean; + onChange: (checked: boolean) => void; + disabled?: boolean; +}) { + return ( + + ); +}