{"$schema":"https://ui.logic2b.com/schema/registry-item.json","name":"chart","type":"registry:ui","description":"A composable chart container built on Recharts. Exposes ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend and ChartLegendContent, plus a ChartConfig type for mapping data keys to labels, icons and CSS colors (--color-*). Use it to build area, bar, line and pie charts with automatic light/dark theme colors and a theme-aware tooltip and legend.","dependencies":["recharts"],"registryDependencies":["utils"],"files":[{"path":"ui/chart.tsx","type":"registry:ui","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\"\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const\n\nexport type ChartConfig = {\n  [k in string]: {\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  ...props\n}: React.ComponentProps<\"div\"> & {\n  config: ChartConfig\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >[\"children\"]\n}) {\n  const uniqueId = React.useId()\n  const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\n        className={cn(\n          \"[&_.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-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 flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.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          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  )\n}\n\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme || config.color\n  )\n\n  if (!colorConfig.length) {\n    return null\n  }\n\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, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||\n      itemConfig.color\n    return color ? `  --color-${key}: ${color};` : null\n  })\n  .join(\"\\n\")}\n}\n`\n          )\n          .join(\"\\n\"),\n      }}\n    />\n  )\n}\n\nconst ChartTooltip = RechartsPrimitive.Tooltip\ntype TooltipNameType = number | string\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 as keyof typeof config]?.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  return (\n    <div\n      className={cn(\n        \"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl\",\n        className\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5\">\n        {payload.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                \"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5\",\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 !== undefined && (\n                      <span className=\"text-foreground font-mono font-medium tabular-nums\">\n                        {item.value.toLocaleString()}\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.map((item) => {\n        const key = `${nameKey || item.dataKey || \"value\"}`\n        const itemConfig = getPayloadConfigFromPayload(config, item, key)\n\n        return (\n          <div\n            key={item.value}\n            className={cn(\n              \"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3\"\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\n// Helper to extract item config from a payload.\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 as Record<string, unknown>)[key] === \"string\"\n  ) {\n    configLabelKey = (payload as Record<string, string>)[key]\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof (payloadPayload as Record<string, unknown>)[key] === \"string\"\n  ) {\n    configLabelKey = (payloadPayload as Record<string, string>)[key]\n  }\n\n  return configLabelKey in config\n    ? config[configLabelKey]\n    : config[key as keyof typeof config]\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n}\n"}],"categories":["data-display"],"accessibility":{"support":"consumer","pattern":"data visualization","primitive":"Recharts","keyboard":[],"aria":["Chart containers can expose a labelled figure while tooltip content is available to pointer and keyboard interactions supported by the composed chart."],"consumer":["Provide a concise chart name, units and a textual or tabular equivalent for the underlying data."],"limitations":["Canvas/SVG marks alone are not a complete accessible data representation."]},"api":{"source":"src/ui/chart.tsx","exports":[{"name":"ChartConfig","kind":"type","definition":"type ChartConfig = { [k in string]: { label?: React.ReactNode icon?: React.ComponentType } & ( | { color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> } ) }"},{"name":"ChartContainer","kind":"component","propsType":"React.ComponentProps<\"div\"> & { config: ChartConfig children: React.ComponentProps< typeof RechartsPrimitive.ResponsiveContainer >[\"children\"] }","props":[{"name":"children","type":"React.ComponentProps< typeof RechartsPrimitive.ResponsiveContainer >[\"children\"]","required":true},{"name":"config","type":"ChartConfig","required":true}]},{"name":"ChartTooltip","kind":"component","aliasOf":"RechartsPrimitive.Tooltip"},{"name":"ChartTooltipContent","kind":"component","propsType":"React.ComponentProps<typeof RechartsPrimitive.Tooltip> & React.ComponentProps<\"div\"> & { hideLabel?: boolean hideIndicator?: boolean indicator?: \"line\" | \"dot\" | \"dashed\" nameKey?: string labelKey?: string } & Omit< RechartsPrimitive.DefaultTooltipContentProps< TooltipValueType, TooltipNameType >, \"accessibilityLayer\" >","props":[{"name":"indicator","type":"\"line\" | \"dot\" | \"dashed\"","required":false,"default":"\"dot\""},{"name":"hideLabel","type":"boolean","required":false,"default":"false"},{"name":"hideIndicator","type":"boolean","required":false,"default":"false"},{"name":"nameKey","type":"string","required":false},{"name":"labelKey","type":"string","required":false}]},{"name":"ChartLegend","kind":"component","aliasOf":"RechartsPrimitive.Legend"},{"name":"ChartLegendContent","kind":"component","propsType":"React.ComponentProps<\"div\"> & { hideIcon?: boolean nameKey?: string } & RechartsPrimitive.DefaultLegendContentProps","props":[{"name":"hideIcon","type":"boolean","required":false,"default":"false"},{"name":"verticalAlign","type":"\"top\" | \"bottom\" | \"middle\"","required":false,"default":"\"bottom\"","description":"Vertical alignment of the whole Legend container: - `bottom`: shows the Legend below chart, and chart height reduces automatically to make space for it. - `top`: shows the Legend above chart, and chart height reduces automatically. - `middle`: shows the Legend in the middle of chart, covering other content, and chart height remains unchanged. The exact behavior changes depending on `align` prop."},{"name":"nameKey","type":"string","required":false}]},{"name":"ChartStyle","kind":"component","propsType":"{ id: string; config: ChartConfig }","props":[{"name":"id","type":"string","required":true},{"name":"config","type":"ChartConfig","required":true}]}]}}