import { ReactNode } from 'react'; import { useCountUp } from '../../lib/motion'; import Sparkline from './Sparkline'; import './StatTile.css'; export type Status = 'good' | 'warning' | 'serious' | 'critical'; /* Status is carried by an icon plus a label, never by colour alone — the light-surface status steps sit below 3:1 by design. */ const STATUS_ICON: Record = { good: '●', warning: '▲', serious: '▲', critical: '■', }; interface StatTileProps { label: string; value: number | string | null | undefined; unit?: string; /** Secondary line: a goal, a range, a comparison. */ detail?: ReactNode; status?: Status; statusLabel?: string; /** 0–1; draws a goal meter under the value. */ progress?: number | null; /** Recent history for an inline trend. */ trend?: Array; decimals?: number; } function StatTile({ label, value, unit, detail, status, statusLabel, progress, trend, decimals = 0, }: StatTileProps) { const numeric = typeof value === 'number' ? value : null; const animated = useCountUp(numeric); const display = value == null ? '—' : numeric != null ? (animated ?? numeric).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals, }) : value; return (
{label} {trend && trend.some((v) => v != null) && ( )}
{display} {unit && value != null && {unit}}
{progress != null && (
)} {(detail || status) && (
{status && ( {statusLabel} )} {detail}
)}
); } export default StatTile;