{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stat",
  "type": "registry:ui",
  "title": "Stat",
  "description": "A single number with its label and change. Direction is shown by icon, sign and screen reader text, never by color alone.",
  "dependencies": [
    "tailwind-variants@^1.0.0"
  ],
  "registryDependencies": [
    "https://junco.madebytobi.com/r/icons.json",
    "https://junco.madebytobi.com/r/junco-theme.json",
    "https://junco.madebytobi.com/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/junco/ui/stat.tsx",
      "type": "registry:ui",
      "content": "import * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { cn } from \"@/lib/utils\";\nimport { MinusIcon, TrendingDownIcon, TrendingUpIcon } from \"@/lib/junco/icons\";\n\nexport const statVariants = tv({\n  slots: {\n    root: \"grid min-w-0 content-start gap-1\",\n    label: \"truncate text-sm text-muted-foreground\",\n    value: \"text-2xl leading-8 font-semibold tracking-tight tabular-nums text-foreground\",\n    footer: \"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs\",\n    delta: \"inline-flex items-center gap-1 font-medium tabular-nums [&_svg]:size-3.5\",\n    deltaLabel: \"text-muted-foreground\",\n    chart: \"mt-2 h-10 w-full text-primary\",\n  },\n  variants: {\n    trend: {\n      up: { delta: \"text-success-text\" },\n      down: { delta: \"text-destructive-text\" },\n      flat: { delta: \"text-muted-foreground\" },\n    },\n    size: {\n      sm: { value: \"text-xl leading-7\" },\n      md: {},\n      lg: { value: \"text-3xl leading-9\" },\n    },\n  },\n  defaultVariants: { trend: \"flat\", size: \"md\" },\n});\n\nexport type StatTrend = \"up\" | \"down\" | \"flat\";\n\nexport interface StatProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\">,\n    Pick<VariantProps<typeof statVariants>, \"size\"> {\n  label: React.ReactNode;\n  value: React.ReactNode;\n  /** Change vs the comparison period, as a number (12.5 means +12.5%) or preformatted text. */\n  delta?: number | string;\n  /** Direction of the change. Inferred from a numeric delta when omitted. */\n  trend?: StatTrend;\n  /**\n   * When a rise is bad (costs, error rate, churn), set this so \"up\" reads as the warning color.\n   * The icon and sign still show the real direction.\n   */\n  isInverse?: boolean;\n  /** Text after the delta, e.g. \"vs last month\". */\n  deltaLabel?: React.ReactNode;\n  /** A small chart (sparkline) under the numbers. Colored with currentColor (primary by default). */\n  chart?: React.ReactNode;\n  classNames?: Partial<Record<\"root\" | \"label\" | \"value\" | \"footer\" | \"delta\" | \"deltaLabel\" | \"chart\", string>>;\n}\n\nfunction formatDelta(delta: number | string): string {\n  if (typeof delta === \"string\") return delta;\n  const sign = delta > 0 ? \"+\" : delta < 0 ? \"−\" : \"\";\n  return `${sign}${Math.abs(delta).toLocaleString(undefined, { maximumFractionDigits: 1 })}%`;\n}\n\nconst trendWords: Record<StatTrend, string> = { up: \"Up\", down: \"Down\", flat: \"No change\" };\n\nconst StatGroupContext = React.createContext(false);\n\n/**\n * A single number with its label and change. Direction is shown by icon, sign and screen reader\n * text, never by color alone.\n */\nexport const Stat = React.forwardRef<HTMLElement, StatProps>(\n  ({ className, classNames, size, label, value, delta, trend, isInverse = false, deltaLabel, chart, ...props }, ref) => {\n    const direction: StatTrend =\n      trend ?? (typeof delta === \"number\" ? (delta > 0 ? \"up\" : delta < 0 ? \"down\" : \"flat\") : \"flat\");\n    const tone: StatTrend = isInverse && direction !== \"flat\" ? (direction === \"up\" ? \"down\" : \"up\") : direction;\n    const styles = statVariants({ trend: tone, size });\n    const Icon = direction === \"up\" ? TrendingUpIcon : direction === \"down\" ? TrendingDownIcon : MinusIcon;\n    // dt/dd need a <dl>: inside a StatGroup the group is the list, alone the stat is its own list.\n    const Root = (React.useContext(StatGroupContext) ? \"div\" : \"dl\") as \"div\";\n\n    return (\n      <Root\n        ref={ref as React.Ref<HTMLDivElement>}\n        data-slot=\"stat\"\n        className={styles.root({ className: cn(classNames?.root, className) })}\n        {...props}\n      >\n        <dt className={styles.label({ className: classNames?.label })}>{label}</dt>\n        <dd className={styles.value({ className: classNames?.value })}>{value}</dd>\n        {delta !== undefined && (\n          <dd className={styles.footer({ className: classNames?.footer })}>\n            <span data-trend={direction} className={styles.delta({ className: classNames?.delta })}>\n              <Icon />\n              <span className=\"sr-only\">{trendWords[direction]} </span>\n              {formatDelta(delta)}\n            </span>\n            {deltaLabel && <span className={styles.deltaLabel({ className: classNames?.deltaLabel })}>{deltaLabel}</span>}\n          </dd>\n        )}\n        {chart && (\n          <dd aria-hidden className={styles.chart({ className: classNames?.chart })}>\n            {chart}\n          </dd>\n        )}\n      </Root>\n    );\n  },\n);\nStat.displayName = \"Stat\";\n\n/**\n * Wraps one or more Stats in a description list so label/value pairs read correctly.\n *\n *   <StatGroup><Stat label=\"MRR\" value=\"$48.2k\" delta={4.1} /></StatGroup>\n */\nexport const StatGroup = React.forwardRef<HTMLDListElement, React.HTMLAttributes<HTMLDListElement>>(\n  ({ className, ...props }, ref) => (\n    <StatGroupContext.Provider value={true}>\n      <dl\n        ref={ref}\n        data-slot=\"stat-group\"\n        className={cn(\"grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-6\", className)}\n        {...props}\n      />\n    </StatGroupContext.Provider>\n  ),\n);\nStatGroup.displayName = \"StatGroup\";\n\nexport interface SparklineProps extends React.SVGProps<SVGSVGElement> {\n  data: number[];\n  /** Fill under the line. Default true. */\n  isFilled?: boolean;\n}\n\n/** A tiny dependency-free line chart for Stat's `chart` slot. Uses currentColor. */\nexport function Sparkline({ data, isFilled = true, className, ...props }: SparklineProps) {\n  // useId can contain characters that break url(#id) references\n  const id = `spark-${React.useId().replace(/[^\\w-]/g, \"\")}`;\n  if (data.length < 2) return null;\n  const min = Math.min(...data);\n  const max = Math.max(...data);\n  const range = max - min || 1;\n  const points = data.map((d, i) => [(i / (data.length - 1)) * 100, 28 - ((d - min) / range) * 26] as const);\n  const line = points.map(([x, y], i) => `${i ? \"L\" : \"M\"}${x.toFixed(2)} ${y.toFixed(2)}`).join(\" \");\n  return (\n    <svg viewBox=\"0 0 100 30\" preserveAspectRatio=\"none\" className={cn(\"size-full overflow-visible\", className)} {...props}>\n      {isFilled && (\n        <>\n          <defs>\n            <linearGradient id={id} x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">\n              <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity={0.18} />\n              <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity={0} />\n            </linearGradient>\n          </defs>\n          <path d={`${line} L100 30 L0 30 Z`} fill={`url(#${id})`} />\n        </>\n      )}\n      <path d={line} fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} vectorEffect=\"non-scaling-stroke\" strokeLinejoin=\"round\" />\n    </svg>\n  );\n}\n"
    }
  ],
  "docs": "Docs and examples: https://junco.madebytobi.com/docs/components/stat",
  "meta": {
    "hash": "b389cf6aa57cc6b3",
    "source": "packages/react/src/components/stat.tsx",
    "exports": [
      "statVariants",
      "StatTrend",
      "StatProps",
      "Stat",
      "StatGroup",
      "SparklineProps",
      "Sparkline"
    ],
    "props": {
      "Stat": {
        "extends": "Omit<React.HTMLAttributes<HTMLDivElement>, \"children\">, Pick<VariantProps<typeof statVariants>, \"size\">",
        "props": [
          {
            "name": "label",
            "optional": false,
            "type": "React.ReactNode"
          },
          {
            "name": "value",
            "optional": false,
            "type": "React.ReactNode"
          },
          {
            "name": "delta",
            "optional": true,
            "type": "number | string",
            "description": "Change vs the comparison period, as a number (12.5 means +12.5%) or preformatted text."
          },
          {
            "name": "trend",
            "optional": true,
            "type": "StatTrend",
            "description": "Direction of the change. Inferred from a numeric delta when omitted."
          },
          {
            "name": "isInverse",
            "optional": true,
            "type": "boolean",
            "description": "When a rise is bad (costs, error rate, churn), set this so \"up\" reads as the warning color. The icon and sign still show the real direction."
          },
          {
            "name": "deltaLabel",
            "optional": true,
            "type": "React.ReactNode",
            "description": "Text after the delta, e.g. \"vs last month\"."
          },
          {
            "name": "chart",
            "optional": true,
            "type": "React.ReactNode",
            "description": "A small chart (sparkline) under the numbers. Colored with currentColor (primary by default)."
          },
          {
            "name": "classNames",
            "optional": true,
            "type": "Partial<Record<\"root\" | \"label\" | \"value\" | \"footer\" | \"delta\" | \"deltaLabel\" | \"chart\""
          }
        ]
      },
      "Sparkline": {
        "extends": "React.SVGProps<SVGSVGElement>",
        "props": [
          {
            "name": "data",
            "optional": false,
            "type": "number[]"
          },
          {
            "name": "isFilled",
            "optional": true,
            "type": "boolean",
            "description": "Fill under the line. Default true."
          }
        ]
      }
    },
    "variants": {
      "variants": {
        "trend": [
          "up",
          "down",
          "flat"
        ],
        "size": [
          "sm",
          "md",
          "lg"
        ]
      },
      "defaultVariants": {
        "trend": "flat",
        "size": "md"
      }
    },
    "usage": "<StatGroup><Stat label=\"MRR\" value=\"$48.2k\" delta={4.1} /></StatGroup>",
    "docsUrl": "https://junco.madebytobi.com/docs/components/stat"
  }
}
