{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "primitives",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "A signed change, with polarity and a redundant non-color cue.",
  "files": [
    {
      "content": "import * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { isZero } from \"@/lib/ledger/format-money\";\nimport type { MoneyValue, PercentChange, Polarity } from \"@/lib/ledger/money.types\";\nimport { Money } from \"@/components/ledger/money\";\nimport { Percent } from \"@/components/ledger/percent\";\nimport { Tooltip } from \"@/components/ledger/tooltip\";\n\nexport interface DeltaProps extends Omit<React.ComponentPropsWithoutRef<\"span\">, \"children\"> {\n  /** Never omit the comparison basis. A bare \"+5.3%\" answers nothing. */\n  basis?: string;\n  /**\n   * The basis, carried on hover instead of inline. For dense surfaces — a card\n   * meta row, a KPI tile — where the visible basis is what turns a two-item\n   * row into a wrapping sentence. Ignored when `basis` is also given, because\n   * saying it twice is worse than either.\n   */\n  basisTooltip?: string;\n  percent?: PercentChange;\n  /** Percent-only delta, for metrics with no monetary magnitude. */\n  percentOnly?: boolean;\n  /** Required in practice: up is not assumed good. */\n  polarity?: Polarity;\n  showArrow?: boolean;\n  size?: \"xs\" | \"sm\" | \"md\";\n  value: MoneyValue | null;\n}\n\n/**\n * A signed change, never coloured without a redundant cue.\n *\n * The sign glyph slot is reserved so a column of deltas aligns even when\n * some are unchanged, and the arrow is a second non-chromatic signal so\n * the whole thing survives greyscale.\n */\nexport const Delta = React.forwardRef<HTMLSpanElement, DeltaProps>(\n  (\n    {\n      className,\n      value,\n      percent,\n      polarity = \"standard\",\n      showArrow = true,\n      basis,\n      basisTooltip,\n      size = \"sm\",\n      percentOnly = false,\n      ...props\n    },\n    ref,\n  ) => {\n    const magnitude = percentOnly ? (percent?.value ?? 0) : numberOf(value);\n    const unchanged = value == null ? percent == null || percent.value === 0 : isZero(value);\n\n    const hint = basis ? undefined : basisTooltip;\n\n    if (unchanged) {\n      return (\n        <Tooltip content={hint}>\n          <span\n            className={cn(\"numeric relative inline-flex items-baseline gap-1\", sizeClass(size), className)}\n            ref={ref}\n            style={{ color: \"var(--neutral-value)\" }}\n            {...props}\n          >\n            <span aria-hidden=\"true\">—</span>\n            <span className=\"sr-only select-none\">{hint ? `unchanged, ${hint}` : \"unchanged\"}</span>\n            {basis ? <BasisText>{basis}</BasisText> : null}\n          </span>\n        </Tooltip>\n      );\n    }\n\n    const up = magnitude > 0;\n    const toneClass =\n      polarity === \"neutral\"\n        ? \"text-text-secondary\"\n        : (polarity === \"standard\") === up\n          ? \"text-positive\"\n          : \"text-negative\";\n\n    return (\n      <Tooltip content={hint}>\n        <span\n          aria-description={hint}\n          className={cn(\n            \"numeric inline-flex flex-wrap items-baseline gap-1\",\n            sizeClass(size),\n            toneClass,\n            className,\n          )}\n          ref={ref}\n          {...props}\n        >\n          {/* The signed run stays one unbreakable unit; only a trailing basis is\n            allowed to fall to the next line. Previously `nowrap` sat on the\n            whole span, so a basis clause forced the entire delta to overflow. */}\n          <span className=\"inline-flex items-baseline gap-1 whitespace-nowrap\">\n            {showArrow ? (\n              <span aria-hidden=\"true\" className=\"text-[0.85em] leading-none\">\n                {up ? \"↑\" : \"↓\"}\n              </span>\n            ) : null}\n\n            {percentOnly ? null : (\n              <Money\n                className=\"font-inherit\"\n                size={size === \"md\" ? \"md\" : size === \"sm\" ? \"sm\" : \"xs\"}\n                tone=\"inherit\"\n                value={value}\n              />\n            )}\n\n            {percent ? (\n              <span className={percentOnly ? undefined : \"text-[0.95em]\"}>\n                {percentOnly ? null : <span aria-hidden=\"true\">(</span>}\n                <Percent className=\"text-inherit\" size={size === \"md\" ? \"md\" : \"xs\"} value={percent} />\n                {percentOnly ? null : <span aria-hidden=\"true\">)</span>}\n              </span>\n            ) : null}\n          </span>\n\n          {basis ? <BasisText>{basis}</BasisText> : null}\n        </span>\n      </Tooltip>\n    );\n  },\n);\nDelta.displayName = \"Delta\";\n\nfunction BasisText({ children }: { children: React.ReactNode }) {\n  return <span className=\"font-normal text-caption text-text-tertiary\">{children}</span>;\n}\n\nfunction sizeClass(size: \"xs\" | \"sm\" | \"md\") {\n  return size === \"md\" ? \"text-figure-md font-medium\" : size === \"sm\" ? \"text-figure-sm\" : \"text-figure-xs\";\n}\n\nfunction numberOf(value: MoneyValue | null): number {\n  if (!value) return 0;\n  return typeof value.amount === \"bigint\" ? Number(value.amount) : value.amount;\n}\n",
      "path": "components/ledger/delta.tsx",
      "target": "components/ledger/delta.tsx",
      "type": "registry:ui"
    }
  ],
  "name": "delta",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn",
    "@lopifi/format-money",
    "@lopifi/money",
    "@lopifi/money.types",
    "@lopifi/percent",
    "@lopifi/tooltip"
  ],
  "title": "Delta",
  "type": "registry:ui"
}
