{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cost-estimate-card",
  "title": "CostEstimateCard",
  "description": "Invoice-shaped cost estimate: quantity by rate per metric, allowances shown, with an optional spending-limit meter.",
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/skeleton.json"
  ],
  "files": [
    {
      "path": "src/components/cost-estimate-card/cost-estimate-card.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface CostLine {\n  /** Stable id, e.g. \"compute_unit_seconds\". */\n  id: string;\n  /** Line label, e.g. \"Compute\". */\n  label: string;\n  /** Billable amount after allowances, in `unit`. */\n  quantity: number;\n  /** Total consumed before the allowance, in `unit`. */\n  used?: number;\n  /** Billing unit, e.g. \"CU-hr\" or \"GB-mo\". */\n  unit: string;\n  /** USD per unit. */\n  rate: number;\n  /** quantity x rate, in USD. */\n  cost: number;\n  /** Amount the plan covered, shown as a quiet \"N included\" note. */\n  included?: number;\n}\n\nexport type CostEstimateCardProps = Omit<\n  ComponentProps<\"section\">,\n  \"children\"\n> & {\n  /** Line items in display order. */\n  lines: CostLine[];\n  /** Overrides the summed total, e.g. when the invoice already exists. */\n  total?: number;\n  /**\n   * \"cost\" ranks the biggest line first, which is what the reader came\n   * for. \"given\" keeps the order you passed, e.g. to match an invoice.\n   */\n  order?: \"cost\" | \"given\";\n  /**\n   * Roll lines costing nothing into one muted summary row. They still\n   * appear, because \"we checked and it was zero\" is worth saying, but\n   * they stop competing with the lines that cost money.\n   */\n  collapseZero?: boolean;\n  /** Plan name shown beside the total, e.g. \"scale\". */\n  plan?: string;\n  /** Billing period readout, e.g. \"Feb 1 – Feb 14\". */\n  period?: string;\n  /**\n   * Spending limit in USD. Draws a progress meter and, once the estimate\n   * passes it, says so rather than letting the number pass unremarked.\n   */\n  spendingLimit?: number;\n  /** Right-side header slot, e.g. a plan switch or DateRangePicker. */\n  action?: ReactNode;\n  /** Footer content, e.g. a link to the invoice. */\n  footer?: ReactNode;\n  /**\n   * Says the estimate is a projection, not a bill. Defaults to a plain\n   * note; pass null to drop it when you're rendering a settled invoice.\n   */\n  note?: ReactNode;\n  isLoading?: boolean;\n  error?: string | null;\n};\n\n/* ─────────────────────────────────────────────────────────\n * COST STORYBOARD\n *\n *  rest      the number first, then how it was reached:\n *            quantity x rate per line. A cost with no\n *            arithmetic behind it is a number to distrust\n *  included  allowances are shown, not silently netted —\n *            \"500 GB included\" explains why 620 GB bills\n *            as 120\n *  limit     with a spending limit set, a meter shows the\n *            distance to it and turns to the warning tone\n *            once crossed; a limit you can't see is a\n *            limit you'll hit\n *  loading   the total and every line skeleton in place\n *  note      the estimate names itself an estimate —\n *            metering lags and rounding differs, and the\n *            invoice is the source of truth\n * ───────────────────────────────────────────────────────── */\n\nconst CURRENCY = new Intl.NumberFormat(\"en-US\", {\n  currency: \"USD\",\n  style: \"currency\",\n});\n\nconst PRECISE_CURRENCY = new Intl.NumberFormat(\"en-US\", {\n  currency: \"USD\",\n  maximumFractionDigits: 4,\n  minimumFractionDigits: 2,\n  style: \"currency\",\n});\n\nconst QUANTITY = new Intl.NumberFormat(\"en-US\", {\n  maximumFractionDigits: 2,\n});\n\nconst PERCENT = new Intl.NumberFormat(\"en-US\", {\n  maximumFractionDigits: 1,\n  minimumFractionDigits: 1,\n  style: \"percent\",\n});\n\nconst LineRow = ({ line, share }: { line: CostLine; share: number }) => (\n  <li className=\"flex items-baseline gap-3 py-1.5\">\n    <span className=\"min-w-0 flex-1\">\n      <span className=\"block truncate text-foreground text-sm\">\n        {line.label}\n      </span>\n      <span className=\"block font-mono text-[11px] text-muted-foreground/70 tabular-nums\">\n        {QUANTITY.format(line.quantity)} {line.unit} ×{\" \"}\n        {PRECISE_CURRENCY.format(line.rate)}\n        {/* One template string, not interleaved JSX text and expressions:\n            JSX collapses the spaces around them and the line renders as\n            \"·604 used,500 included\". */}\n        {line.included ? (\n          <span className=\"ml-1.5 text-muted-foreground/60\">\n            {`· ${QUANTITY.format(\n              line.used ?? line.quantity + line.included\n            )} used, ${QUANTITY.format(line.included)} included`}\n          </span>\n        ) : null}\n      </span>\n    </span>\n    <span className=\"font-mono text-foreground text-sm tabular-nums\">\n      {CURRENCY.format(line.cost)}\n    </span>\n    <span className=\"w-12 text-right font-mono text-[11px] text-muted-foreground tabular-nums\">\n      {PERCENT.format(share)}\n    </span>\n  </li>\n);\n\n/** The lines that cost nothing, said once instead of a row each. */\nconst ZeroRow = ({ lines }: { lines: CostLine[] }) => (\n  <li className=\"flex items-baseline gap-3 py-1.5\">\n    <span className=\"min-w-0 flex-1 truncate text-[11px] text-muted-foreground/70\">\n      {lines.length} with no usage ·{\" \"}\n      {lines.map((line) => line.label).join(\", \")}\n    </span>\n    <span className=\"font-mono text-[11px] text-muted-foreground/70 tabular-nums\">\n      {CURRENCY.format(0)}\n    </span>\n    <span className=\"w-12\" />\n  </li>\n);\n\n/**\n * Below this share the fill is too narrow to hold its own label, so the\n * spent figure steps outside the fill instead of being clipped by it.\n */\nconst MIN_INSIDE_SHARE = 0.24;\nconst FULL_METER = 100;\n\nconst SpendMeter = ({ total, limit }: { total: number; limit: number }) => {\n  const share = limit > 0 ? Math.min(total / limit, 1) : 0;\n  const isOver = total > limit;\n  const fitsInside = share >= MIN_INSIDE_SHARE;\n\n  return (\n    // No progressbar role: the bar carries real text stating spent, left,\n    // and the limit, which reads better than a percentage announcement.\n    <div\n      className=\"relative mt-3 h-6 w-full overflow-hidden rounded-full bg-muted\"\n      data-slot=\"cost-estimate-card-meter\"\n    >\n      <div\n        aria-hidden=\"true\"\n        className={cn(\n          \"absolute inset-y-0 left-0 rounded-full transition-[width] duration-500 ease-out motion-reduce:transition-none\",\n          isOver ? \"bg-destructive\" : \"bg-primary\"\n        )}\n        style={{ width: `${share * FULL_METER}%` }}\n      />\n\n      {/* The figures ride the bar rather than sitting under it: the amount\n          spent is the fill, so the number belongs where the color is. */}\n      <div className=\"absolute inset-0 flex items-center justify-between gap-2 px-2.5 font-mono text-[10px] tabular-nums\">\n        <span\n          className={cn(\n            \"whitespace-nowrap transition-colors\",\n            fitsInside\n              ? \"text-primary-foreground dark:text-background\"\n              : \"text-foreground\"\n          )}\n          style={\n            fitsInside\n              ? undefined\n              : { marginInlineStart: `${share * FULL_METER}%` }\n          }\n        >\n          {CURRENCY.format(total)} spent\n        </span>\n        <span\n          className={cn(\n            \"whitespace-nowrap\",\n            isOver ? \"text-destructive-foreground\" : \"text-muted-foreground\"\n          )}\n        >\n          {isOver\n            ? `${CURRENCY.format(total - limit)} over`\n            : `${CURRENCY.format(limit - total)} left of ${CURRENCY.format(limit)}`}\n        </span>\n      </div>\n    </div>\n  );\n};\n\nexport const CostEstimateCard = ({\n  action,\n  className,\n  collapseZero = true,\n  error = null,\n  footer,\n  isLoading = false,\n  lines,\n  order = \"cost\",\n  note = \"estimate · metering lags ~15m, the invoice is the source of truth\",\n  period,\n  plan,\n  spendingLimit,\n  total,\n  ...props\n}: CostEstimateCardProps) => {\n  const sum = total ?? lines.reduce((carry, line) => carry + line.cost, 0);\n\n  // Biggest line first: the reader came to find what is driving the bill,\n  // and metric declaration order has nothing to do with cost.\n  const ranked =\n    order === \"cost\" ? [...lines].toSorted((a, b) => b.cost - a.cost) : lines;\n  const charged = collapseZero\n    ? ranked.filter((line) => line.cost > 0)\n    : ranked;\n  const free = collapseZero ? ranked.filter((line) => line.cost === 0) : [];\n  const shareOf = (cost: number) => (sum > 0 ? cost / sum : 0);\n\n  if (isLoading) {\n    return (\n      <section\n        className={cn(\n          \"rounded-lg border border-border/60 bg-card p-4\",\n          className\n        )}\n        data-slot=\"cost-estimate-card\"\n        {...props}\n      >\n        <Skeleton className=\"h-4 w-28\" />\n        <Skeleton className=\"mt-3 h-8 w-32\" />\n        <div className=\"mt-4 space-y-3\">\n          {lines.map((line) => (\n            <Skeleton className=\"h-8 w-full\" key={line.id} />\n          ))}\n        </div>\n      </section>\n    );\n  }\n\n  return (\n    <section\n      className={cn(\n        \"rounded-lg border border-border/60 bg-card p-4\",\n        className\n      )}\n      data-slot=\"cost-estimate-card\"\n      {...props}\n    >\n      <header className=\"flex items-start justify-between gap-3\">\n        <div>\n          <h3 className=\"font-mono text-muted-foreground text-xs\">\n            estimated cost\n          </h3>\n          <p className=\"mt-1 font-medium font-mono text-3xl text-foreground tabular-nums\">\n            {CURRENCY.format(sum)}\n          </p>\n          {/* The plan sets every rate on this card, so it reads at full\n              strength rather than as a footnote beside the date. */}\n          <p className=\"mt-1 flex items-baseline gap-1.5 font-mono text-[11px] tabular-nums\">\n            {plan ? <span className=\"text-foreground\">{plan} plan</span> : null}\n            {plan && period ? (\n              <span className=\"text-muted-foreground/40\">·</span>\n            ) : null}\n            {period ? (\n              <span className=\"text-muted-foreground/70\">{period}</span>\n            ) : null}\n          </p>\n        </div>\n        {action}\n      </header>\n\n      {spendingLimit === undefined ? null : (\n        <SpendMeter limit={spendingLimit} total={sum} />\n      )}\n\n      {error ? (\n        <p\n          className=\"mt-3 flex items-baseline gap-2 text-sm\"\n          data-slot=\"cost-estimate-card-error\"\n          role=\"alert\"\n        >\n          <span className=\"font-mono text-destructive text-xs\">error</span>\n          <span className=\"text-foreground\">{error}</span>\n        </p>\n      ) : null}\n\n      {/* No total row: the figure is already the headline and the meter's\n          left label. Three copies of one number is not emphasis. */}\n      <ul className=\"mt-4 border-border/50 border-t pt-1\">\n        {charged.map((line) => (\n          <LineRow key={line.id} line={line} share={shareOf(line.cost)} />\n        ))}\n        {free.length > 0 ? <ZeroRow lines={free} /> : null}\n      </ul>\n\n      {note ? (\n        <p\n          className=\"mt-3 text-[10px] text-muted-foreground/70\"\n          data-slot=\"cost-estimate-card-note\"\n        >\n          {note}\n        </p>\n      ) : null}\n      {footer ? <div className=\"mt-3\">{footer}</div> : null}\n    </section>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}