Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: the schedules, notification flows and flow editor show placeholders while they load

The schedules card, the notification router card (flows and recent
deliveries) and the flow editor showed a bare 'loading…' line. They now show
grey placeholders in the layout of what is coming: schedule cards with the
real card's row heights, flow cards with a block for the graph, the
deliveries table's header over blank rows, and lines for the editor.
This commit is contained in:
iris 2026-11-06 19:26:20 +01:00 • committed by mara
commit 5eef1f4a3d
3 changed files with 111 additions and 7 deletions

View file

@ -31,3 +31,19 @@
.ui-schedule-card-actions {
gap: 0.5em;
}
/* The loading twin's list: the gap between cards in the real list. */
.ui-schedule-card-list {
display: flex;
flex-direction: column;
gap: 0.5em;
}
/* The loading twin's rows take the height of a line of text, and its head the
height of the title, so the card is the size of a real one. */
.ui-schedule-card-head-skeleton {
min-height: 1.76em;
}
.ui-schedule-card-line {
display: flex;
align-items: center;
min-height: 1.6em;
}

View file

@ -7,6 +7,7 @@ import { ActionRow } from "../../ui/action-row/ActionRow.js";
import { Button } from "../../ui/button/Button.js";
import { Card } from "../../ui/card/Card.js";
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
import { Skeleton, SkeletonRegion } from "../../ui/skeleton/Skeleton.js";
import {
fmtInterval,
fmtLocal,
@ -87,3 +88,49 @@ export function ScheduleCard({
</Card>
);
}
// The twin of `ScheduleCard` for a list that is loading: the same card and
// layout classes, with a placeholder in each slot. The cards sit in the
// container the real list uses, so the gap between them is the same.
function ScheduleCardSkeleton() {
return (
<div>
<Card class="ui-schedule-card">
<div class="ui-schedule-card-head ui-schedule-card-head-skeleton">
<Skeleton width="10em" height="1.2em" class="ui-schedule-card-name" />
<Skeleton width="4em" height="1.6em" />
<Skeleton width="6em" height="1.6em" />
</div>
<div class="ui-schedule-card-line">
<Skeleton width="14em" />
</div>
<div class="ui-schedule-card-line">
<Skeleton width="90%" />
</div>
<div class="ui-schedule-card-line">
<Skeleton width="60%" />
</div>
<div class="ui-schedule-card-line">
<Skeleton width="12em" />
</div>
<ActionRow align="end" class="ui-schedule-card-actions">
<Skeleton width="4em" height="2.75em" />
<Skeleton width="4.5em" height="2.75em" />
<Skeleton width="5em" height="2.75em" />
</ActionRow>
</Card>
</div>
);
}
export function ScheduleListSkeleton({ count = 3 }: { count?: number }) {
return (
<SkeletonRegion label="the schedules">
<div class="ui-schedule-card-list">
{Array.from({ length: count }, (_, i) => (
<ScheduleCardSkeleton key={i} />
))}
</div>
</SkeletonRegion>
);
}

View file

@ -24,7 +24,7 @@ import {
import { fetchJson, usePolled } from "../../ui/polled/usePolled.js";
import type { TableColumn } from "../../ui/table/Table.js";
import type { AgentRow } from "../../agent/AgentTypes.js";
import { ScheduleCard } from "./ScheduleCard.js";
import { ScheduleCard, ScheduleListSkeleton } from "./ScheduleCard.js";
import { agentNamesFrom, agentOptionsFor } from "./agent-options.js";
import { ScheduleForm } from "./ScheduleForm.js";
import { scheduleState, type Schedule } from "./ScheduleTypes.js";
@ -184,7 +184,7 @@ function SchedulesBody() {
onCancel={() => setCreating(false)}
/>
) : null}
{!error && schedules === null ? <p>loading…</p> : null}
{!error && schedules === null ? <ScheduleListSkeleton /> : null}
{schedules ? (
<FilterableView
columns={COLUMNS}