{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "cards",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Metric + delta + optional sparkline.",
  "files": [
    {
      "content": "import * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport type { MoneyValue, PercentChange, Polarity } from \"@/lib/ledger/money.types\";\nimport { Card } from \"@/components/ledger/card\";\nimport { Delta } from \"@/components/ledger/delta\";\nimport { Figure } from \"@/components/ledger/figure\";\nimport { Sparkline } from \"@/components/ledger/sparkline\";\n\nexport interface KpiCardProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /** Always shown. Never a bare \"+5.3%\". */\n  basis: string;\n  data?: number[];\n  delta?: MoneyValue | null;\n  footnote?: string;\n  label: string;\n  percent?: PercentChange;\n  /** Required, no default. Forcing the decision is the point. */\n  polarity: Polarity;\n  value: MoneyValue | null;\n}\n\n/**\n * The dashboard atom. The display family is forbidden here — these figures\n * align across a `KpiRow`, and only the figure face holds a column.\n *\n * Baselines align across the row via `auto-rows-fr` plus a fixed label row,\n * or two cards with different label lengths put their figures at different\n * heights — the most visible alignment failure on any dashboard.\n */\nexport const KpiCard = React.forwardRef<HTMLDivElement, KpiCardProps>(\n  ({ className, label, value, delta, percent, polarity, basis, data, footnote, ...props }, ref) => (\n    <Card className={cn(\"flex h-full flex-col\", className)} ref={ref} {...props}>\n      <span className=\"line-clamp-2 min-h-8 text-label font-medium text-text-secondary\">{label}</span>\n      <div className=\"mt-2\">\n        {/* family is not offered: the parent forces Inter. */}\n        <Figure size=\"lg\" value={value} />\n      </div>\n      {/* The basis gets its own line rather than trailing the delta. Inline, it\n          wrapped on some cards in a row and not others, which put the sparklines\n          at different heights — the alignment failure this component exists to\n          avoid. On its own line it costs one row on every card equally. */}\n      <div className=\"mt-2\">\n        <Delta percent={percent} polarity={polarity} size=\"xs\" value={delta ?? null} />\n        <p className=\"mt-0.5 truncate text-caption text-text-tertiary\">{basis}</p>\n      </div>\n      {data ? (\n        <div className=\"mt-auto pt-4\">\n          <Sparkline data={data} height={24} polarity={polarity} />\n        </div>\n      ) : null}\n      {footnote ? <p className=\"mt-2 text-caption text-text-tertiary\">{footnote}</p> : null}\n    </Card>\n  ),\n);\nKpiCard.displayName = \"KpiCard\";\n\nexport interface KpiRowProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  columns?: 2 | 3 | 4;\n}\n\n/** 2–4 cards on a shared figure baseline. Equal-height rows, one line of CSS. */\nexport const KpiRow = React.forwardRef<HTMLDivElement, KpiRowProps>(\n  ({ className, columns = 4, ...props }, ref) => (\n    <div\n      className={cn(\n        \"grid auto-rows-fr gap-4\",\n        columns === 2 && \"grid-cols-1 sm:grid-cols-2\",\n        columns === 3 && \"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\",\n        columns === 4 && \"grid-cols-1 sm:grid-cols-2 xl:grid-cols-4\",\n        className,\n      )}\n      ref={ref}\n      {...props}\n    />\n  ),\n);\nKpiRow.displayName = \"KpiRow\";\n\nexport interface StatCardProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  label: string;\n  value: MoneyValue | null;\n}\n\n/** The minimal unit: label + value, 16px inset. No delta, no media. */\nexport const StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(\n  ({ className, label, value, ...props }, ref) => (\n    <Card className={className} inset=\"compact\" ref={ref} {...props}>\n      <span className=\"block truncate text-caption text-text-secondary\">{label}</span>\n      <Figure className=\"mt-1\" size=\"md\" value={value} />\n    </Card>\n  ),\n);\nStatCard.displayName = \"StatCard\";\n",
      "path": "components/ledger/kpi-card.tsx",
      "target": "components/ledger/kpi-card.tsx",
      "type": "registry:component"
    }
  ],
  "meta": {
    "license": "MIT"
  },
  "name": "kpi-card",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/card",
    "@lopifi/cn",
    "@lopifi/delta",
    "@lopifi/figure",
    "@lopifi/money.types",
    "@lopifi/sparkline"
  ],
  "title": "KpiCard",
  "type": "registry:component"
}
