{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "metric-tile",
  "title": "Metric Tile",
  "description": "One headline number with its unit, change and trend.",
  "registryDependencies": [
    "https://sproutboat.com/ikada/r/theme.json",
    "https://sproutboat.com/ikada/r/cn.json",
    "https://sproutboat.com/ikada/r/format.json",
    "https://sproutboat.com/ikada/r/icons.json",
    "https://sproutboat.com/ikada/r/locale.json",
    "https://sproutboat.com/ikada/r/skeleton.json"
  ],
  "files": [
    {
      "path": "registry/ikada/ui/metric-tile.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { cn } from \"@/components/ikada/lib/cn\";\nimport { formatCompact } from \"@/components/ikada/lib/format\";\nimport { useLocale } from \"@/components/ikada/lib/locale\";\nimport { ArrowDownIcon, ArrowUpIcon } from \"@/components/ikada/lib/icons\";\nimport { Skeleton } from \"@/components/ikada/skeleton\";\n\nexport interface MetricDelta {\n  /** \"12%\" or \"0.3 pt\". */\n  value: string;\n  direction: \"up\" | \"down\";\n  /** Whether that direction is good news. Default neutral: a rise in requests is not good or bad. */\n  sentiment?: \"good\" | \"bad\" | \"neutral\";\n}\n\nexport interface MetricTileProps extends Omit<ComponentProps<\"div\">, \"children\"> {\n  /** What the number measures, above it. */\n  label: string;\n  /** Already formatted: \"0.21\". For a plain count prefer `amount`. */\n  value?: string;\n  /** A count, formatted compactly in `locale` (\"1.28M\", \"860K\"). Wins over `value`. */\n  amount?: number;\n  /** Number-formatting locale; defaults to the `LocaleProvider` value, else `en`. */\n  locale?: string;\n  /** \"M\", \"%\", \"ms p50\". */\n  unit?: string;\n  /** Change against the previous period, shown as an arrow and a value. */\n  delta?: MetricDelta;\n  /** A line under the value: \"14.8 req/s average\". */\n  hint?: ReactNode;\n  /** A `<Sparkline />` from `@sproutboat/ikada/charts`, flush to the tile's bottom edge, its line ending in line with the tile's text. */\n  sparkline?: ReactNode;\n  /** Draws a skeleton in place of the value. */\n  loading?: boolean;\n  /** Nothing to show for this range: an em dash and `emptyText`. */\n  noData?: boolean;\n  /** Shown when there is no value. Default \"No data for this range\". */\n  emptyText?: string;\n}\n\nconst sentimentClass = {\n  good: \"bg-brand-soft text-brand-fg\",\n  bad: \"bg-danger-soft text-danger-fg\",\n  neutral: \"bg-neutral-soft text-neutral-fg\",\n} as const;\n\n/**\n * One headline number with its unit, change and trend. The hero figure is proportional at 28px\n * (tabular figures belong in columns); the delta is a soft pill; the sparkline marks the current point.\n */\nexport function MetricTile({\n  label,\n  value,\n  amount,\n  unit,\n  delta,\n  hint,\n  sparkline,\n  loading = false,\n  noData = false,\n  emptyText = \"No data for this range\",\n  locale,\n  className,\n  ref,\n  ...props\n}: MetricTileProps) {\n  const resolved = useLocale(locale);\n  const shown = amount === undefined ? value : formatCompact(amount, { locale: resolved });\n  const Arrow = delta?.direction === \"up\" ? ArrowUpIcon : ArrowDownIcon;\n  return (\n    <div\n      data-slot=\"metric-tile\"\n      ref={ref}\n      role=\"group\"\n      aria-label={label}\n      aria-busy={loading || undefined}\n      className={cn(\"flex min-w-0 flex-col overflow-hidden rounded-xl bg-surface shadow-card\", className)}\n      {...props}\n    >\n      <div className=\"flex flex-col gap-1 px-4 pt-3.5\">\n        <span className=\"text-sb-sm font-medium text-fg-muted [overflow-wrap:anywhere]\">{label}</span>\n        {loading ? (\n          <div className=\"flex flex-col gap-2 py-1.5\">\n            <Skeleton className=\"h-6 w-24\" />\n            <Skeleton className=\"w-32\" />\n            <span className=\"sr-only\">Loading</span>\n          </div>\n        ) : (\n          <>\n            <div className=\"flex flex-wrap items-baseline gap-x-1.5\">\n              <span\n                className={cn(\n                  \"min-w-0 text-sb-metric proportional-nums [overflow-wrap:anywhere]\",\n                  noData && \"text-fg-muted\",\n                )}\n              >\n                {noData ? \"—\" : shown}\n              </span>\n              {unit && !noData ? <span className=\"text-sb-base font-medium text-fg-muted\">{unit}</span> : null}\n              {delta && !noData ? (\n                <span\n                  className={cn(\n                    \"ms-1 inline-flex h-5 items-center gap-0.5 self-center rounded-full px-1.5 text-sb-xs font-semibold tabular-nums\",\n                    sentimentClass[delta.sentiment ?? \"neutral\"],\n                  )}\n                >\n                  <Arrow className=\"size-3.5\" />\n                  <span className=\"sr-only\">{delta.direction === \"up\" ? \"Up\" : \"Down\"} </span>\n                  {delta.value}\n                </span>\n              ) : null}\n            </div>\n            {noData || hint ? (\n              <span className=\"text-sb-sm text-fg-muted [overflow-wrap:anywhere]\">{noData ? emptyText : hint}</span>\n            ) : null}\n          </>\n        )}\n      </div>\n      {/* The slot exists only for tiles that have a sparkline, and keeps its height while loading or empty so the tile doesn't jump. */}\n      {sparkline ? (\n        <div className=\"mt-2 flex h-11 flex-col justify-end px-2.5\">{loading || noData ? null : sparkline}</div>\n      ) : (\n        <div className=\"pb-3.5\" />\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/ikada/metric-tile.tsx"
    }
  ],
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}