{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart",
  "title": "Chart",
  "description": "Recharts wrapper with a themed container, tooltip and legend driven by a chart config.",
  "dependencies": [
    "recharts@^3.8.0"
  ],
  "registryDependencies": [
    "@pestoui/style",
    "@pestoui/utils"
  ],
  "files": [
    {
      "path": "pestoui/ui/chart.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RechartsPrimitive from \"recharts\"\nimport type { TooltipValueType } from \"recharts\"\n\nimport { cn } from \"@/lib/utils\"\nimport { chartIdFrom, chartStyleCss } from \"@/components/ui/chart-style\"\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const\ntype TooltipNameType = number | string\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode\n    icon?: React.ComponentType\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n>\n\ntype ChartContextProps = {\n  config: ChartConfig\n}\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null)\n\nfunction useChart() {\n  const context = React.useContext(ChartContext)\n\n  if (!context) {\n    throw new Error(\"useChart must be used within a <ChartContainer />\")\n  }\n\n  return context\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  initialDimension = INITIAL_DIMENSION,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  config: ChartConfig\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >[\"children\"]\n  initialDimension?: {\n    width: number\n    height: number\n  }\n}) {\n  const uniqueId = React.useId()\n  const chartId = `chart-${id ?? chartIdFrom(uniqueId)}`\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\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/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.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        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer\n          initialDimension={initialDimension}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  )\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const css = chartStyleCss(id, config, THEMES)\n\n  if (!css) {\n    return null\n  }\n\n  return (\n    <style\n      // oxlint-disable-next-line react/no-danger -- every value in it passed chart-style.ts\n      dangerouslySetInnerHTML={{ __html: css }}\n    />\n  )\n}\n\nconst ChartTooltip = RechartsPrimitive.Tooltip\n\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}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<\"div\"> & {\n    hideLabel?: boolean\n    hideIndicator?: boolean\n    indicator?: \"line\" | \"dot\" | \"dashed\"\n    nameKey?: string\n    labelKey?: string\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<\n      TooltipValueType,\n      TooltipNameType\n    >,\n    \"accessibilityLayer\"\n  >) {\n  const { config } = useChart()\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null\n    }\n\n    const [item] = payload\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? \"value\"}`\n    const itemConfig = getPayloadConfigFromPayload(config, item, key)\n    const value =\n      !labelKey && typeof label === \"string\"\n        ? (config[label]?.label ?? label)\n        : itemConfig?.label\n\n    if (labelFormatter) {\n      return (\n        <div className={cn(\"font-medium\", labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      )\n    }\n\n    if (!value) {\n      return null\n    }\n\n    return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey,\n  ])\n\n  if (!active || !payload?.length) {\n    return null\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== \"dot\"\n\n  /* The chart marks animate themselves — that is the charting library's job and\n     it drives them from JavaScript, so the tokens here cannot reach them. The\n     tooltip surface is the one piece this file actually owns, and it is worth\n     owning: it materialises when the pointer enters the plot and is torn down\n     when it leaves, which is a genuine arrival rather than decoration. While\n     the pointer travels between data points the surface stays mounted and only\n     its contents change, so the entrance plays once per visit instead of once\n     per point. There is no exit — the surface is unmounted the instant the\n     pointer leaves, before any CSS could run on it. */\n  return (\n    <div\n      className={cn(\n        \"grid min-w-36 items-start gap-1.5 rounded-[12px] bg-popover px-3 py-2 text-xs shadow-float ring-1 ring-foreground/[0.06] animate-in fade-in-0 zoom-in-97 blur-in-[3px] duration-spring-swap ease-spring-swap\",\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 = getPayloadConfigFromPayload(config, item, key)\n            const indicatorColor = color ?? item.payload?.fill ?? item.color\n\n            return (\n              <div\n                key={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, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            \"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)\",\n                            {\n                              \"h-2.5 w-2.5\": indicator === \"dot\",\n                              \"w-1\": indicator === \"line\",\n                              \"w-0 border-[1.5px] border-dashed bg-transparent\":\n                                indicator === \"dashed\",\n                              \"my-0.5\": nestLabel && indicator === \"dashed\",\n                            }\n                          )}\n                          style={\n                            {\n                              \"--color-bg\": indicatorColor,\n                              \"--color-border\": indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        \"flex flex-1 justify-between leading-none\",\n                        nestLabel ? \"items-end\" : \"items-center\"\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"font-mono font-medium text-foreground tabular-nums\">\n                          {typeof item.value === \"number\"\n                            ? item.value.toLocaleString()\n                            : 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\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = \"bottom\",\n  nameKey,\n}: React.ComponentProps<\"div\"> & {\n  hideIcon?: boolean\n  nameKey?: string\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart()\n\n  if (!payload?.length) {\n    return null\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center justify-center gap-4\",\n        verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== \"none\")\n        .map((item, index) => {\n          const key = `${nameKey ?? item.dataKey ?? \"value\"}`\n          const itemConfig = getPayloadConfigFromPayload(config, item, key)\n\n          return (\n            <div\n              key={index}\n              className={cn(\n                \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\"\n              )}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          )\n        })}\n    </div>\n  )\n}\n\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== \"object\" || payload === null) {\n    return undefined\n  }\n\n  const payloadPayload =\n    \"payload\" in payload &&\n    typeof payload.payload === \"object\" &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined\n\n  let configLabelKey: string = key\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === \"string\"\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key]\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "pestoui/ui/chart-style.ts",
      "content": "/* ChartStyle (chart.tsx) writes one CSS custom property per series into a\n   <style> element, and React does not escape what goes into one. So every value\n   that reaches that stylesheet is checked here first. Ids and series keys must\n   be plain identifiers. A colour must be a var(--token) reference, a hex value,\n   or an rgb, hsl or oklch-family function of numbers. Anything else is dropped,\n   never escaped: a rejected colour behaves exactly like a series with no colour,\n   and nothing a caller supplies can end the element early with `</style>`.\n\n   A .ts file on purpose: the barrels and the ./ui/* export map collect .tsx\n   files only, so this stays private to the package, and node --test can load it\n   without a JSX transform (packages/ui/test/chart-style.test.ts). */\n\ntype SeriesColors = Record<string, { color?: string; theme?: Record<string, string> }>\n\nconst IDENT = /^[A-Za-z0-9_-]+$/\n\nconst COLOR_FORMS = [\n  /^var\\(--[A-Za-z0-9_-]+\\)$/,\n  /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,\n  /^(?:rgba?|hsla?|oklch|oklab|lch|lab)\\((?:[0-9.,%\\s/+-]|deg|turn|rad|grad|none)*\\)$/,\n]\n\nexport function isChartColor(value: string) {\n  return COLOR_FORMS.some((form) => form.test(value))\n}\n\n/* The id ChartContainer takes from useId when the caller passes none. React has\n   changed that id's shape between versions (\":r1:\", \"«r1»\", \"_r_1_\"), and any\n   character outside the identifier set would fail the check below and silently\n   drop every colour, so only identifier characters are kept. */\nexport function chartIdFrom(reactId: string) {\n  return reactId.replace(/[^A-Za-z0-9_-]/g, \"\")\n}\n\n/* The stylesheet ChartStyle renders, or null when there is nothing safe to\n   write. For valid input the text is exactly what the upstream shadcn component\n   produces, so a chart's appearance does not change. */\nexport function chartStyleCss(\n  id: string,\n  config: SeriesColors,\n  themes: Record<string, string>,\n): string | null {\n  if (!IDENT.test(id)) return null\n\n  const series = Object.entries(config).filter(([, item]) => item.theme ?? item.color)\n  if (!series.length) return null\n\n  return Object.entries(themes)\n    .map(\n      ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${series\n  .map(([key, item]) => {\n    const color = item.theme?.[theme] ?? item.color\n    return color && IDENT.test(key) && isChartColor(color)\n      ? `  --color-${key}: ${color};`\n      : null\n  })\n  .join(\"\\n\")}\n}\n`,\n    )\n    .join(\"\\n\")\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}