Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: the hives card and the alerts table show placeholder rows under their own header while they load

TableSkeleton is the twin of Table: the same columns, so the same header cells,
with a placeholder shape in each cell of three blank rows and no sort or
filter, which have nothing to work on yet. The hives card and the alerts table
use it in place of their "loading…" text.
This commit is contained in:
iris 2026-11-06 00:50:26 +01:00
commit e149ff3315
5 changed files with 54 additions and 3 deletions

View file

@ -44,7 +44,9 @@ The Jobs page has none yet.
The pages that only display (agents, alerts, builds, review runs and the hives
card) show the answer they last saw at once, then replace it with the fresh one
while the spinner runs, so opening one never starts on an empty page. The browser
while the spinner runs, so opening one never starts on an empty page. On the very
first visit, the hives card and the alerts table show their own header with three
grey placeholder rows until the first answer comes. The browser
keeps the answer for itself only, and forgets it after a day, after a new build
of the UI, at **log out**, and when the login ends. The pages with an editor
(schedules, notification flows, broadcast) start empty as before.

View file

@ -15,6 +15,7 @@ import {
} from "../ui/refresh-interval/RefreshInterval.js";
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 { activeSinceMs, alertKey, listedAlerts, type Alert } from "./alerts.js";
const STATE_TONE: Record<string, BadgeTone> = {
@ -111,7 +112,9 @@ function AlertsBody() {
problem={error}
/>
) : null}
{!error && alerts === null ? <p>loading…</p> : null}
{!error && alerts === null ? (
<TableSkeleton columns={COLUMNS} label="the alerts" />
) : null}
{listed && listed.length === 0 ? (
<p>{showAll ? "no alerts." : "nothing is alerting."}</p>
) : null}

View file

@ -23,6 +23,7 @@ import {
} from "../ui/refresh-interval/RefreshInterval.js";
import { SplitView } from "../ui/split-view/SplitView.js";
import { Table, type TableColumn } from "../ui/table/Table.js";
import { TableSkeleton } from "../ui/table/TableSkeleton.js";
import { TextField } from "../ui/text-field/TextField.js";
import { TextAreaField } from "../ui/text-area-field/TextAreaField.js";
import {
@ -491,6 +492,9 @@ export function ComponentsPage() {
<AgentListSkeleton count={2} />
</SkeletonRegion>
</Sample>
<Sample label="table placeholder">
<TableSkeleton columns={TABLE_COLUMNS} label="the rows" />
</Sample>
</Section>
<Section title="MultiselectFilter">

View file

@ -24,6 +24,7 @@ import {
} from "../../ui/refresh-interval/RefreshInterval.js";
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 { domainCell } from "../hive-domain.js";
type Freshness = "fresh" | "stale" | "never_reported" | "unknown";
@ -142,7 +143,9 @@ function HiveRoster() {
problem={error}
/>
) : null}
{!error && hives === null ? <p>loading…</p> : null}
{!error && hives === null ? (
<TableSkeleton columns={COLUMNS} label="the hives" />
) : null}
{hives ? (
<Table
columns={COLUMNS}

View file

@ -0,0 +1,39 @@
// <TableSkeleton> — the twin of <Table> for a view that is waiting on its rows:
// the same header cells (the page passes the `columns` it gives its <Table>)
// over a few blank rows, with a placeholder shape in each cell instead of the
// real cell. Sort and filter are left out: there is nothing to sort or filter
// yet. The overall width matches, but the width of each column does not: a
// table sizes its columns from their content, which is not known yet, and the
// real header also carries sort and filter controls. The table is hidden from
// assistive tech; the region's label says that rows are loading.
import { Skeleton, SkeletonRegion } from "../skeleton/Skeleton.js";
import { Table, type TableColumn } from "./Table.js";
export function TableSkeleton<T>({
columns,
label,
rows = 3,
}: {
columns: TableColumn<T>[];
/** What is loading, as a screen reader says it: "the hives". */
label: string;
rows?: number;
}) {
const shapes: TableColumn<number>[] = columns.map((c) => ({
key: c.key,
header: c.header,
render: () => <Skeleton width="70%" height="1.4em" />,
}));
return (
<SkeletonRegion label={label}>
<div aria-hidden="true">
<Table
columns={shapes}
rows={Array.from({ length: rows }, (_, i) => i)}
rowKey={String}
storageKey="swarm-ui:table-skeleton"
/>
</div>
</SkeletonRegion>
);
}