Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/frontend/packages/swarm-ui/src/ui/expandable-card/ExpandableCard.tsx
iris 8a2e5203ad swarm-ui: shared Hint, ActionRow, Toolbar, EmptyState, ExternalLink and ExpandableCard replace per-page copies
Hint, ActionRow, Toolbar, EmptyState and ExternalLink replace the muted
text, button row, refresh bar, empty line and _blank anchor that each page
restyled under its own class. ExpandableCard (with a loading twin) holds the
builds and recent-runs cards, which had identical head and body CSS. The
create form's raw checkbox becomes CheckboxField. The components page shows
each new piece.
2026-11-06 18:41:25 +01:00

112 lines
2.8 KiB
TypeScript

// <ExpandableCard> — a card in a list whose head is a button that opens its
// body in place: a build, a classifier run. The head holds a title, a badge
// and a line of meta (when it started, how long it took); the body is shown
// only while the card is open. It is controlled (`open`, `onToggle`), so a
// page can keep its open cards across a refresh and open one for the user.
//
// Not `ui/disclosure`: that is a `<details>` for one section of a page, and a
// list of these needs the page to own which are open. The head is a real
// `<button>` with `aria-expanded` and `aria-controls`.
//
// `ExpandableCardSkeleton` is its twin for a list that is loading: the same
// card and the same head layout with a placeholder in each slot.
import type { ComponentChildren } from "preact";
import { Skeleton } from "../skeleton/Skeleton.js";
import { Card } from "../card/Card.js";
import "./ExpandableCard.css";
function Head({
title,
badge,
meta,
metaOnOwnRow,
}: {
title: ComponentChildren;
badge: ComponentChildren;
meta: ComponentChildren;
metaOnOwnRow: boolean;
}) {
return (
<>
<span class="ui-expandable-title">{title}</span>
{badge}
<span
class={
"ui-expandable-meta" + (metaOnOwnRow ? " ui-expandable-meta-row" : "")
}
>
{meta}
</span>
</>
);
}
export function ExpandableCard({
id,
open,
onToggle,
title,
badge,
meta,
metaOnOwnRow = false,
class: extraClass,
children,
}: {
/** The id of the card's element, for a link that scrolls to it. */
id: string;
open: boolean;
onToggle: () => void;
title: ComponentChildren;
badge: ComponentChildren;
meta: ComponentChildren;
/** Put the meta line on a row of its own under the title and badge. */
metaOnOwnRow?: boolean;
class?: string;
children: ComponentChildren;
}) {
const bodyId = `${id}-body`;
return (
<Card class={extraClass}>
<div id={id}>
<button
type="button"
class="ui-expandable-head"
aria-expanded={open}
aria-controls={bodyId}
onClick={onToggle}
>
<Head
title={title}
badge={badge}
meta={meta}
metaOnOwnRow={metaOnOwnRow}
/>
</button>
{open ? (
<div class="ui-expandable-body" id={bodyId}>
{children}
</div>
) : null}
</div>
</Card>
);
}
export function ExpandableCardSkeleton({
metaOnOwnRow = false,
}: {
metaOnOwnRow?: boolean;
}) {
return (
<Card>
<div class="ui-expandable-head ui-expandable-head-static">
<Head
title={<Skeleton width="12em" />}
badge={<Skeleton width="5em" height="1.6em" />}
meta={<Skeleton width="14em" />}
metaOnOwnRow={metaOnOwnRow}
/>
</div>
</Card>
);
}