KPI tile
Composition rather than a component. A tile is shadcn's Card plus the three pieces this library already specifies—the delta with its window, the freshness of the data, and a status the viewer can read at a glance.
npx shadcn@latest add card
Tokens this needs: --foreground, --muted, --muted-foreground, --border, --status-nominal, --chart-1, --direction-up
The status, chart, scale, state and direction names are an extension,
not a rename. shadcn has --destructive and five --chart-* and nothing
else in this territory.
────────────────────────────────────────────────────────────────────────
// KpiTile.tsx
────────────────────────────────────────────────────────────────────────
import { Card } from "@/components/ui/card";
import { Delta } from "../delta-indicator/Delta";
import { Sparkline } from "../sparkline/Sparkline";
import { freshness, FRESHNESS_COLOR } from "../freshness-indicator/freshness";
import type { Polarity } from "../red-green-direction/direction";
/**
* A tile is a composition, not a primitive.
*
* shadcn gives you the Card. What it cannot give you is the rule that a number
* without context is a decoration: the viewer needs to know which way it moved,
* against what, and how old it is. So `delta` and `lastArrival` are required
* props rather than optional garnish, and a tile that cannot supply them should
* be a plain figure instead of pretending to be a KPI.
*/
export function KpiTile({
label,
value,
delta,
window,
polarity = "higher-is-better",
lastArrival,
expectedEveryMs,
now,
trend,
}: {
label: string;
value: string;
delta: number;
window: string;
polarity?: Polarity;
lastArrival: Date;
expectedEveryMs: number;
/** The clock to age against. Defaults to now; pass one to render on a server. */
now?: Date;
/** The shape that got here: the third context signal, beside the delta. */
trend?: number[];
}) {
const { state } = freshness(lastArrival, expectedEveryMs, now);
return (
{label} {value}