{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-value-readout",
  "type": "registry:ui",
  "title": "Value Readout",
  "description": "A caption over a big tabular-monospace number, with its unit beside it and an optional qualifier underneath — the REDUCTION / −14.0 dB element every meter-bearing face grows, made once. Tabular figures so a live value does not reflow as its digits change; the unit as a separate muted element so the eye reads the magnitude first; and, crucially, a missing value prints an em dash rather than a zero, because a meter with no source showing 0.0 dB claims a measurement it never received. A real zero still prints as zero — that negative case is pinned. The note line is where \"predicted\", \"reference pattern\" or \"no tap from this host\" goes, and an accent recolours the value alone, never a placeholder.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/value-readout.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/value-readout\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\n/**\n * ValueReadout — a caption over a big number, with its unit and an optional\n * note underneath.\n *\n * The \"REDUCTION / −14.0 dB\" element every meter-bearing face grows, made once.\n * Three things it insists on, because they are what makes a readout trustworthy\n * at a glance:\n *\n *   - **The value is monospace and tabular.** A number that reflows as its\n *     digits change is unreadable while it moves, and a live readout moves\n *     constantly. `font-variant-numeric: tabular-nums` fixes each glyph's\n *     advance so −9.9 and −14.0 occupy the same width.\n *   - **The unit is not part of the number.** It sits beside the value in muted\n *     type at a smaller size, so the eye reads the magnitude first. It is also\n *     a separate element, which means a face can leave it out without the\n *     value's typography changing.\n *   - **A missing value is a dash, not a zero.** Pass `null`/`undefined` (or\n *     let the host pass a non-finite number) and the readout prints an em dash.\n *     A meter with no source showing \"0.0 dB\" claims a measurement it never\n *     received; this is the same \"absent is not zero\" rule the hosted-face\n *     feeds follow.\n *\n * `note` is the line for the qualifier that keeps the number honest —\n * \"predicted\", \"reference pattern\", \"no tap from this host\". It is styled as\n * secondary text and never competes with the value.\n *\n * Presentational and SSR-safe: inline styles, no DOM access, no state. Colours\n * resolve through `token()`; `accent` recolours the value alone (a budget\n * exceeded, a lane in warning) without touching the label or note.\n */\n\nimport type { CSSProperties, ReactNode } from \"react\"\nimport { token } from \"maruaudio/ui/shared/theme\"\n\nexport type ValueReadoutSize = \"sm\" | \"md\" | \"lg\"\nexport type ValueReadoutAlign = \"start\" | \"center\" | \"end\"\n\nexport interface ValueReadoutProps {\n  /** Caption above the value. Short, uppercase by convention. */\n  label: ReactNode\n  /**\n   * The value. A string is printed verbatim (already formatted by the host, so\n   * the readout never imposes a rounding policy); a number is printed as-is;\n   * null / undefined / a non-finite number prints an em dash rather than a\n   * fabricated zero.\n   */\n  value: ReactNode\n  /** Unit, beside the value in muted type. */\n  unit?: ReactNode\n  /** Secondary line under the value — the qualifier, not more precision. */\n  note?: ReactNode\n  /** Recolour the value (and unit) — a budget exceeded, a warning state. */\n  accent?: string\n  /** Text alignment of the whole block. Defaults to \"start\". */\n  align?: ValueReadoutAlign\n  /** Type scale. Defaults to \"md\". */\n  size?: ValueReadoutSize\n  className?: string\n  style?: CSSProperties\n}\n\n/** Em dash: what a readout prints when it has no value to print. */\nexport const VALUE_READOUT_PLACEHOLDER = \"—\"\n\n/**\n * Type sizes per scale, in px: [label, value, unit, note].\n *\n * A table rather than arithmetic because these are three deliberate sizes a\n * face picks from, not a continuum — and because the value/unit ratio is the\n * part that has to stay right at every size.\n */\nconst SIZES: Record<ValueReadoutSize, {\n  label: number\n  value: number\n  unit: number\n  note: number\n}> = {\n  sm: { label: 9, value: 16, unit: 10, note: 9 },\n  md: { label: 10, value: 22, unit: 12, note: 10 },\n  lg: { label: 11, value: 32, unit: 14, note: 11 },\n}\n\n/**\n * What the value element should print.\n *\n * Pure and exported so the \"absent is not zero\" rule can be asserted in Node\n * rather than inferred from a screenshot: anything the host cannot vouch for\n * becomes the placeholder, and only a real value is printed.\n */\nexport function formatReadoutValue(value: unknown): unknown {\n  if (value === null || value === undefined) return VALUE_READOUT_PLACEHOLDER\n  if (typeof value === \"number\" && !Number.isFinite(value)) {\n    return VALUE_READOUT_PLACEHOLDER\n  }\n  if (typeof value === \"string\" && value.trim() === \"\") {\n    return VALUE_READOUT_PLACEHOLDER\n  }\n  return value\n}\n\n/** True when a readout is showing the placeholder rather than a measurement. */\nexport function isReadoutEmpty(value: unknown): boolean {\n  return formatReadoutValue(value) === VALUE_READOUT_PLACEHOLDER\n}\n\nexport function ValueReadout({\n  label,\n  value,\n  unit,\n  note,\n  accent,\n  align = \"start\",\n  size = \"md\",\n  className,\n  style,\n}: ValueReadoutProps) {\n  const type = SIZES[size] ?? SIZES.md\n  const shown = formatReadoutValue(value)\n  const empty = shown === VALUE_READOUT_PLACEHOLDER\n  // A placeholder never takes the accent — an em dash lit up like a live value\n  // is exactly the confusion this component exists to prevent.\n  const valueColor = empty ? token.textMuted : (accent ?? token.text)\n\n  return (\n    <div\n      data-value-readout=\"\"\n      data-empty={empty ? \"\" : undefined}\n      className={className}\n      style={{\n        display: \"flex\",\n        flexDirection: \"column\",\n        alignItems:\n          align === \"center\"\n            ? \"center\"\n            : align === \"end\"\n              ? \"flex-end\"\n              : \"flex-start\",\n        gap: 2,\n        minWidth: 0,\n        fontFamily: token.font,\n        textAlign: align === \"center\" ? \"center\" : align === \"end\" ? \"right\" : \"left\",\n        ...style,\n      }}\n    >\n      <span\n        data-value-readout-label=\"\"\n        style={{\n          color: token.textMuted,\n          fontSize: type.label,\n          fontWeight: 600,\n          letterSpacing: \"0.14em\",\n          lineHeight: 1,\n          textTransform: \"uppercase\",\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        {label}\n      </span>\n\n      <span\n        style={{\n          display: \"flex\",\n          alignItems: \"baseline\",\n          gap: 4,\n          minWidth: 0,\n        }}\n      >\n        <span\n          data-value-readout-value=\"\"\n          style={{\n            color: valueColor,\n            fontFamily: token.fontMono,\n            fontSize: type.value,\n            fontWeight: 600,\n            // Tabular figures: the number must not reflow as its digits change.\n            fontVariantNumeric: \"tabular-nums\",\n            lineHeight: 1,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {shown as ReactNode}\n        </span>\n        {unit !== undefined && unit !== null ? (\n          <span\n            data-value-readout-unit=\"\"\n            style={{\n              color: empty ? token.textMuted : (accent ?? token.textMuted),\n              fontFamily: token.fontMono,\n              fontSize: type.unit,\n              fontWeight: 500,\n              lineHeight: 1,\n              opacity: empty ? 0.6 : 0.85,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {unit}\n          </span>\n        ) : null}\n      </span>\n\n      {note !== undefined && note !== null ? (\n        <span\n          data-value-readout-note=\"\"\n          style={{\n            color: token.textMuted,\n            fontSize: type.note,\n            fontWeight: 500,\n            letterSpacing: \"0.04em\",\n            lineHeight: 1.2,\n          }}\n        >\n          {note}\n        </span>\n      ) : null}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/value-readout.tsx"
    }
  ]
}
