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.
112 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|