{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart",
  "title": "Chart",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Theme-aware chart wrapper for Recharts with token colors, a styled tooltip and legend, a named figure with a screen-reader summary, and keyboard navigation.",
  "dependencies": [
    "recharts@^3"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/chart.tsx",
      "content": "// Ballmac UI: Chart. https://ui.ballmac.com/components/chart\n// Based on shadcn/ui Chart (MIT, Copyright (c) 2023 shadcn) on Recharts (MIT, Copyright (c) 2015-present Recharts Group), adding a named figure with a screen-reader summary, keyboard navigation by default, theme-token series colors, and a tooltip with indicator styles.\n\"use client\";\n\nimport * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\nimport { cn } from \"@/lib/utils\";\n\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\ntype ChartConfig = {\n  [key: string]: {\n    /** Name shown in the tooltip and legend. */\n    label?: React.ReactNode;\n    /** Icon shown before the label in the legend and tooltip. */\n    icon?: React.ComponentType;\n  } & (\n    | { /** A CSS color or token, for example `var(--chart-1)`. */ color?: string; theme?: never }\n    | { color?: never; /** Separate colors per theme. */ theme: Record<keyof typeof THEMES, string> }\n  );\n};\n\ntype ChartContextProps = { config: ChartConfig };\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n  if (!context) throw new Error(\"useChart must be used within a <ChartContainer />\");\n  return context;\n}\n\ntype ChartContainerProps = React.ComponentProps<\"figure\"> & {\n  /** Series definitions keyed by the chart's `dataKey`. Each becomes a `--color-<key>` CSS variable. */\n  config: ChartConfig;\n  /** One Recharts chart (AreaChart, BarChart, LineChart, PieChart, RadarChart…). */\n  children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>[\"children\"];\n  /** Accessible name of the figure. */\n  label: string;\n  /** Plain-language summary of what the chart shows, read by screen readers. */\n  summary?: string;\n  /** Size used before the container is measured, so the server render has a stable box. */\n  initialDimension?: { width: number; height: number };\n};\n\n/** Wraps a Recharts chart: sizes it responsively, exposes series colors as CSS variables and names it for assistive tech. */\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  label,\n  summary,\n  initialDimension = { width: 480, height: 270 },\n  ...props\n}: ChartContainerProps) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, \"\")}`;\n  const summaryId = `${chartId}-summary`;\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <figure\n        data-slot=\"chart\"\n        data-chart={chartId}\n        aria-label={label}\n        aria-describedby={summary ? summaryId : undefined}\n        className={cn(\n          \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke$='ccc']]:stroke-border/60 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke$='fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-pie:focus-visible_.recharts-sector]:stroke-ring [&_.recharts-pie:focus-visible_.recharts-sector]:[stroke-width:2px] [&_.recharts-polar-grid_[stroke$='ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted/60 [&_.recharts-reference-line_[stroke$='ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke$='fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n          className,\n        )}\n        {...props}\n      >\n        {summary && (\n          <figcaption id={summaryId} className=\"sr-only\">\n            {summary}\n          </figcaption>\n        )}\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer initialDimension={initialDimension}>\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </figure>\n    </ChartContext.Provider>\n  );\n}\n\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n  const colorConfig = Object.entries(config).filter(([, c]) => c.theme || c.color);\n  if (!colorConfig.length) return null;\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=\"${id}\"] {\n${colorConfig\n  .map(([key, item]) => {\n    const color = item.theme?.[theme as keyof typeof item.theme] || item.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .filter(Boolean)\n  .join(\"\\n\")}\n}\n`,\n          )\n          .join(\"\\n\"),\n      }}\n    />\n  );\n}\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\ntype PayloadItem = {\n  dataKey?: string | number;\n  name?: string | number;\n  value?: number | string | Array<number | string>;\n  color?: string;\n  fill?: string;\n  stroke?: string;\n  type?: string;\n  payload?: Record<string, unknown> & { fill?: string };\n};\n\nfunction getItemConfig(config: ChartConfig, item: PayloadItem, key: string) {\n  const payload = item.payload;\n  const candidates = [\n    key,\n    typeof item.dataKey === \"string\" ? item.dataKey : undefined,\n    typeof item.name === \"string\" ? item.name : undefined,\n    payload && typeof payload[key] === \"string\" ? (payload[key] as string) : undefined,\n  ];\n  for (const c of candidates) if (c && c in config) return config[c];\n  return undefined;\n}\n\ntype ChartTooltipContentProps = {\n  active?: boolean;\n  payload?: PayloadItem[];\n  label?: React.ReactNode;\n  className?: string;\n  /** Marker style before each value. */\n  indicator?: \"dot\" | \"line\" | \"dashed\";\n  hideLabel?: boolean;\n  hideIndicator?: boolean;\n  /** Key in the data point whose value is used as the tooltip title. */\n  labelKey?: string;\n  /** Key used to look up the series name and color in `config`. */\n  nameKey?: string;\n  labelFormatter?: (label: React.ReactNode, payload: PayloadItem[]) => React.ReactNode;\n  formatter?: (value: number | string, name: string, item: PayloadItem, index: number) => React.ReactNode;\n  labelClassName?: string;\n  color?: string;\n};\n\n/** The tooltip card. Pass it as `<ChartTooltip content={<ChartTooltipContent />} />`. */\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = \"dot\",\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: ChartTooltipContentProps) {\n  const { config } = useChart();\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) return null;\n    const [item] = payload;\n    const key = `${labelKey ?? item.dataKey ?? item.name ?? \"value\"}`;\n    const itemConfig = getItemConfig(config, item, key);\n    const value = !labelKey && typeof label === \"string\" ? (config[label]?.label ?? label) : itemConfig?.label;\n    if (labelFormatter) return <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>;\n    return value ? <div className={cn(\"font-medium\", labelClassName)}>{value}</div> : null;\n  }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n  if (!active || !payload?.length) return null;\n  const nestLabel = payload.length === 1 && indicator !== \"dot\";\n  return (\n    <div\n      className={cn(\n        \"grid min-w-36 items-start gap-1.5 rounded-lg border bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-[0_12px_36px_-10px_rgb(0_0_0/0.25)]\",\n        className,\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== \"none\")\n          .map((item, index) => {\n            const key = `${nameKey ?? item.name ?? item.dataKey ?? \"value\"}`;\n            const itemConfig = getItemConfig(config, item, key);\n            const indicatorColor = color ?? item.payload?.fill ?? item.color;\n            const Icon = itemConfig?.icon;\n            return (\n              <div\n                key={`${item.dataKey ?? index}`}\n                className={cn(\n                  \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n                  indicator === \"dot\" && \"items-center\",\n                )}\n              >\n                {formatter && item.value !== undefined && item.name ? (\n                  formatter(item.value as number | string, String(item.name), item, index)\n                ) : (\n                  <>\n                    {Icon ? (\n                      <Icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)\", {\n                            \"h-2.5 w-2.5\": indicator === \"dot\",\n                            \"w-1\": indicator === \"line\",\n                            \"w-0 border-[1.5px] border-dashed bg-transparent\": indicator === \"dashed\",\n                            \"my-0.5\": nestLabel && indicator === \"dashed\",\n                          })}\n                          style={{ \"--color-bg\": indicatorColor, \"--color-border\": indicatorColor } as React.CSSProperties}\n                        />\n                      )\n                    )}\n                    <div className={cn(\"flex flex-1 justify-between gap-4 leading-none\", nestLabel ? \"items-end\" : \"items-center\")}>\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">{itemConfig?.label ?? item.name}</span>\n                      </div>\n                      {item.value !== undefined && (\n                        <span className=\"font-mono font-medium text-foreground tabular-nums\">\n                          {typeof item.value === \"number\" ? item.value.toLocaleString(\"en-US\") : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\ntype ChartLegendContentProps = React.ComponentProps<\"div\"> & {\n  payload?: Array<{ value?: string | number; dataKey?: string | number; color?: string; type?: string }>;\n  verticalAlign?: \"top\" | \"middle\" | \"bottom\";\n  hideIcon?: boolean;\n  /** Key used to look up the series in `config`. */\n  nameKey?: string;\n};\n\n/** The legend row. Pass it as `<ChartLegend content={<ChartLegendContent />} />`. */\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = \"bottom\",\n  nameKey,\n}: ChartLegendContentProps) {\n  const { config } = useChart();\n  if (!payload?.length) return null;\n  return (\n    <ul\n      className={cn(\"flex items-center justify-center gap-4\", verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\", className)}\n    >\n      {payload\n        .filter((item) => item.type !== \"none\")\n        .map((item) => {\n          const key = `${nameKey ?? item.dataKey ?? item.value ?? \"value\"}`;\n          const itemConfig = getItemConfig(config, item as PayloadItem, key);\n          const Icon = itemConfig?.icon;\n          return (\n            <li\n              key={String(item.value)}\n              className=\"flex items-center gap-1.5 text-muted-foreground [&>svg]:h-3 [&>svg]:w-3\"\n            >\n              {Icon && !hideIcon ? (\n                <Icon />\n              ) : (\n                <span aria-hidden=\"true\" className=\"h-2 w-2 shrink-0 rounded-[2px]\" style={{ backgroundColor: item.color }} />\n              )}\n              {itemConfig?.label ?? item.value}\n            </li>\n          );\n        })}\n    </ul>\n  );\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n  useChart,\n  type ChartConfig,\n  type ChartContainerProps,\n  type ChartTooltipContentProps,\n  type ChartLegendContentProps,\n};\n",
      "type": "registry:ui",
      "target": "@components/ballmac/chart.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "chart",
      "recharts",
      "graph",
      "data"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "Wrap any Recharts chart in ChartContainer with a config of label and color per series. Use ChartTooltipContent and ChartLegendContent. Always pass label and summary.",
      "whenToUse": [
        "Dashboards and reports",
        "Trends, comparisons and proportions"
      ],
      "whenNotToUse": [
        "A tiny inline trend; use sparkline",
        "A single ring value; use progress-ring"
      ],
      "composesWith": [
        "card",
        "stat-card"
      ],
      "a11y": [
        {
          "keys": "Arrow keys",
          "action": "Move between data points (accessibilityLayer)"
        },
        {
          "keys": "Screen readers",
          "action": "Read the figure name and summary"
        }
      ],
      "customization": [
        "config colors via chart tokens",
        "tooltip indicator: dot | line | dashed",
        "legend content",
        "summary text for assistive tech"
      ]
    },
    "source": {
      "name": "shadcn/ui Chart",
      "url": "https://github.com/shadcn-ui/ui",
      "license": "MIT",
      "copyright": "Copyright (c) 2023 shadcn",
      "modified": true
    },
    "examples": [
      "chart-demo",
      "chart-bars",
      "chart-donut"
    ],
    "url": "https://ui.ballmac.com/components/chart"
  },
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}