{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "primitives",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "A large display numeral with automatic size step-down.",
  "files": [
    {
      "content": "import * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { formatMoneyParts, moneyAriaLabel } from \"@/lib/ledger/format-money\";\nimport type { FigureSize, MoneyValue } from \"@/lib/ledger/money.types\";\n\nexport interface FigureProps extends Omit<React.ComponentPropsWithoutRef<\"div\">, \"children\"> {\n  colorBySign?: boolean;\n  /**\n   * A figure may leave the figure face for Inter at display size only when it\n   * never aligns with another figure. A parent that stacks figures (KpiRow, a\n   * table) forces `\"sans\"` rather than trusting the caller.\n   */\n  family?: \"sans\" | \"display\";\n  locale?: string;\n  redacted?: boolean;\n  size?: FigureSize;\n  value: MoneyValue | null;\n}\n\nconst STEPS: FigureSize[] = [\"xs\", \"sm\", \"md\", \"lg\", \"hero\"];\n\nconst CLASSES: Record<FigureSize, string> = {\n  hero: \"text-figure-hero\",\n  lg: \"text-figure-lg\",\n  md: \"text-figure-md\",\n  sm: \"text-figure-sm\",\n  xs: \"text-figure-xs\",\n};\n\n/**\n * A number never truncates. A truncated figure is not degraded, it is wrong.\n *\n * The step-down is driven by character count so the value always lands on a\n * type-scale token, rather than `clamp()` landing it between two.\n */\nexport const Figure = React.forwardRef<HTMLDivElement, FigureProps>(\n  (\n    {\n      className,\n      value,\n      size = \"hero\",\n      family = \"sans\",\n      redacted = false,\n      colorBySign = false,\n      locale,\n      ...props\n    },\n    ref,\n  ) => {\n    if (value == null) {\n      return (\n        <div\n          className={cn(\"numeric relative text-text-tertiary\", CLASSES[size], className)}\n          ref={ref}\n          {...props}\n        >\n          <span aria-hidden=\"true\">n/a</span>\n          <span className=\"sr-only select-none\">not available</span>\n        </div>\n      );\n    }\n\n    const parts = formatMoneyParts(value, { locale });\n    const characters = parts.symbol.length + parts.integer.length + parts.fraction.length;\n\n    // Step down one token per threshold crossed, never below `md`.\n    const index = STEPS.indexOf(size);\n    let resolved = size;\n    if (size === \"hero\" && characters > 15) resolved = \"md\";\n    else if (size === \"hero\" && characters > 11) resolved = \"lg\";\n    else if (size === \"lg\" && characters > 14) resolved = \"md\";\n    void index;\n\n    const large = resolved === \"hero\" || resolved === \"lg\";\n    const negative = parts.negative;\n\n    return (\n      <div\n        className={cn(\n          \"numeric relative flex items-baseline font-medium leading-none\",\n          CLASSES[resolved],\n          family === \"display\" && \"font-display font-normal\",\n          colorBySign && negative ? \"text-negative\" : \"text-text-primary\",\n          className,\n        )}\n        ref={ref}\n        {...props}\n      >\n        {redacted ? (\n          <span aria-hidden=\"true\" className=\"text-text-tertiary\">\n            ••••••\n          </span>\n        ) : (\n          <>\n            {negative ? <span aria-hidden=\"true\">−</span> : null}\n            {parts.symbol ? (\n              <span\n                aria-hidden=\"true\"\n                className={cn(\n                  \"font-normal text-text-tertiary\",\n                  /* Display sets in Inter, which needs the air a mono cell\n                     already carries. */\n                  family === \"display\" ? \"mr-[4px]\" : \"mr-[var(--numeric-symbol-gap)]\",\n                )}\n              >\n                {parts.symbol}\n              </span>\n            ) : null}\n            <span aria-hidden=\"true\">{parts.integer}</span>\n            {parts.fraction ? (\n              <span aria-hidden=\"true\" className={large ? \"text-text-tertiary\" : undefined}>\n                {parts.fraction}\n              </span>\n            ) : null}\n            {parts.symbolSuffix ? (\n              <span aria-hidden=\"true\" className=\"ml-1 font-normal text-text-tertiary\">\n                {parts.symbolSuffix}\n              </span>\n            ) : null}\n          </>\n        )}\n        {/* Spoken text, not an aria-label (prohibited on a role-less element).\n            A redacted figure says so — it never reads the amount it hides. */}\n        <span className=\"sr-only select-none\">{redacted ? \"hidden\" : moneyAriaLabel(value, { locale })}</span>\n      </div>\n    );\n  },\n);\nFigure.displayName = \"Figure\";\n",
      "path": "components/ledger/figure.tsx",
      "target": "components/ledger/figure.tsx",
      "type": "registry:ui"
    }
  ],
  "name": "figure",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn",
    "@lopifi/format-money",
    "@lopifi/money.types"
  ],
  "title": "Figure",
  "type": "registry:ui"
}
