{
  "name": "MetricList",
  "type": "pattern",
  "description": "Grouped label/value measurements with an uppercase eyebrow per group, monospace figures, and a divider that flips orientation responsively",
  "export": "@gtmi/strix-react/atoms/metric-list",
  "path": "@gtmi/strix-react/atoms/metric-list",
  "figma": {
    "file": "zT3vGm4QlFEI2Jp9nxyFKI",
    "fileName": "Demo components library",
    "nodes": [
      {
        "node": "22:5798",
        "name": ".profile-details",
        "section": "Atoms"
      },
      {
        "node": "22:5805",
        "name": ".result-item",
        "section": "Atoms"
      }
    ],
    "note": "Ports two Atoms-section nodes that share one shape."
  },
  "props": [
    {
      "name": "groups",
      "type": "{ heading?: ReactNode; metrics: { label: ReactNode; value: number | string | null; unit?: string; tooltip?: ReactNode }[] }[]",
      "required": true,
      "description": "Groups, laid out in a row from sm up and stacked below it, divided from each other"
    },
    {
      "name": "emptyValue",
      "type": "string",
      "default": "'--'",
      "description": "Rendered in place of a null value. Zero is a measurement and renders as 0"
    },
    {
      "name": "infoLabel",
      "type": "string",
      "default": "'More information'",
      "description": "Accessible name for the info buttons that reveal a metric's tooltip. A tooltip is not an accessible name, so without this the button is announced as unlabelled"
    }
  ],
  "parts": [
    {
      "name": "metric-list",
      "description": "Root, via data-slot"
    },
    {
      "name": "metric-list-group",
      "description": "One group column"
    },
    {
      "name": "metric-list-heading",
      "description": "The uppercase eyebrow, a <p>"
    },
    {
      "name": "metric-list-row",
      "description": "One label/value row"
    },
    {
      "name": "metric-list-label",
      "description": "Label plus optional info button"
    },
    {
      "name": "metric-list-info",
      "description": "Focusable info button that triggers the tooltip"
    },
    {
      "name": "metric-list-value",
      "description": "The monospace figure"
    },
    {
      "name": "metric-list-divider",
      "description": "Between-group rule, aria-hidden, orientation flips at sm"
    }
  ],
  "ai_tags": ["metrics", "stats", "key-value", "list", "measurements", "dashboard", "pattern"],
  "since": "0.2.0",
  "source": "cr-web-demo apps/cr/app/components/CallMetricsSection.tsx",
  "tokens_used": [
    "--color-text-primary",
    "--color-text-tertiary",
    "--color-text-accent-strong",
    "--color-border-neutral-soft",
    "--color-border-focus",
    "--spacing-gap-100",
    "--spacing-gap-200",
    "--spacing-gap-300",
    "--text-size-body-small",
    "--text-size-eyebrow-small",
    "--text-size-mono-large"
  ],
  "notes": "DOMAIN STRIPPED: the source hardcodes seven named call-centre metrics (interruptionsByYou, totalTurns, tokensPerSecond, network, speechToText, llm, textToSpeech) in two hand-built columns, plus react-i18next. This is data-driven via `groups` and carries no call-centre vocabulary and no i18n; every user-facing string (`emptyValue`, `infoLabel`) is a prop with an English default. `fmt` is ported verbatim as `formatValue`: `null` -> `emptyValue`, otherwise `${value}${unit}` with no separator. Note that `0` is a measurement, not a missing one — the check is `=== null`, not falsiness, and a test pins that. TOOLTIP: composes Strix's portalled `Tooltip` and drops the source's `InfoIcon` entirely — that was a hand-rolled fixed-position div with manual viewport clamping on a non-focusable span, so it was hover-only and invisible to a keyboard and to a screen reader. The trigger here is a real `<button type=\"button\">` with an `aria-label`, so the explanation opens on focus as well as hover, and the icon comes from `icons/info` rather than an inlined SVG. A ReactNode tooltip routes to `Tooltip`'s `customContent`, a string to `text`. NOT `Separator`: the source's divider is a single box that flips axis (`h-px w-full sm:h-auto sm:w-px sm:self-stretch`), and `Separator` picks its line from an `orientation` prop at render time and passes that same value to Base UI, which writes it into `aria-orientation` — a responsive flip needs both orientations' box classes live at once and would leave `aria-orientation` lying at one of the two breakpoints. It is therefore a plain `aria-hidden` box rather than `role=\"separator\"`: the group headings already announce the grouping. TOKEN CHOICE (value ink): the source paints the figure `#3acefa`, which is exactly `--color-highlight` and measures 1.84:1 against the light surface — copying it would have shipped a WCAG 1.4.3 failure the dark-only source never had to face. `--color-text-accent-strong` is 6.98:1 light / 12.12:1 dark. Mutation-tested: swapping it for `text-highlight` makes the contrast test fail with 1.84:1 in light. TYPE ROLES: the source's `text-[11px] font-semibold uppercase tracking-wider` heading becomes `eyebrow-s-semibold`, which means the same thing in tokens but resolves to 14px — the layer has no 11px eyebrow step, and 11px tracked uppercase is below comfortable legibility. Heading ink `--color-text-tertiary` is the nearest token to the source's `#7e869c` at 4.78:1 light / 8.17:1 dark, measured with the harness. The source's `font-mono text-base` value becomes `mono-l-semibold`, the 16px mono role; the layer has no 16px regular-weight mono step, and the extra weight suits the row's payload. Contrast is derived from the component's own cva output rather than named, so changing a token moves the assertion."
}
