Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: add CheckboxField and use it for the issue report toggle and the account delete option

This commit is contained in:
iris 2026-10-21 23:49:57 +02:00 • committed by mara
commit acf82beba3
7 changed files with 105 additions and 30 deletions

View file

@ -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 `<input type="checkbox">`.
**Every new `ui/` component gets a demo section on `/components` the
same day it lands** — no primitive without a place to see it. One

View file

@ -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.
</p>
{deleteTarget.kind === "matrix" ? (
<label>
<input
type="checkbox"
checked={revoke}
onChange={(e) =>
setRevoke((e.target as HTMLInputElement).checked)
}
/>{" "}
also log the token out at the homeserver; if that fails, the
account is kept
</label>
<CheckboxField
id="linked-accounts-revoke"
label="also log the token out at the homeserver; if that fails, the account is kept"
checked={revoke}
onChange={setRevoke}
/>
) : null}
{deleteError ? (
<ApiErrorPanel context="delete failed" problem={deleteError} />

View file

@ -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 (
<CheckboxField
id="sample-checkbox-field"
label="hide blocked (open dependency)"
checked={checked}
onChange={setChecked}
/>
);
}
function RefreshIntervalPickerSample() {
const [value, setValue] = useState<RefreshIntervalMs>(30_000);
return (
@ -517,6 +530,21 @@ export function ComponentsPage() {
</Sample>
</Section>
<Section title="CheckboxField">
<Sample label="editable">
<CheckboxFieldSample />
</Sample>
<Sample label="disabled">
<CheckboxField
id="sample-checkbox-field-disabled"
label="also log the token out"
checked={false}
onChange={() => {}}
disabled
/>
</Sample>
</Section>
<Section title="Button">
<div class="components-chip-row">
{BUTTON_VARIANTS.map((variant) => (

View file

@ -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;

View file

@ -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)}
/>
<label class="issue-report-toggle">
<input
type="checkbox"
checked={hideBlocked}
onChange={(e) =>
setHideBlocked((e.target as HTMLInputElement).checked)
}
/>
hide blocked (open dependency)
</label>
<CheckboxField
id="issue-report-hide-blocked"
label="hide blocked (open dependency)"
checked={hideBlocked}
onChange={setHideBlocked}
/>
</div>
{reposError ? (
<ApiErrorPanel

View file

@ -0,0 +1,25 @@
/* <CheckboxField> — 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;
}

View file

@ -0,0 +1,33 @@
// <CheckboxField> — 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 (
<label class="ui-checkbox-field" for={id}>
<input
id={id}
type="checkbox"
checked={checked}
disabled={disabled}
onChange={(e) => onChange((e.target as HTMLInputElement).checked)}
/>
<span>{label}</span>
</label>
);
}