Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: the refresh picker of a polling card sits in the card's header row

On the jobs, builds and hives cards the picker was alone on the first line of
the body. DisclosureAction puts a body's control in the header of the
disclosure it is in, beside the chevron and outside the toggle, so it costs no
row and pressing it does not fold the card.
This commit is contained in:
iris 2026-11-06 20:24:25 +01:00 • committed by mara
commit bf7345405f
5 changed files with 43 additions and 9 deletions

View file

@ -22,9 +22,9 @@ import type { ComponentChildren } from "preact";
import { useState } from "preact/hooks";
import { JobqGraph } from "@hive/shared/jobq-graph.js";
import { JobqRollup } from "@hive/shared/jobq-rollup.js";
import { DisclosureAction } from "../ui/disclosure/DisclosureAction.js";
import { LazyDisclosure } from "../ui/disclosure/LazyDisclosure.js";
import { Skeleton, SkeletonRegion } from "../ui/skeleton/Skeleton.js";
import { Toolbar } from "../ui/toolbar/Toolbar.js";
import { BuildsCard } from "./builds/BuildsPage.js";
import { ReviewPage } from "./review/ReviewPage.js";
import {
@ -89,13 +89,13 @@ function JobsBody() {
return (
<>
<Toolbar>
<DisclosureAction>
<RefreshIntervalPicker
id="jobs-refresh"
value={intervalMs}
onChange={setIntervalMs}
/>
</Toolbar>
</DisclosureAction>
<JobqRollup endpoint="/api/jobq/rollup" refreshToken={refreshToken} />
<JobqGraph
endpoint="/api/jobq/graph"

View file

@ -16,7 +16,7 @@ import {
} from "../../ui/expandable-card/ExpandableCard.js";
import { Hint } from "../../ui/hint/Hint.js";
import { Preformatted } from "../../ui/preformatted/Preformatted.js";
import { Toolbar } from "../../ui/toolbar/Toolbar.js";
import { DisclosureAction } from "../../ui/disclosure/DisclosureAction.js";
import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js";
import {
RefreshIntervalPicker,
@ -134,14 +134,14 @@ function BuildsBody() {
return (
<>
<Toolbar>
<DisclosureAction>
<RefreshIntervalPicker
id="builds-refresh"
value={intervalMs}
onChange={setIntervalMs}
refreshing={refreshing}
/>
</Toolbar>
</DisclosureAction>
<p>
What the swarm builder built, newest first: agent config PR heads, and
the agents it builds from internal/meta before a deploy. How each ended,

View file

@ -17,6 +17,7 @@ import { useState } from "preact/hooks";
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
import { ExternalLink } from "../../ui/external-link/ExternalLink.js";
import { DisclosureAction } from "../../ui/disclosure/DisclosureAction.js";
import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js";
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
import {
@ -26,7 +27,6 @@ import {
import { fetchJson, usePolled } from "../../ui/polled/usePolled.js";
import { Table, type TableColumn } from "../../ui/table/Table.js";
import { TableSkeleton } from "../../ui/table/TableSkeleton.js";
import { Toolbar } from "../../ui/toolbar/Toolbar.js";
import { domainCell } from "../hive-domain.js";
type Freshness = "fresh" | "stale" | "never_reported" | "unknown";
@ -125,14 +125,14 @@ function HiveRoster() {
return (
<>
<Toolbar>
<DisclosureAction>
<RefreshIntervalPicker
id="hives-refresh"
value={intervalMs}
onChange={setIntervalMs}
refreshing={refreshing}
/>
</Toolbar>
</DisclosureAction>
{error ? (
<ApiErrorPanel
context="failed to load the hive roster"

View file

@ -0,0 +1,18 @@
/* The disclosure is the positioning box; the action sits on the title line of
its header (the header's top padding, one line of title) and to the left of
the chevron. The hint stops short of it, so the two do not overlap. */
.ui-disclosure {
position: relative;
}
.ui-disclosure-action {
position: absolute;
z-index: 1;
top: 0.75em;
right: 2.6em;
display: flex;
align-items: center;
height: 1.6em;
}
.ui-disclosure:has(.ui-disclosure-action) .ui-disclosure-hint {
margin-right: 7em;
}

View file

@ -0,0 +1,16 @@
// <DisclosureAction> — a control for the header row of the disclosure it is in:
// the refresh picker of a card that polls. The body owns the control's state, so
// a body renders it where it needs the state, and it shows in the header, beside
// the chevron, instead of taking a row of the body. It is a sibling of the
// header's toggle, not inside it, so pressing it does not fold the card. A
// disclosure that is shut has no body, so the control is there only while open.
import type { ComponentChildren } from "preact";
import "./DisclosureAction.css";
export function DisclosureAction({
children,
}: {
children: ComponentChildren;
}) {
return <div class="ui-disclosure-action">{children}</div>;
}