import { Suspense, lazy, useMemo } from 'react'; import { cn } from '@/lib/utils'; import { EditedChip, Label } from './chrome'; import { formatNumber, formatSigned, useMediaQuery, usePrefersReducedMotion } from './format'; import type { MetricPoint } from './MetricChart'; export type { MetricPoint } from './MetricChart'; // Lazy, and deliberately not a static import: recharts is ~100 kB gzipped and // exactly one surface on the site uses it. `vite.config.ts` also names it as // its own manual chunk, so this stays out of the entry bundle in both dev and // production builds. const MetricChart = lazy(() => import('./MetricChart')); const CHART_HEIGHT = 168; export interface MetricMoverProps { /** The one number a reader would repeat in a meeting. */ label: string; value: number; digits?: number; unit?: string; /** The dashed rule: what the number was before, or what counts as par. */ baseline?: { value: number; label: string }; series?: MetricPoint[]; /** One sentence on what moved it. Not a caveat — the mechanism. */ caption?: string; edited?: boolean; className?: string; } /** * The headline number, with the line that shows it moving. * * The reference rule is not decoration: a number on its own is a claim, and a * number against the line it used to sit on is evidence. If a demo has no * baseline to draw, it should not be using this component. * * Under `sm` the table IS the view and the chart is not fetched: a 168px line * with four categorical ticks in a 326px column is a picture of a table that * would fit better than the picture. */ export function MetricMover({ label, value, digits = 3, unit, baseline, series, caption, edited = false, className, }: MetricMoverProps) { const reducedMotion = usePrefersReducedMotion(); const wide = useMediaQuery('(min-width: 640px)', true); // The rule is still drawn when the headline IS the baseline — it is the line // the other arms are read against. The delta text is not: "+0.000 vs itself" // is noise dressed up as a measurement. const rawDelta = baseline ? value - baseline.value : null; const delta = rawDelta !== null && Math.abs(rawDelta) > 1e-9 ? rawDelta : null; const points = useMemo(() => series ?? [], [series]); const table = ( {points.map((point) => ( ))}
{label}, one row per recorded arm
Arm Total
{String(point.x)} {formatNumber(point.y, digits)}
); return (
{edited ? : null}

{formatNumber(value, digits)} {unit ? {unit} : null} {delta !== null ? ( 0 ? 'text-positive' : delta < 0 ? 'text-danger' : 'text-muted', )} > {formatSigned(delta, digits)} vs {baseline?.label} ) : null}

{points.length > 1 ? (
{wide ? ( <> {/* The chart is a picture of the table; the table is the accessible version of the picture. Both are the same numbers. */}
Show these points as a table
{table}
) : (
{table}
)}
) : null} {caption ?

{caption}

: null}
); }