{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart",
  "type": "registry:ui",
  "title": "Chart primitives",
  "description": "Recharts container with series config to CSS variables, tooltip, legend and hidden data table on Wibo tokens.",
  "dependencies": [
    "recharts"
  ],
  "registryDependencies": [
    "@wibo/utils"
  ],
  "files": [
    {
      "path": "src/components/chart.tsx",
      "type": "registry:ui",
      "content": "import { createContext, useContext, useId, useMemo, type ComponentProps, type CSSProperties, type ReactNode } from \"react\";\nimport * as Recharts from \"recharts\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Chart primitives on Recharts, in the shape of shadcn's chart: a container\n * that turns a series config into CSS variables (`--color-<key>`), a tooltip\n * and a legend rendered with Wibo tokens. Series take the six ordered chart\n * hues in fixed order (`--chart-1` to `--chart-6`): the first series is always\n * `chart-1`, never a cycled colour. Text wears text tokens, never the series\n * colour. Marks are thin: 2 px lines, 4 px rounded bar ends, recessive grid.\n */\nexport interface ChartSeriesConfig {\n  label: ReactNode;\n  /** A token colour such as `var(--chart-1)`; defaults to the slot's chart hue. */\n  color?: string;\n}\nexport type ChartConfig = Record<string, ChartSeriesConfig>;\n\nconst ChartContext = createContext<{ config: ChartConfig } | null>(null);\n\nfunction useChart() {\n  const ctx = useContext(ChartContext);\n  if (!ctx) throw new Error(\"useChart must be used inside <ChartContainer>\");\n  return ctx;\n}\n\n/** The chart hue of the n-th series (1-based), as a CSS value. */\nexport function seriesColor(index: number): string {\n  return `var(--chart-${((index - 1) % 6) + 1})`;\n}\n\ninterface ChartContainerProps extends ComponentProps<\"div\"> {\n  config: ChartConfig;\n  /** Accessible name of the figure. */\n  title?: string;\n  description?: string;\n  /** Height of the plot area; the width follows the container. */\n  height?: number;\n  /** The hidden data table (or any fallback) rendered after the plot, outside the responsive container. */\n  table?: ReactNode;\n  children: ComponentProps<typeof Recharts.ResponsiveContainer>[\"children\"];\n}\n\nfunction ChartContainer({ config, title, description, height = 240, table, className, children, ...props }: ChartContainerProps) {\n  const id = useId().replace(/:/g, \"\");\n  const style = useMemo(() => {\n    const vars: Record<string, string> = {};\n    Object.keys(config).forEach((key, i) => {\n      vars[`--color-${key}`] = config[key]?.color ?? seriesColor(i + 1);\n    });\n    return vars as CSSProperties;\n  }, [config]);\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <figure\n        data-slot=\"chart\"\n        data-chart={id}\n        role=\"figure\"\n        aria-label={title}\n        className={cn(\n          \"flex w-full flex-col gap-2 text-xs text-muted-foreground\",\n          \"[&_.recharts-cartesian-grid_line]:stroke-border [&_.recharts-cartesian-axis-line]:stroke-border [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground\",\n          \"[&_.recharts-tooltip-cursor]:stroke-border [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-foreground/4 [&_.recharts-reference-line_line]:stroke-border\",\n          \"[&_.recharts-layer]:outline-none [&_.recharts-surface]:outline-none\",\n          className,\n        )}\n        style={style}\n        {...props}\n      >\n        {description ? <figcaption className=\"sr-only\">{description}</figcaption> : null}\n        <div style={{ height }} className=\"w-full\">\n          <Recharts.ResponsiveContainer width=\"100%\" height=\"100%\">\n            {children}\n          </Recharts.ResponsiveContainer>\n        </div>\n        {table}\n      </figure>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartTooltip = Recharts.Tooltip;\n\ninterface TooltipPayloadItem {\n  name?: string | number;\n  dataKey?: string | number;\n  value?: number | string;\n  color?: string;\n  payload?: Record<string, unknown>;\n}\n\ninterface ChartTooltipContentProps {\n  active?: boolean;\n  payload?: TooltipPayloadItem[];\n  label?: ReactNode;\n  labelFormatter?: (label: ReactNode, payload: TooltipPayloadItem[]) => ReactNode;\n  valueFormatter?: (value: number | string, key: string) => ReactNode;\n  hideLabel?: boolean;\n  className?: string;\n}\n\n/** Tooltip body: label row, then one line per series with its mark, name and value in tabular mono. */\nfunction ChartTooltipContent({ active, payload, label, labelFormatter, valueFormatter, hideLabel, className }: ChartTooltipContentProps) {\n  const { config } = useChart();\n  if (!active || !payload?.length) return null;\n  return (\n    <div className={cn(\"grid min-w-32 gap-1.5 rounded-lg border border-border bg-popover px-2.5 py-2 text-xs text-popover-foreground shadow-overlay\", className)}>\n      {!hideLabel && label !== undefined ? <div className=\"font-medium text-foreground\">{labelFormatter ? labelFormatter(label, payload) : label}</div> : null}\n      <div className=\"grid gap-1\">\n        {payload.map((item) => {\n          const key = String(item.dataKey ?? item.name ?? \"\");\n          const series = config[key];\n          return (\n            <div key={key} className=\"flex items-center gap-2\">\n              <span aria-hidden=\"true\" className=\"size-2 shrink-0 rounded-[2px]\" style={{ background: series?.color ?? `var(--color-${key})` }} />\n              <span className=\"flex-1 text-muted-foreground\">{series?.label ?? item.name}</span>\n              <span className=\"font-mono tabular-nums text-foreground\">{item.value !== undefined ? (valueFormatter ? valueFormatter(item.value, key) : item.value) : \"\"}</span>\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = Recharts.Legend;\n\ninterface ChartLegendContentProps {\n  payload?: { value?: string; dataKey?: string | number; color?: string }[];\n  className?: string;\n}\n\n/** Legend: a mark and the series label per entry, always present for two or more series. */\nfunction ChartLegendContent({ payload, className }: ChartLegendContentProps) {\n  const { config } = useChart();\n  if (!payload?.length) return null;\n  return (\n    <ul className={cn(\"flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-2\", className)}>\n      {payload.map((item) => {\n        const key = String(item.dataKey ?? item.value ?? \"\");\n        const series = config[key];\n        return (\n          <li key={key} className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n            <span aria-hidden=\"true\" className=\"size-2 rounded-[2px]\" style={{ background: series?.color ?? `var(--color-${key})` }} />\n            {series?.label ?? item.value}\n          </li>\n        );\n      })}\n    </ul>\n  );\n}\n\n/** Visually hidden table of the plotted data: the chart's values for screen readers and copy. */\nfunction ChartDataTable<T extends Record<string, unknown>>({ data, xKey, config, caption, format }: { data: T[]; xKey: keyof T & string; config: ChartConfig; caption?: string; format?: (value: unknown, key: string) => ReactNode }) {\n  const keys = Object.keys(config);\n  return (\n    <table className=\"sr-only\">\n      {caption ? <caption>{caption}</caption> : null}\n      <thead>\n        <tr>\n          <th scope=\"col\">{xKey}</th>\n          {keys.map((k) => (\n            <th key={k} scope=\"col\">\n              {config[k]?.label}\n            </th>\n          ))}\n        </tr>\n      </thead>\n      <tbody>\n        {data.map((row, i) => (\n          <tr key={i}>\n            <th scope=\"row\">{String(row[xKey])}</th>\n            {keys.map((k) => (\n              <td key={k}>{format ? format(row[k], k) : String(row[k] ?? \"\")}</td>\n            ))}\n          </tr>\n        ))}\n      </tbody>\n    </table>\n  );\n}\n\nexport { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartDataTable, Recharts, useChart };\nexport type { ChartContainerProps, ChartTooltipContentProps };\n"
    }
  ]
}
