{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stat-card",
  "type": "registry:ui",
  "title": "Stat Card",
  "description": "A single labeled metric — the building block of dashboards, scorecards, and \"key results\" rows. Renders: label / value / optional delta / optional icon. The `tone` variant pairs the delta accent with one of four semantic colors (default / success / warning…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "data",
    "pattern"
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/skeleton.json",
    "https://ds.interlace.tools/r/typography.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/patterns/stat-card.tsx",
      "target": "components/ui/patterns/stat-card.tsx",
      "type": "registry:ui",
      "content": "import * as React from 'react';\n\n// @interlace/stat-card v1.2.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/stat-card\n// What changed since: https://ds.interlace.tools/c/stat-card#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — StatCard\n *\n * A single labeled metric — the building block of dashboards, scorecards,\n * and \"key results\" rows. Renders: label / value / optional delta / optional\n * icon. The `tone` variant pairs the delta accent with one of four semantic\n * colors (default / success / warning / danger).\n *\n * Numbers are rendered with `font-variant-numeric: tabular-nums` (inherited\n * from preflight when wrapped in a `[data-tabular-nums]` container or table)\n * — set `data-tabular-nums` on the StatCard root to opt in.\n *\n * ## Anatomy\n *\n *   StatCard                         (article — data-min-viewport=320)\n *     ├─ <div>                       (header row — label + icon)\n *     │   ├─ Typography ui-sm        (label)\n *     │   └─ {icon}                  (lucide icon, optional)\n *     ├─ Typography h2               (value — tabular numerals)\n *     ├─ {delta}                     (optional — \"+12.3% · 30d\" — tone-tinted)\n *     └─ {footnote}                  (optional smaller meta — \"since launch\")\n *\n * ## MIN_VIEWPORT — 320\n *\n * Dashboards must work on phones; this is the smallest widget on the canvas.\n *\n * ## Contrast — delta text on `--background` (measured in-browser)\n *\n * | tone    | Light  | Dark    | Floor              |\n * | ------- | ------ | ------- | ------------------ |\n * | default | 9.41:1 | 11.32:1 | 7:1 (SC 1.4.6 AAA) |\n * | success | 7.68:1 | 10.30:1 | 7:1 (SC 1.4.6 AAA) |\n * | warning | 9.07:1 |  9.22:1 | 7:1 (SC 1.4.6 AAA) |\n * | danger  | 8.31:1 | 10.43:1 | 7:1 (SC 1.4.6 AAA) |\n *\n * These are semantic tokens, not palette. The previous `text-emerald-600` /\n * `text-rose-600` measured 3.65:1 and 4.52:1 — below even AA — and failed the\n * enforced `color-contrast` + `color-contrast-enhanced` gate.\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends native el + VariantProps | `React.ComponentProps<'article'> & VariantProps<...>`       |\n * | R6   | data-slot + data-tone            | `data-slot=\"stat-card\"` + `data-tone`                       |\n * | R7   | cn + ...rest                     | `cn(statCardVariants(...), className)` + `{...props}`       |\n * | R8   | Enums for tone                   | `tone = default|success|warning|danger`                     |\n * | R10  | Composition seams                | `icon` / `delta` / `footnote` props                         |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}` + exported const |\n * | R19  | Tokens only                      | tone classes map to semantic tokens                         |\n * | R20  | AAA contrast                     | table above — every tone measured in both schemes           |\n * | R25  | Server component                 | No hooks → no `'use client'`                                |\n */\n\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\nimport { Skeleton } from '@/components/ui/skeleton';\nimport { Typography } from '@/components/ui/typography';\n\nexport const MIN_VIEWPORT = 320 as const;\n\nconst statCardVariants = cva(\n  'border-border bg-card text-card-foreground rounded-lg border p-5 flex flex-col gap-2',\n  {\n    variants: {\n      tone: {\n        default: '',\n        success: 'border-success/40',\n        warning: 'border-warning/40',\n        danger: 'border-destructive/40',\n      },\n    },\n    defaultVariants: {\n      tone: 'default',\n    },\n  },\n);\n\n// Semantic tokens, NOT raw palette. `text-emerald-600` / `-rose-600` scored\n// 3.65:1 and 4.52:1 on white — failing `color-contrast` (AA, 4.5:1) and\n// `color-contrast-enhanced` (AAA, 7:1), both of which this DS enforces. The\n// `--success` / `--warning` / `--destructive` ladder in interlace-theme.css is\n// already tuned to clear AAA in BOTH schemes, so it needs no `dark:` variant:\n// the token itself re-resolves under `.dark`. This is what R19 (\"tone classes\n// map to semantic tokens\") meant all along.\nconst deltaToneClass: Record<NonNullable<VariantProps<typeof statCardVariants>['tone']>, string> = {\n  default: 'text-muted-foreground',\n  success: 'text-success',\n  warning: 'text-warning',\n  danger: 'text-destructive',\n};\n\ntype StatCardProps = React.ComponentProps<'article'> &\n  VariantProps<typeof statCardVariants> & {\n    label?: React.ReactNode;\n    value?: React.ReactNode;\n    /** Optional change/trend line — e.g. \"+12.3% · 30d\". Inherits the tone color. */\n    delta?: React.ReactNode;\n    /** Optional secondary line under the delta — e.g. \"since launch\". */\n    footnote?: React.ReactNode;\n    /** Optional lucide icon — pinned to the top-right of the card. */\n    icon?: React.ReactNode;\n    /** When true, render a `<Skeleton variant=\"stat-card\" />` placeholder. */\n    loading?: boolean;\n  };\n\nexport function StatCard({\n  className,\n  tone = 'default',\n  label,\n  value,\n  delta,\n  footnote,\n  icon,\n  loading,\n  ...props\n}: StatCardProps) {\n  if (loading) {\n    return (\n      <Skeleton\n        variant=\"stat-card\"\n        data-slot=\"stat-card\"\n        className={className}\n      />\n    );\n  }\n  return (\n    <article\n      data-slot=\"stat-card\"\n      data-tone={tone ?? undefined}\n      data-min-viewport={String(MIN_VIEWPORT)}\n      className={cn(statCardVariants({ tone, className }))}\n      {...props}\n    >\n      <div className=\"flex items-start justify-between gap-2\">\n        <Typography variant=\"ui-sm\" tone=\"muted\">\n          {label}\n        </Typography>\n        {icon ? (\n          <span className=\"text-muted-foreground\" aria-hidden>\n            {icon}\n          </span>\n        ) : null}\n      </div>\n      <Typography as=\"div\" variant=\"h2\" data-tabular-nums>\n        {value}\n      </Typography>\n      {delta ? (\n        <div className={cn('text-sm font-medium', deltaToneClass[tone ?? 'default'])}>\n          {delta}\n        </div>\n      ) : null}\n      {footnote ? (\n        <Typography variant=\"ui-sm\" tone=\"muted\">\n          {footnote}\n        </Typography>\n      ) : null}\n    </article>\n  );\n}\n\nexport { statCardVariants };\n"
    }
  ],
  "meta": {
    "tier": "pattern",
    "client": false,
    "minViewport": 320,
    "loading": true,
    "version": "1.2.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/stat-card\n\nInstalled to `components/ui/patterns/stat-card.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/patterns/stat-card';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/stat-card\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
