{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "usage-card",
  "title": "UsageCard",
  "description": "Single Neon consumption metric on the MetricCard shell: derives label, format, unit, total, sparkline, and delta from raw per-project samples.",
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/metric-card.json"
  ],
  "files": [
    {
      "path": "src/components/usage-card/usage-card.tsx",
      "content": "import type {\n  MetricCardProps,\n  MetricFormat,\n  MetricTrendPoint,\n} from \"@/components/metric-card/metric-card\";\nimport { MetricCard } from \"@/components/metric-card/metric-card\";\n\n/**\n * The Neon per-project consumption metrics UsageCard understands. Each maps to\n * a field on the consumption-history timeframe returned by\n * `getConsumptionHistoryPerProject`.\n */\nexport type UsageMetric =\n  | \"compute\"\n  | \"active-time\"\n  | \"storage\"\n  | \"written-data\";\n\n/** One consumption sample, in the metric's native unit (seconds or bytes). */\nexport interface UsagePoint {\n  /** Axis label for the sparkline, e.g. a short date or hour. */\n  label: string;\n  /** Native value: seconds for time metrics, bytes for data metrics. */\n  value: number;\n}\n\ninterface UsageMetricConfig {\n  label: string;\n  format: MetricFormat;\n  unit?: string;\n  /**\n   * Cumulative metrics (compute, writes) sum across the window; level metrics\n   * (storage) read the latest sample.\n   */\n  aggregate: \"sum\" | \"last\";\n  /** Native unit → displayed value (seconds → hours, bytes stay bytes). */\n  toDisplay: (native: number) => number;\n}\n\nconst SECONDS_PER_HOUR = 3600;\nconst secondsToHours = (seconds: number) => seconds / SECONDS_PER_HOUR;\nconst identity = (value: number) => value;\n\nconst USAGE_METRICS: Record<UsageMetric, UsageMetricConfig> = {\n  \"active-time\": {\n    aggregate: \"sum\",\n    format: \"number\",\n    label: \"Active time\",\n    toDisplay: secondsToHours,\n    unit: \"hrs\",\n  },\n  compute: {\n    aggregate: \"sum\",\n    format: \"number\",\n    label: \"Compute\",\n    toDisplay: secondsToHours,\n    unit: \"hrs\",\n  },\n  storage: {\n    aggregate: \"last\",\n    format: \"bytes\",\n    label: \"Storage\",\n    toDisplay: identity,\n  },\n  \"written-data\": {\n    aggregate: \"sum\",\n    format: \"bytes\",\n    label: \"Data written\",\n    toDisplay: identity,\n  },\n};\n\nconst PERCENT = 100;\n\n/** Aggregate the window in native units per the metric's rule. */\nconst aggregateNative = (data: UsagePoint[], aggregate: \"sum\" | \"last\") => {\n  if (aggregate === \"last\") {\n    return data.at(-1)?.value ?? 0;\n  }\n\n  return data.reduce((sum, point) => sum + point.value, 0);\n};\n\nexport type UsageCardProps = Omit<\n  MetricCardProps,\n  \"value\" | \"format\" | \"unit\" | \"trend\" | \"label\" | \"delta\"\n> & {\n  /** Which consumption metric this card reads. */\n  metric: UsageMetric;\n  /** The window's samples, newest last, in native units. */\n  data: UsagePoint[];\n  /** Previous-window total (native units); when set, drives the signed delta. */\n  previousTotal?: number;\n  /** Overrides the metric's default label. */\n  label?: string;\n  /** Appended to the label as quiet context, e.g. \"14d\". */\n  windowLabel?: string;\n};\n\n/**\n * A single Neon consumption metric on the MetricCard shell. Hand it the raw\n * per-project samples in their native units and the metric kind; it derives the\n * label, formatting, unit, total, sparkline, and (with `previousTotal`) the\n * signed delta. Presentational: data comes from a server component or the Data\n * API — see example.tsx.\n */\nexport const UsageCard = ({\n  metric,\n  data,\n  previousTotal,\n  label,\n  windowLabel,\n  comparisonLabel,\n  ...props\n}: UsageCardProps) => {\n  const config = USAGE_METRICS[metric];\n  const currentNative = aggregateNative(data, config.aggregate);\n\n  const trend: MetricTrendPoint[] | undefined =\n    data.length > 0\n      ? data.map((point) => ({\n          label: point.label,\n          value: config.toDisplay(point.value),\n        }))\n      : undefined;\n\n  const hasDelta =\n    previousTotal !== undefined && previousTotal !== 0 && data.length > 0;\n  const delta = hasDelta\n    ? Math.round(((currentNative - previousTotal) / previousTotal) * PERCENT)\n    : undefined;\n\n  const fullLabel = windowLabel\n    ? `${label ?? config.label} · ${windowLabel}`\n    : (label ?? config.label);\n\n  return (\n    <MetricCard\n      comparisonLabel={comparisonLabel}\n      data-slot=\"usage-card\"\n      delta={delta}\n      format={config.format}\n      label={fullLabel}\n      trend={trend}\n      unit={config.unit}\n      value={config.toDisplay(currentNative)}\n      {...props}\n    />\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}