{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stats-display",
  "type": "registry:block",
  "title": "Stats Display",
  "description": "Display key metrics in compact cards.",
  "dependencies": [
    "zod"
  ],
  "registryDependencies": [
    "card"
  ],
  "files": [
    {
      "path": "components/tool-ui/shared/contract.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/contract.ts",
      "content": "import { z } from \"zod\";\nimport { parseWithSchema, safeParseWithSchema } from \"./parse\";\n\nexport interface ToolUiContract<T> {\n  schema: z.ZodType<T>;\n  parse: (input: unknown) => T;\n  safeParse: (input: unknown) => T | null;\n}\n\nexport function defineToolUiContract<T>(\n  componentName: string,\n  schema: z.ZodType<T>,\n): ToolUiContract<T> {\n  return {\n    schema,\n    parse: (input: unknown) => parseWithSchema(schema, input, componentName),\n    safeParse: (input: unknown) => safeParseWithSchema(schema, input),\n  };\n}\n"
    },
    {
      "path": "components/tool-ui/shared/parse.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/parse.ts",
      "content": "import { z } from \"zod\";\n\nfunction formatZodPath(path: Array<string | number | symbol>): string {\n  if (path.length === 0) return \"root\";\n  return path\n    .map((segment) =>\n      typeof segment === \"number\" ? `[${segment}]` : String(segment),\n    )\n    .join(\".\");\n}\n\n/**\n * Format Zod errors into a compact `path: message` string.\n */\nexport function formatZodError(error: z.ZodError): string {\n  const parts = error.issues.map((issue) => {\n    const path = formatZodPath(issue.path);\n    return `${path}: ${issue.message}`;\n  });\n\n  return Array.from(new Set(parts)).join(\"; \");\n}\n\n/**\n * Parse unknown input and throw a readable error.\n */\nexport function parseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n  name: string,\n): T {\n  const res = schema.safeParse(input);\n  if (!res.success) {\n    throw new Error(`Invalid ${name} payload: ${formatZodError(res.error)}`);\n  }\n  return res.data;\n}\n\n/**\n * Parse unknown input, returning `null` instead of throwing on failure.\n *\n * Use this in assistant-ui `render` functions where `args` stream in\n * incrementally and may be incomplete until the tool call finishes.\n */\nexport function safeParseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n): T | null {\n  const res = schema.safeParse(input);\n  return res.success ? res.data : null;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/schema.ts",
      "content": "import { z } from \"zod\";\nimport type { ReactNode } from \"react\";\n\n/**\n * Tool UI conventions:\n * - Serializable schemas are JSON-safe (no callbacks/ReactNode/`className`).\n * - Schema: `SerializableXSchema`\n * - Parser: `parseSerializableX(input: unknown)` (throws on invalid)\n * - Safe parser: `safeParseSerializableX(input: unknown)` (returns `null` on invalid)\n * - Actions: `LocalActions` for non-receipt actions and `DecisionActions` for consequential actions\n * - Root attrs: `data-tool-ui-id` + `data-slot`\n */\n\n/**\n * Schema for tool UI identity.\n *\n * Every tool UI should have a unique identifier that:\n * - Is stable across re-renders\n * - Is meaningful (not auto-generated)\n * - Is unique within the conversation\n *\n * Format recommendation: `{component-type}-{semantic-identifier}`\n * Examples: \"data-table-expenses-q3\", \"option-list-deploy-target\"\n */\nexport const ToolUIIdSchema = z.string().min(1);\n\nexport type ToolUIId = z.infer<typeof ToolUIIdSchema>;\n\n/**\n * Primary role of a Tool UI surface in a chat context.\n */\nexport const ToolUIRoleSchema = z.enum([\n  \"information\",\n  \"decision\",\n  \"control\",\n  \"state\",\n  \"composite\",\n]);\n\nexport type ToolUIRole = z.infer<typeof ToolUIRoleSchema>;\n\nexport const ToolUIReceiptOutcomeSchema = z.enum([\n  \"success\",\n  \"partial\",\n  \"failed\",\n  \"cancelled\",\n]);\n\nexport type ToolUIReceiptOutcome = z.infer<typeof ToolUIReceiptOutcomeSchema>;\n\n/**\n * Optional receipt metadata: a durable summary of an outcome.\n */\nexport const ToolUIReceiptSchema = z.object({\n  outcome: ToolUIReceiptOutcomeSchema,\n  summary: z.string().min(1),\n  identifiers: z.record(z.string(), z.string()).optional(),\n  at: z.string().datetime(),\n});\n\nexport type ToolUIReceipt = z.infer<typeof ToolUIReceiptSchema>;\n\n/**\n * Base schema for Tool UI payloads (id + optional role/receipt).\n */\nexport const ToolUISurfaceSchema = z.object({\n  id: ToolUIIdSchema,\n  role: ToolUIRoleSchema.optional(),\n  receipt: ToolUIReceiptSchema.optional(),\n});\n\nexport type ToolUISurface = z.infer<typeof ToolUISurfaceSchema>;\n\nexport const ActionSchema = z.object({\n  id: z.string().min(1),\n  label: z.string().min(1),\n  /**\n   * Canonical narration the assistant can use after this action is taken.\n   *\n   * Example: \"I exported the table as CSV.\" / \"I opened the link in a new tab.\"\n   */\n  sentence: z.string().optional(),\n  confirmLabel: z.string().optional(),\n  variant: z\n    .enum([\"default\", \"destructive\", \"secondary\", \"ghost\", \"outline\"])\n    .optional(),\n  icon: z.custom<ReactNode>().optional(),\n  loading: z.boolean().optional(),\n  disabled: z.boolean().optional(),\n  shortcut: z.string().optional(),\n});\n\nexport type Action = z.infer<typeof ActionSchema>;\nexport type LocalAction = Action;\nexport type DecisionAction = Action;\n\nexport const DecisionResultSchema = z.object({\n  kind: z.literal(\"decision\"),\n  version: z.literal(1),\n  decisionId: z.string().min(1),\n  actionId: z.string().min(1),\n  actionLabel: z.string().min(1),\n  at: z.string().datetime(),\n  payload: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport type DecisionResult<\n  TPayload extends Record<string, unknown> = Record<string, unknown>,\n> = Omit<z.infer<typeof DecisionResultSchema>, \"payload\"> & {\n  payload?: TPayload;\n};\n\nexport function createDecisionResult<\n  TPayload extends Record<string, unknown> = Record<string, unknown>,\n>(args: {\n  decisionId: string;\n  action: { id: string; label: string };\n  payload?: TPayload;\n}): DecisionResult<TPayload> {\n  return {\n    kind: \"decision\",\n    version: 1,\n    decisionId: args.decisionId,\n    actionId: args.action.id,\n    actionLabel: args.action.label,\n    at: new Date().toISOString(),\n    payload: args.payload,\n  };\n}\n\nexport const ActionButtonsPropsSchema = z.object({\n  actions: z.array(ActionSchema).min(1),\n  align: z.enum([\"left\", \"center\", \"right\"]).optional(),\n  confirmTimeout: z.number().positive().optional(),\n  className: z.string().optional(),\n});\n\nexport const SerializableActionSchema = ActionSchema.omit({ icon: true });\nexport const SerializableActionsSchema = ActionButtonsPropsSchema.extend({\n  actions: z.array(SerializableActionSchema),\n}).omit({ className: true });\n\nexport interface ActionsConfig {\n  items: Action[];\n  align?: \"left\" | \"center\" | \"right\";\n  confirmTimeout?: number;\n}\n\nexport const SerializableActionsConfigSchema = z.object({\n  items: z.array(SerializableActionSchema).min(1),\n  align: z.enum([\"left\", \"center\", \"right\"]).optional(),\n  confirmTimeout: z.number().positive().optional(),\n});\n\nexport type SerializableActionsConfig = z.infer<\n  typeof SerializableActionsConfigSchema\n>;\n\nexport type SerializableAction = z.infer<typeof SerializableActionSchema>;\n"
    },
    {
      "path": "components/tool-ui/stats-display/_adapter.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/stats-display/_adapter.tsx",
      "content": "/**\n * Adapter: UI and utility re-exports for copy-standalone portability.\n *\n * When copying this component to another project, update these imports\n * to match your project's paths:\n *\n *   cn   → Your Tailwind merge utility (e.g., \"@/lib/utils\", \"~/lib/cn\")\n *   Card → shadcn/ui Card\n */\n\nexport { cn } from \"@/lib/utils\";\nexport {\n  Card,\n  CardHeader,\n  CardTitle,\n  CardDescription,\n  CardContent,\n} from \"@/components/ui/card\";\n"
    },
    {
      "path": "components/tool-ui/stats-display/index.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/stats-display/index.tsx",
      "content": "export { StatsDisplay } from \"./stats-display\";\nexport { Sparkline, type SparklineProps } from \"./sparkline\";\nexport {\n  type SerializableStatsDisplay,\n  type StatsDisplayProps,\n  type StatFormat,\n  type StatDiff,\n  type StatSparkline,\n  type StatItem,\n} from \"./schema\";\n"
    },
    {
      "path": "components/tool-ui/stats-display/README.md",
      "type": "registry:file",
      "target": "components/tool-ui/stats-display/README.md",
      "content": "# Stats Display\n\nImplementation for the \"stats-display\" Tool UI surface.\n\n## Files\n\n- public exports: components/tool-ui/stats-display/index.tsx\n- serializable schema + parse helpers: components/tool-ui/stats-display/schema.ts\n\n## Companion assets\n\n- Docs page: app/docs/stats-display/content.mdx\n- Preset payload: lib/presets/stats-display.ts\n\n## Quick check\n\nRun this after edits:\n\npnpm test\n"
    },
    {
      "path": "components/tool-ui/stats-display/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/stats-display/schema.ts",
      "content": "import { z } from \"zod\";\nimport { defineToolUiContract } from \"../shared/contract\";\nimport { ToolUIIdSchema, ToolUIRoleSchema } from \"../shared/schema\";\n\nconst TextFormatSchema = z.object({\n  kind: z.literal(\"text\"),\n});\n\nconst NumberFormatSchema = z.object({\n  kind: z.literal(\"number\"),\n  decimals: z.number().int().min(0).optional(),\n  compact: z.boolean().optional(),\n});\n\nconst CurrencyFormatSchema = z.object({\n  kind: z.literal(\"currency\"),\n  currency: z.string().min(1),\n  decimals: z.number().int().min(0).optional(),\n});\n\nconst PercentFormatSchema = z.object({\n  kind: z.literal(\"percent\"),\n  decimals: z.number().int().min(0).optional(),\n  basis: z.enum([\"fraction\", \"unit\"]).optional(),\n});\n\nexport const StatFormatSchema = z.discriminatedUnion(\"kind\", [\n  TextFormatSchema,\n  NumberFormatSchema,\n  CurrencyFormatSchema,\n  PercentFormatSchema,\n]);\n\nexport type StatFormat = z.infer<typeof StatFormatSchema>;\n\nexport const StatDiffSchema = z.object({\n  value: z.number(),\n  decimals: z.number().int().min(0).optional(),\n  upIsPositive: z.boolean().optional(),\n  label: z.string().optional(),\n});\n\nexport type StatDiff = z.infer<typeof StatDiffSchema>;\n\nexport const StatSparklineSchema = z.object({\n  data: z.array(z.number()).min(2),\n  color: z.string().optional(),\n});\n\nexport type StatSparkline = z.infer<typeof StatSparklineSchema>;\n\nexport const StatItemSchema = z.object({\n  key: z.string().min(1),\n  label: z.string().min(1),\n  value: z.union([z.string(), z.number()]),\n  format: StatFormatSchema.optional(),\n  diff: StatDiffSchema.optional(),\n  sparkline: StatSparklineSchema.optional(),\n});\n\nexport type StatItem = z.infer<typeof StatItemSchema>;\n\nexport const SerializableStatsDisplaySchema = z.object({\n  id: ToolUIIdSchema,\n  role: ToolUIRoleSchema.optional(),\n  title: z.string().optional(),\n  description: z.string().optional(),\n  stats: z.array(StatItemSchema).min(1),\n});\n\nexport type SerializableStatsDisplay = z.infer<\n  typeof SerializableStatsDisplaySchema\n>;\n\nconst SerializableStatsDisplaySchemaContract = defineToolUiContract(\n  \"StatsDisplay\",\n  SerializableStatsDisplaySchema,\n);\n\nexport const parseSerializableStatsDisplay: (\n  input: unknown,\n) => SerializableStatsDisplay = SerializableStatsDisplaySchemaContract.parse;\n\nexport const safeParseSerializableStatsDisplay: (\n  input: unknown,\n) => SerializableStatsDisplay | null =\n  SerializableStatsDisplaySchemaContract.safeParse;\nexport interface StatsDisplayProps extends SerializableStatsDisplay {\n  className?: string;\n  locale?: string;\n}\n"
    },
    {
      "path": "components/tool-ui/stats-display/sparkline.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/stats-display/sparkline.tsx",
      "content": "\"use client\";\n\nimport type { CSSProperties } from \"react\";\nimport { useId } from \"react\";\nimport { cn } from \"./_adapter\";\n\nexport interface SparklineProps {\n  data: number[];\n  color?: string;\n  width?: number;\n  height?: number;\n  className?: string;\n  style?: CSSProperties;\n  showFill?: boolean;\n  fillOpacity?: number;\n}\n\nexport function Sparkline({\n  data,\n  color = \"currentColor\",\n  width = 64,\n  height = 24,\n  className,\n  style,\n  showFill = false,\n  fillOpacity = 0.09,\n}: SparklineProps) {\n  const gradientId = useId();\n\n  if (data.length < 2) {\n    return null;\n  }\n\n  const minVal = Math.min(...data);\n  const maxVal = Math.max(...data);\n  const range = maxVal - minVal || 1;\n\n  const padding = 0;\n  const usableWidth = width;\n  const usableHeight = height;\n\n  const linePoints = data.map((value, index) => {\n    const x = padding + (index / (data.length - 1)) * usableWidth;\n    const y =\n      padding + usableHeight - ((value - minVal) / range) * usableHeight;\n    return { x, y };\n  });\n\n  const linePointsString = linePoints.map((p) => `${p.x},${p.y}`).join(\" \");\n\n  const areaPointsString = [\n    `${padding},${height}`,\n    ...linePoints.map((p) => `${p.x},${p.y}`),\n    `${width - padding},${height}`,\n  ].join(\" \");\n\n  const animationDelay = style?.animationDelay ?? \"0ms\";\n  const baseAnimationDelay =\n    typeof animationDelay === \"number\" ? `${animationDelay}ms` : animationDelay;\n  const secondaryAnimationDelay = `calc(${baseAnimationDelay} + 100ms)`;\n\n  return (\n    <svg\n      viewBox={`0 0 ${width} ${height}`}\n      aria-hidden=\"true\"\n      className={cn(\"h-full w-full shrink-0\", className)}\n      style={style}\n      preserveAspectRatio=\"none\"\n    >\n      {showFill && (\n        <>\n          <defs>\n            <linearGradient id={gradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              <stop offset=\"0%\" stopColor={color} stopOpacity={fillOpacity} />\n              <stop offset=\"100%\" stopColor={color} stopOpacity={0} />\n            </linearGradient>\n          </defs>\n          <polygon\n            points={areaPointsString}\n            fill={`url(#${gradientId})`}\n            className=\"animate-in fade-in duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] fill-mode-both\"\n            style={{ animationDelay }}\n          />\n        </>\n      )}\n      <polyline\n        points={linePointsString}\n        fill=\"none\"\n        stroke={color}\n        strokeWidth={1}\n        strokeOpacity={0.15}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        vectorEffect=\"non-scaling-stroke\"\n      />\n      <polyline\n        points={linePointsString}\n        fill=\"none\"\n        stroke={color}\n        strokeWidth={0.75}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        vectorEffect=\"non-scaling-stroke\"\n        pathLength={1}\n        strokeDasharray=\"0.36 0.64\"\n        strokeDashoffset={0}\n        strokeOpacity={0.2}\n        className=\"opacity-0 motion-safe:animate-in motion-safe:fade-in motion-safe:duration-700 motion-safe:ease-out motion-safe:fill-mode-both\"\n        style={{ animationDelay: baseAnimationDelay }}\n      />\n      <polyline\n        points={linePointsString}\n        fill=\"none\"\n        stroke={color}\n        strokeWidth={0.75}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        vectorEffect=\"non-scaling-stroke\"\n        pathLength={1}\n        strokeDasharray=\"0.24 0.76\"\n        strokeDashoffset={0}\n        strokeOpacity={0.65}\n        className=\"opacity-0 motion-safe:animate-in motion-safe:fade-in motion-safe:duration-500 motion-safe:ease-out motion-safe:fill-mode-both\"\n        style={{ animationDelay: secondaryAnimationDelay }}\n      />\n    </svg>\n  );\n}\n"
    },
    {
      "path": "components/tool-ui/stats-display/stats-display.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/stats-display/stats-display.tsx",
      "content": "\"use client\";\nimport {\n  cn,\n  Card,\n  CardHeader,\n  CardTitle,\n  CardDescription,\n  CardContent,\n} from \"./_adapter\";\nimport type {\n  StatsDisplayProps,\n  StatItem,\n  StatFormat,\n  StatDiff,\n} from \"./schema\";\nimport { Sparkline } from \"./sparkline\";\n\ninterface FormattedValueProps {\n  value: string | number;\n  format?: StatFormat;\n  locale?: string;\n}\n\nfunction FormattedValue({ value, format, locale }: FormattedValueProps) {\n  if (typeof value === \"string\" || !format) {\n    return <span className=\"font-light tabular-nums\">{String(value)}</span>;\n  }\n\n  switch (format.kind) {\n    case \"number\": {\n      const decimals = format.decimals ?? 0;\n      if (format.compact) {\n        const parts = new Intl.NumberFormat(locale, {\n          minimumFractionDigits: decimals,\n          maximumFractionDigits: decimals,\n          notation: \"compact\",\n        }).formatToParts(value);\n        const fullNumber = new Intl.NumberFormat(locale).format(value);\n        return (\n          <span className=\"font-light tabular-nums\" aria-label={fullNumber}>\n            {parts.map((part, i) =>\n              part.type === \"compact\" ? (\n                <span\n                  key={i}\n                  className=\"ml-0.5 text-[0.65em] opacity-80\"\n                  aria-hidden=\"true\"\n                >\n                  {part.value}\n                </span>\n              ) : (\n                <span key={i}>{part.value}</span>\n              ),\n            )}\n          </span>\n        );\n      }\n      const formatted = new Intl.NumberFormat(locale, {\n        minimumFractionDigits: decimals,\n        maximumFractionDigits: decimals,\n      }).format(value);\n      return <span className=\"font-light tabular-nums\">{formatted}</span>;\n    }\n    case \"currency\": {\n      const currency = format.currency;\n      const decimals = format.decimals ?? 2;\n      const formatted = new Intl.NumberFormat(locale, {\n        style: \"currency\",\n        currency,\n        minimumFractionDigits: decimals,\n        maximumFractionDigits: decimals,\n      }).format(value);\n      const spokenValue = new Intl.NumberFormat(locale, {\n        style: \"currency\",\n        currency,\n        currencyDisplay: \"name\",\n        minimumFractionDigits: decimals,\n        maximumFractionDigits: decimals,\n      }).format(value);\n      return (\n        <span className=\"font-light tabular-nums\" aria-label={spokenValue}>\n          {formatted}\n        </span>\n      );\n    }\n    case \"percent\": {\n      const decimals = format.decimals ?? 2;\n      const basis = format.basis ?? \"fraction\";\n      const numeric = basis === \"fraction\" ? value * 100 : value;\n      const formatted = numeric.toFixed(decimals);\n      return (\n        <span\n          className=\"font-light tabular-nums\"\n          aria-label={`${formatted} percent`}\n        >\n          {formatted}\n          <span className=\"ml-0.5 text-[0.65em] opacity-80\" aria-hidden=\"true\">\n            %\n          </span>\n        </span>\n      );\n    }\n    case \"text\":\n    default:\n      return <span className=\"font-light tabular-nums\">{String(value)}</span>;\n  }\n}\n\ninterface DeltaValueProps {\n  diff: StatDiff;\n}\n\nfunction DeltaValue({ diff }: DeltaValueProps) {\n  const { value, decimals = 1, upIsPositive = true, label } = diff;\n\n  const isPositive = value > 0;\n  const isNegative = value < 0;\n\n  const isGood = upIsPositive ? isPositive : isNegative;\n  const isBad = upIsPositive ? isNegative : isPositive;\n\n  const colorClass = isGood\n    ? \"text-green-600 dark:text-green-400\"\n    : isBad\n      ? \"text-red-600 dark:text-red-500\"\n      : \"text-muted-foreground\";\n\n  const bgClass = isGood\n    ? \"bg-green-500/10 dark:bg-green-600/15\"\n    : isBad\n      ? \"bg-red-500/10 dark:bg-red-500/15\"\n      : \"bg-muted\";\n\n  const formatted = Math.abs(value).toFixed(decimals);\n  const sign = isNegative ? \"−\" : \"+\";\n  const display = `${sign}${formatted}%`;\n\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs  tabular-nums\",\n        colorClass,\n        bgClass,\n      )}\n    >\n      {!upIsPositive && (\n        <span className=\"text-[0.9em]\">{isGood ? \"↓\" : \"↑\"}</span>\n      )}\n      {display}\n      {label && (\n        <span className=\"text-muted-foreground font-normal\">{label}</span>\n      )}\n    </span>\n  );\n}\n\ninterface StatCardProps {\n  stat: StatItem;\n  locale?: string;\n  isSingle?: boolean;\n  index?: number;\n}\n\nfunction StatCard({\n  stat,\n  locale,\n  isSingle = false,\n  index = 0,\n}: StatCardProps) {\n  const sparklineColor = stat.sparkline?.color ?? \"var(--muted-foreground)\";\n  const hasSparkline = Boolean(stat.sparkline);\n  const baseDelay = index * 175;\n\n  return (\n    <div\n      className={cn(\n        \"relative flex min-h-28 flex-col gap-1 px-6\",\n        isSingle ? \"justify-center\" : \"justify-end\",\n      )}\n    >\n      {hasSparkline && (\n        <Sparkline\n          data={stat.sparkline!.data}\n          color={sparklineColor}\n          showFill\n          fillOpacity={0.09}\n          className=\"pointer-events-none absolute inset-x-0 top-2 bottom-2 animate-in fade-in slide-in-from-bottom-12 duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] fill-mode-both\"\n          style={{ animationDelay: `${baseDelay}ms` }}\n        />\n      )}\n      <span\n        className=\"text-muted-foreground relative text-xs font-normal tracking-wider uppercase opacity-90 animate-in fade-in slide-in-from-bottom-1 duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] fill-mode-both\"\n        style={{ animationDelay: `${baseDelay + 75}ms` }}\n      >\n        {stat.label}\n      </span>\n      <div\n        className=\"relative flex items-baseline gap-2 pb-2 animate-in fade-in slide-in-from-bottom-2 duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] fill-mode-both\"\n        style={{ animationDelay: `${baseDelay + 150}ms` }}\n      >\n        <span\n          className={cn(\n            \"font-light tracking-normal\",\n            isSingle ? \"text-5xl\" : \"text-3xl\",\n          )}\n        >\n          <FormattedValue\n            value={stat.value}\n            format={stat.format}\n            locale={locale}\n          />\n        </span>\n        {stat.diff && <DeltaValue diff={stat.diff} />}\n      </div>\n    </div>\n  );\n}\n\nexport function StatsDisplay({\n  id,\n  title,\n  description,\n  stats,\n  className,\n  locale: localeProp,\n}: StatsDisplayProps) {\n  const locale =\n    localeProp ??\n    (typeof navigator !== \"undefined\" ? navigator.language : undefined);\n  const hasHeader = Boolean(title || description);\n  const isSingle = stats.length === 1;\n\n  return (\n    <article\n      data-slot=\"stats-display\"\n      data-tool-ui-id={id}\n      className={cn(\n        \"w-full min-w-80 max-w-xl\",\n        isSingle && \"max-w-sm\",\n        className,\n      )}\n    >\n      <Card className={cn(\"overflow-clip !pb-0 !pt-2\", hasHeader && \"!gap-0\")}>\n        {hasHeader && (\n          <CardHeader className=\"border-b border-border !pt-3 !pb-4\">\n            {title && <CardTitle className=\"text-pretty\">{title}</CardTitle>}\n            {description && (\n              <CardDescription className=\"text-pretty\">\n                {description}\n              </CardDescription>\n            )}\n          </CardHeader>\n        )}\n        <CardContent className=\"@container overflow-hidden p-0\">\n          <div\n            className=\"grid @[440px]:-ml-px @[440px]:-mt-px\"\n            style={{\n              gridTemplateColumns: \"repeat(auto-fit, minmax(220px, 1fr))\",\n            }}\n          >\n            {stats.map((stat, index) => (\n              <div\n                key={stat.key}\n                className={cn(\n                  \"overflow-clip py-3 first:pt-0 @[440px]:py-3 @[440px]:first:pt-3 @[440px]:border-l @[440px]:border-t @[440px]:border-border\",\n                  index > 0 && \"border-border border-t\",\n                )}\n              >\n                <StatCard\n                  stat={stat}\n                  locale={locale}\n                  isSingle={isSingle}\n                  index={index}\n                />\n              </div>\n            ))}\n          </div>\n        </CardContent>\n      </Card>\n    </article>\n  );\n}\n"
    }
  ]
}
