Single-column narrative One obvious reading order and nothing to configure. Each section states its finding in a sentence above the chart, so a reader who does not read charts still leaves with the answer. npm i recharts Tokens this needs: --card, --card-foreground, --muted, --muted-foreground, --border, --chart-1 ──────────────────────────────────────────────────────────────────────── // NarrativeReport.tsx ──────────────────────────────────────────────────────────────────────── import { useState } from "react"; import { Line, LineChart, YAxis } from "recharts"; import { cn } from "@/lib/utils"; export type Metric = { key: string; label: string; /** The headline figure, already formatted: "42.1k", "38%", "2m 4s". */ value: string; /** One point per day in the window. The chart plots whichever is selected. */ series: number[]; }; export type Breakdown = { title: string; rows: { label: string; value: number }[] }; export type Detail = { columns: [string, string]; rows: [string, string][] }; type Props = { metrics: Metric[]; breakdowns: Breakdown[]; detail: Detail; /** Which metric the chart opens on. Defaults to the first. */ defaultMetric?: string; onSelect?: (key: string) => void; }; /** * One column in the order the argument runs: headline, shape, decomposition, * detail. The metric row is the only control on the page, and selecting a * metric is the same gesture as reading it. */ export function NarrativeReport({ metrics, breakdowns, detail, defaultMetric, onSelect }: Props) { const [selected, setSelected] = useState(defaultMetric ?? metrics[0]?.key); const metric = metrics.find((m) => m.key === selected) ?? metrics[0]; const pick = (key: string) => { setSelected(key); onSelect?.(key); }; return (
{b.title}
| {detail.columns[0]} | {detail.columns[1]} |
|---|---|
| {a} | {b} |