2026-09-11 02:46:56 +02:00
|
|
|
/* ============================================================================
|
|
|
|
|
Card.Stat - Figma 25:792 (260 x 166)
|
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
|
The figure card used by the home page stats strip: a large accent number,
|
|
|
|
|
an optional trend chip, the label it counts, a rule and a caption. Never a
|
|
|
|
|
link - a number is not a destination.
|
|
|
|
|
|
|
|
|
|
USAGE
|
|
|
|
|
-----
|
|
|
|
|
<ul className="grid gap-lg sm:grid-cols-2 lg:grid-cols-4">
|
|
|
|
|
<Card.Stat
|
|
|
|
|
as="li"
|
|
|
|
|
value="2,450"
|
|
|
|
|
label="Participants reached"
|
|
|
|
|
caption="Cumulative regional outreach, updated each quarter."
|
|
|
|
|
trend={{ label: "12%", direction: "up", description: "up 12% on last quarter" }}
|
|
|
|
|
/>
|
|
|
|
|
<Card.Stat as="li" value="18" label="Municipalities engaged" />
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
// Arabic - Eastern Arabic numerals belong in the value string itself.
|
|
|
|
|
<Card.Stat value="٢٬٤٥٠" label="عدد المشاركين" />
|
|
|
|
|
========================================================================= */
|
|
|
|
|
|
|
|
|
|
import { CardIcon, CardShell, cx } from "./CardShell";
|
|
|
|
|
import type { CardElement } from "./types";
|
|
|
|
|
|
|
|
|
|
export interface CardStatTrend {
|
|
|
|
|
/** Visible text, e.g. "12%". */
|
|
|
|
|
label: string;
|
|
|
|
|
/** Up is the accent fill, down the danger fill. Defaults to up. */
|
|
|
|
|
direction?: "up" | "down";
|
|
|
|
|
/**
|
|
|
|
|
* Spoken instead of the bare figure, e.g. "up 12% on last quarter". The
|
|
|
|
|
* arrow is decorative, so without this a screen reader hears only "12%".
|
|
|
|
|
*/
|
|
|
|
|
description?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CardStatProps {
|
|
|
|
|
/** Pre-formatted for the locale - the card never formats a number itself. */
|
|
|
|
|
value: string;
|
|
|
|
|
label: string;
|
|
|
|
|
caption?: string;
|
|
|
|
|
trend?: CardStatTrend;
|
|
|
|
|
as?: CardElement;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function CardStat({
|
|
|
|
|
value,
|
|
|
|
|
label,
|
|
|
|
|
caption,
|
|
|
|
|
trend,
|
|
|
|
|
as = "article",
|
|
|
|
|
className,
|
|
|
|
|
}: CardStatProps) {
|
|
|
|
|
const isDown = trend?.direction === "down";
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<CardShell
|
|
|
|
|
as={as}
|
|
|
|
|
className={cx("flex-col items-start gap-md p-lg", className)}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex w-full items-center justify-between gap-sm">
|
|
|
|
|
<p className="display-sm tabular-nums text-text-accent">{value}</p>
|
|
|
|
|
|
|
|
|
|
{trend ? (
|
|
|
|
|
/* Figma paints the chip label in text/accent on a background/accent
|
|
|
|
|
fill - green on green, invisible. Kept as the accent fill with the
|
|
|
|
|
always-white on-accent label, and the danger fill when the figure
|
|
|
|
|
is falling. */
|
|
|
|
|
<span
|
|
|
|
|
className={cx(
|
2026-09-20 11:14:15 +02:00
|
|
|
"inline-flex shrink-0 items-center gap-2xs rounded-chip px-xs py-2xs",
|
2026-09-11 02:46:56 +02:00
|
|
|
"label-md text-text-on-accent",
|
|
|
|
|
isDown ? "bg-surface-danger" : "bg-surface-accent",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<CardIcon
|
|
|
|
|
name="arrow-up"
|
|
|
|
|
size={10}
|
|
|
|
|
className={isDown ? "rotate-180" : undefined}
|
|
|
|
|
/>
|
|
|
|
|
{trend.description ? (
|
|
|
|
|
<>
|
|
|
|
|
<span aria-hidden="true">{trend.label}</span>
|
|
|
|
|
<span className="sr-only">{trend.description}</span>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
trend.label
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p className="body-md-bold text-text-primary">{label}</p>
|
|
|
|
|
|
|
|
|
|
<div aria-hidden="true" className="w-full border-t border-border-subtle" />
|
|
|
|
|
|
|
|
|
|
{caption ? (
|
|
|
|
|
<p className="body-sm text-text-secondary">{caption}</p>
|
|
|
|
|
) : null}
|
|
|
|
|
</CardShell>
|
|
|
|
|
);
|
|
|
|
|
}
|