Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/frontend/packages/swarm-ui/src/ui/skeleton/Skeleton.tsx
iris a9f0d61daa swarm-ui: an agent's settings and AGENTS.md show placeholders while they load
BadgeSkeleton is the twin of Badge: the real Badge frame, as tall as an
interactive one (the settings values are buttons), with a placeholder for its
value. Permissions, resource caps, internet and allowed domains use it in place of
their "loading…" text, the details popover shows lines of text, and the
AGENTS.md editor a block, in place of theirs. SkeletonLines is a few stacked
lines for such blocks.
2026-11-06 17:27:55 +01:00

70 lines
2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// <Skeleton> — one grey placeholder shape that shimmers, for a view that is
// waiting on the server. A component that shows loaded data has a twin that
// fills the same layout with these shapes instead of its sub components (see
// `AgentCardSkeleton`), so the page does not jump when the data lands.
//
// <SkeletonRegion> wraps a group of them for assistive tech: the shapes
// themselves are `aria-hidden`, and the region says once that something is
// loading.
import type { ComponentChildren } from "preact";
import "./Skeleton.css";
export type SkeletonShape = "line" | "circle" | "block";
export function Skeleton({
shape = "line",
width,
height,
class: extraClass,
}: {
/** `line`: a rounded bar the height of a line of text; `circle`; `block`: a
* rounded square that fills `width` × `height`. */
shape?: SkeletonShape;
/** Any CSS length; a line fills its row when this is left out. */
width?: string;
height?: string;
class?: string;
}) {
const classes = ["ui-skeleton", `ui-skeleton-${shape}`, extraClass]
.filter(Boolean)
.join(" ");
return <span class={classes} style={{ width, height }} aria-hidden="true" />;
}
export function SkeletonRegion({
label,
children,
}: {
/** What is loading, as a screen reader says it: "the agents". */
label: string;
children: ComponentChildren;
}) {
return (
<div class="ui-skeleton-region" role="status">
<span class="ui-skeleton-label">loading {label}</span>
{children}
</div>
);
}
const LINE_WIDTHS = ["90%", "75%", "85%", "60%"];
/** A few lines of text, for a block of prose or a list still loading. */
export function SkeletonLines({
label,
count = 3,
}: {
/** What is loading, as a screen reader says it. */
label: string;
count?: number;
}) {
return (
<SkeletonRegion label={label}>
<div class="ui-skeleton-lines">
{Array.from({ length: count }, (_, i) => (
<Skeleton key={i} width={LINE_WIDTHS[i % LINE_WIDTHS.length]} />
))}
</div>
</SkeletonRegion>
);
}