{
  "schemaVersion": 1,
  "name": "chart",
  "useFor": "Use to show volume over time (messages sent, calls, webhook deliveries) or a share (deliveries by status). Name and color each series in a ChartConfig, wrap the chart in ChartContainer, and add ChartTooltip and ChartLegend. Keep to five series; the colors follow the series order.",
  "constraints": [
    "Gradients other than the area fade",
    "3D or shadowed marks",
    "More than five series",
    "Colors outside the series tokens"
  ],
  "documentation": "",
  "source": "Extrapolated from the Inkbox console (Figma 427:2715): muted labels, lilac rules, floating panels and the status colors",
  "exports": [
    "ChartConfig",
    "AreaChart",
    "BarChart",
    "ChartContainer",
    "ChartLegend",
    "ChartLegendContent",
    "ChartStyle",
    "ChartTooltip",
    "ChartTooltipContent",
    "LineChart",
    "PieChart",
    "useChart"
  ],
  "client": true,
  "docsUrl": "https://inkbox-ds.vercel.app/components/chart",
  "markdownUrl": "https://inkbox-ds.vercel.app/r/chart.md",
  "examples": [
    {
      "path": "examples/chart.tsx",
      "content": "\"use client\";\n\nimport { AreaChart, BarChart, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, LineChart, PieChart } from \"@/components/ui/chart\";\n\nconst MESSAGES = [\n  { month: \"January\", email: 186, imessage: 80 },\n  { month: \"February\", email: 305, imessage: 200 },\n  { month: \"March\", email: 237, imessage: 120 },\n  { month: \"April\", email: 173, imessage: 190 },\n  { month: \"May\", email: 209, imessage: 130 },\n  { month: \"June\", email: 264, imessage: 140 },\n];\n\nconst config = {\n  email: { label: \"Email\" },\n  imessage: { label: \"iMessage\" },\n} satisfies ChartConfig;\n\nconst short = (v: string) => v.slice(0, 3);\n\nexport function Preview() {\n  return (\n    <ChartContainer config={config} className=\"aspect-auto h-80\">\n      <BarChart data={MESSAGES} index=\"month\" tickFormatter={short} aria-label=\"Messages sent per month by channel\" />\n      <ChartTooltip content={<ChartTooltipContent />} />\n      <ChartLegend content={<ChartLegendContent />} />\n    </ChartContainer>\n  );\n}\n\nexport function Line() {\n  return (\n    <ChartContainer config={config} className=\"aspect-auto h-80\">\n      <LineChart data={MESSAGES} index=\"month\" tickFormatter={short} showYAxis dots aria-label=\"Messages sent per month\" />\n      <ChartTooltip content={<ChartTooltipContent indicator=\"line\" />} />\n      <ChartLegend content={<ChartLegendContent />} />\n    </ChartContainer>\n  );\n}\n\nexport function Area() {\n  return (\n    <ChartContainer config={config} className=\"aspect-auto h-80\">\n      <AreaChart data={MESSAGES} index=\"month\" tickFormatter={short} stacked aria-label=\"Messages per month, stacked by channel\" />\n      <ChartTooltip content={<ChartTooltipContent indicator=\"dashed\" />} />\n    </ChartContainer>\n  );\n}\n\nconst DELIVERIES = [\n  { status: \"delivered\", count: 1240 },\n  { status: \"retrying\", count: 86 },\n  { status: \"failed\", count: 32 },\n];\n\nconst deliveryConfig = {\n  delivered: { label: \"Delivered\", color: \"var(--ds-color-chart-series-1)\" },\n  retrying: { label: \"Retrying\", color: \"var(--ds-color-chart-series-2)\" },\n  failed: { label: \"Failed\", color: \"var(--ds-color-chart-series-3)\" },\n} satisfies ChartConfig;\n\nexport function Pie() {\n  return (\n    <ChartContainer config={deliveryConfig} className=\"mx-auto aspect-square max-h-72\">\n      <PieChart data={DELIVERIES} index=\"status\" value=\"count\" aria-label=\"Webhook deliveries by status\" />\n      <ChartTooltip content={<ChartTooltipContent hideLabel />} />\n      <ChartLegend content={<ChartLegendContent />} />\n    </ChartContainer>\n  );\n}\n",
      "exports": [
        "Preview",
        "Line",
        "Area",
        "Pie"
      ],
      "requires": [
        "chart"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Preview",
          "url": "https://inkbox-ds.vercel.app/preview/chart/Preview"
        },
        {
          "name": "Line",
          "url": "https://inkbox-ds.vercel.app/preview/chart/Line"
        },
        {
          "name": "Area",
          "url": "https://inkbox-ds.vercel.app/preview/chart/Area"
        },
        {
          "name": "Pie",
          "url": "https://inkbox-ds.vercel.app/preview/chart/Pie"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Chart",
  "description": "Line, area, bar and pie charts in SVG with lilac rules, 12px muted labels and a white floating tooltip.",
  "surface": "site",
  "kinds": [],
  "requires": [
    "theme",
    "utils"
  ],
  "packages": [],
  "files": [
    {
      "path": "components/ui/chart.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n// Charts drawn in SVG (extrapolated from the Inkbox console, Figma 427:2715: its 12px muted labels, lilac rules, white\n// floating panels and the purple, pink, blue, teal and green of its states). ChartContainer takes a config that names\n// and colors each series (series-1 purple, then pink, avatar blue, info teal, success green) and holds the chart, its\n// tooltip and its legend. Lines draw in from the left and bars rise from the axis once, on mount (700ms and 400ms, the\n// strong ease-out); with reduced motion they fade in. Hovering or moving with the arrow keys shows the tooltip.\n\nexport type ChartConfig = Record<string, { label?: React.ReactNode; icon?: React.ComponentType<{ className?: string }>; color?: string }>;\n\ntype Datum = Record<string, string | number>;\ntype Active = { index: number; x: number; y: number } | null;\ntype ChartContextProps = {\n  config: ChartConfig;\n  data: Datum[];\n  setData: (data: Datum[], index: string, keys: string[]) => void;\n  index: string;\n  keys: string[];\n  active: Active;\n  setActive: (active: Active) => void;\n  hidden: Set<string>;\n  toggle: (key: string) => void;\n};\n\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\nconst SERIES = [\"var(--ds-color-chart-series-1)\", \"var(--ds-color-chart-series-2)\", \"var(--ds-color-chart-series-3)\", \"var(--ds-color-chart-series-4)\", \"var(--ds-color-chart-series-5)\"];\nconst colorOf = (config: ChartConfig, key: string, i: number) => `var(--color-${key}, ${config[key]?.color ?? SERIES[i % SERIES.length]})`;\n\n/** Kept for API parity: ChartContainer already sets each series' color as --color-<key> on itself. */\nfunction ChartStyle(_props: { id?: string; config: ChartConfig }) {\n  return null;\n}\n\nfunction ChartContainer({ id, className, children, config, ...props }: React.ComponentProps<\"div\"> & { config: ChartConfig }) {\n  const [data, setDataState] = React.useState<Datum[]>([]);\n  const [index, setIndex] = React.useState(\"\");\n  const [keys, setKeys] = React.useState<string[]>([]);\n  const [active, setActive] = React.useState<Active>(null);\n  const [hidden, setHidden] = React.useState<Set<string>>(new Set());\n  const setData = React.useCallback((d: Datum[], i: string, k: string[]) => {\n    setDataState(d);\n    setIndex(i);\n    setKeys(k);\n  }, []);\n  const toggle = React.useCallback((key: string) => setHidden((h) => {\n    const next = new Set(h);\n    if (next.has(key)) next.delete(key);\n    else next.add(key);\n    return next;\n  }), []);\n  const style = Object.fromEntries(Object.entries(config).map(([key, v], i) => [`--color-${key}`, v.color ?? SERIES[i % SERIES.length]])) as React.CSSProperties;\n  return (\n    <ChartContext.Provider value={{ config, data, setData, index, keys, active, setActive, hidden, toggle }}>\n      <div data-slot=\"chart\" data-chart={id} style={style} className={cn(\"relative flex aspect-video w-full flex-col justify-center font-sans text-caption text-fg-muted\", className)} {...props}>\n        {children}\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nfunction useSize<T extends HTMLElement>() {\n  const ref = React.useRef<T>(null);\n  const [size, setSize] = React.useState({ width: 0, height: 0 });\n  React.useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const observer = new ResizeObserver(([entry]) => setSize({ width: entry.contentRect.width, height: entry.contentRect.height }));\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, []);\n  return [ref, size] as const;\n}\n\nconst PAD = { top: 12, right: 12, bottom: 28, left: 12 };\n\nfunction niceMax(max: number) {\n  if (max <= 0) return 1;\n  const step = 10 ** Math.floor(Math.log10(max));\n  return Math.ceil(max / step) * step;\n}\n\n/** A smooth path through the points (monotone in x), or straight segments. */\nfunction pathOf(points: [number, number][], curve: \"monotone\" | \"linear\") {\n  if (!points.length) return \"\";\n  if (curve === \"linear\" || points.length < 3) return points.map(([x, y], i) => `${i ? \"L\" : \"M\"}${x},${y}`).join(\"\");\n  let d = `M${points[0][0]},${points[0][1]}`;\n  for (let i = 0; i < points.length - 1; i++) {\n    const [x0, y0] = points[i - 1] ?? points[i];\n    const [x1, y1] = points[i];\n    const [x2, y2] = points[i + 1];\n    const [x3, y3] = points[i + 2] ?? points[i + 1];\n    const c1 = [x1 + (x2 - x0) / 6, y1 + (y2 - y0) / 6];\n    const c2 = [x2 - (x3 - x1) / 6, y2 - (y3 - y1) / 6];\n    d += `C${c1[0]},${c1[1]} ${c2[0]},${c2[1]} ${x2},${y2}`;\n  }\n  return d;\n}\n\ntype CartesianProps = {\n  data: Datum[];\n  /** The key of each datum's label on the x axis (month, day). */\n  index: string;\n  /** The series to draw, in order; defaults to every key in the config. */\n  keys?: string[];\n  showGrid?: boolean;\n  showXAxis?: boolean;\n  showYAxis?: boolean;\n  /** Format the x labels (e.g. the first three letters of a month). */\n  tickFormatter?: (value: string) => string;\n  valueFormatter?: (value: number) => string;\n  className?: string;\n  \"aria-label\"?: string;\n};\n\nfunction useCartesian(kind: \"line\" | \"area\" | \"bar\", { data, index, keys: keysProp, showYAxis }: CartesianProps, stacked: boolean) {\n  const chart = useChart();\n  const keys = React.useMemo(() => keysProp ?? Object.keys(chart.config), [keysProp, chart.config]);\n  const { setData } = chart;\n  React.useEffect(() => setData(data, index, keys), [data, index, keys, setData]);\n  const [ref, size] = useSize<HTMLDivElement>();\n  const visible = keys.filter((k) => !chart.hidden.has(k));\n  const values = data.map((d) => (stacked ? visible.reduce((s, k) => s + Number(d[k] ?? 0), 0) : Math.max(0, ...visible.map((k) => Number(d[k] ?? 0)))));\n  const max = niceMax(Math.max(0, ...values));\n  const left = PAD.left + (showYAxis ? 32 : 0);\n  const innerW = Math.max(0, size.width - left - PAD.right);\n  // Lines and areas keep their first and last points (and labels) clear of the edges.\n  const inset = kind === \"bar\" ? 0 : 16;\n  const innerH = Math.max(0, size.height - PAD.top - PAD.bottom);\n  const band = data.length ? innerW / data.length : 0;\n  const xAt = (i: number) => (kind === \"bar\" ? left + band * i + band / 2 : left + inset + (data.length > 1 ? ((innerW - inset * 2) * i) / (data.length - 1) : innerW / 2 - inset));\n  const yAt = (v: number) => PAD.top + innerH - (innerH * v) / max;\n  return { chart, keys, visible, ref, size, max, left, innerW, innerH, band, xAt, yAt };\n}\n\nfunction Frame({ kind, props, geometry, children }: { kind: \"line\" | \"area\" | \"bar\"; props: CartesianProps; geometry: ReturnType<typeof useCartesian>; children: React.ReactNode }) {\n  const { chart, ref, size, max, left, innerW, innerH, band, xAt, yAt } = geometry;\n  const { data, index, showGrid = true, showXAxis = true, showYAxis = false, tickFormatter = (v: string) => v, valueFormatter = (v: number) => v.toLocaleString() } = props;\n  const ticks = [0, 0.25, 0.5, 0.75, 1].map((t) => max * t);\n  const pick = (clientX: number, rect: DOMRect) => {\n    const x = clientX - rect.left;\n    let best = 0;\n    for (let i = 0; i < data.length; i++) if (Math.abs(xAt(i) - x) < Math.abs(xAt(best) - x)) best = i;\n    return best;\n  };\n  const activate = (i: number) => chart.setActive({ index: i, x: xAt(i), y: PAD.top });\n  const labelStep = Math.max(1, Math.ceil(data.length / Math.max(1, Math.floor(innerW / 56))));\n  return (\n    <div ref={ref} className={cn(\"relative h-full min-h-0 w-full flex-1\", props.className)}>\n      {size.width > 0 && (\n        <svg\n          width={size.width}\n          height={size.height}\n          role=\"img\"\n          tabIndex={0}\n          aria-label={props[\"aria-label\"] ?? \"Chart\"}\n          className=\"block overflow-visible rounded-control outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring\"\n          onPointerMove={(e) => activate(pick(e.clientX, e.currentTarget.getBoundingClientRect()))}\n          onPointerLeave={() => chart.setActive(null)}\n          onBlur={() => chart.setActive(null)}\n          onFocus={() => activate(chart.active?.index ?? 0)}\n          onKeyDown={(e) => {\n            if (e.key !== \"ArrowLeft\" && e.key !== \"ArrowRight\") return;\n            e.preventDefault();\n            const current = chart.active?.index ?? 0;\n            activate(Math.min(data.length - 1, Math.max(0, current + (e.key === \"ArrowRight\" ? 1 : -1))));\n          }}\n        >\n          {showGrid && ticks.map((t) => <line key={t} x1={left} x2={left + innerW} y1={yAt(t)} y2={yAt(t)} className=\"stroke-line-subtle\" strokeWidth={1} strokeDasharray={t === 0 ? undefined : \"3 3\"} />)}\n          {showYAxis && ticks.map((t) => (\n            <text key={t} x={left - 8} y={yAt(t)} dy=\"0.32em\" textAnchor=\"end\" className=\"fill-fg-muted text-caption\">\n              {valueFormatter(t)}\n            </text>\n          ))}\n          {chart.active && kind !== \"bar\" && <line x1={xAt(chart.active.index)} x2={xAt(chart.active.index)} y1={PAD.top} y2={PAD.top + innerH} className=\"stroke-line-field\" strokeWidth={1} />}\n          {chart.active && kind === \"bar\" && <rect x={xAt(chart.active.index) - band / 2} y={PAD.top} width={band} height={innerH} rx={6} className=\"fill-subtle\" />}\n          {children}\n          {showXAxis && data.map((d, i) => (i % labelStep === 0 ? (\n            <text key={i} x={xAt(i)} y={PAD.top + innerH + 18} textAnchor=\"middle\" className=\"fill-fg-muted text-caption\">\n              {tickFormatter(String(d[index]))}\n            </text>\n          ) : null))}\n        </svg>\n      )}\n    </div>\n  );\n}\n\nfunction LineChart(props: CartesianProps & { curve?: \"monotone\" | \"linear\"; dots?: boolean }) {\n  const g = useCartesian(\"line\", props, false);\n  const { curve = \"monotone\", dots = false } = props;\n  return (\n    <Frame kind=\"line\" props={props} geometry={g}>\n      {g.visible.map((key) => {\n        const i = g.keys.indexOf(key);\n        const points = props.data.map((d, n) => [g.xAt(n), g.yAt(Number(d[key] ?? 0))] as [number, number]);\n        return (\n          <g key={key} data-series={key}>\n            <path d={pathOf(points, curve)} fill=\"none\" stroke={colorOf(g.chart.config, key, i)} strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" pathLength={1} strokeDasharray=\"1 1\" className=\"animate-chart-line\" />\n            {(dots || g.chart.active) && points.map(([x, y], n) => (dots || g.chart.active?.index === n ? <circle key={n} cx={x} cy={y} r={g.chart.active?.index === n ? 4 : 3} fill=\"var(--ds-color-bg-default)\" stroke={colorOf(g.chart.config, key, i)} strokeWidth={2} /> : null))}\n          </g>\n        );\n      })}\n    </Frame>\n  );\n}\n\nfunction AreaChart(props: CartesianProps & { curve?: \"monotone\" | \"linear\"; stacked?: boolean }) {\n  const { curve = \"monotone\", stacked = false } = props;\n  const g = useCartesian(\"area\", props, stacked);\n  const base = props.data.map(() => 0);\n  const id = React.useId().replace(/:/g, \"\");\n  return (\n    <Frame kind=\"area\" props={props} geometry={g}>\n      <defs>\n        {g.visible.map((key) => (\n          <linearGradient key={key} id={`${id}-${key}`} x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">\n            <stop offset=\"0%\" stopColor={colorOf(g.chart.config, key, g.keys.indexOf(key))} stopOpacity={0.32} />\n            <stop offset=\"100%\" stopColor={colorOf(g.chart.config, key, g.keys.indexOf(key))} stopOpacity={0.02} />\n          </linearGradient>\n        ))}\n      </defs>\n      {g.visible.map((key) => {\n        const i = g.keys.indexOf(key);\n        const lows = stacked ? [...base] : props.data.map(() => 0);\n        const highs = props.data.map((d, n) => lows[n] + Number(d[key] ?? 0));\n        if (stacked) highs.forEach((h, n) => (base[n] = h));\n        const top = highs.map((v, n) => [g.xAt(n), g.yAt(v)] as [number, number]);\n        const bottom = lows.map((v, n) => [g.xAt(n), g.yAt(v)] as [number, number]).reverse();\n        const line = pathOf(top, curve);\n        const area = `${line}L${bottom.map(([x, y]) => `${x},${y}`).join(\"L\")}Z`;\n        return (\n          <g key={key} data-series={key}>\n            <path d={area} fill={`url(#${id}-${key})`} className=\"animate-fade-in\" />\n            <path d={line} fill=\"none\" stroke={colorOf(g.chart.config, key, i)} strokeWidth={2} strokeLinecap=\"round\" pathLength={1} strokeDasharray=\"1 1\" className=\"animate-chart-line\" />\n          </g>\n        );\n      })}\n    </Frame>\n  );\n}\n\nfunction BarChart(props: CartesianProps & { stacked?: boolean; radius?: number }) {\n  const { stacked = false, radius = 4 } = props;\n  const g = useCartesian(\"bar\", props, stacked);\n  const gap = 4;\n  const groupW = Math.max(0, g.band * 0.72);\n  const barW = stacked ? groupW : Math.max(2, (groupW - gap * (g.visible.length - 1)) / Math.max(1, g.visible.length));\n  return (\n    <Frame kind=\"bar\" props={props} geometry={g}>\n      {props.data.map((d, n) => {\n        let acc = 0;\n        return (\n          <g key={n}>\n            {g.visible.map((key, k) => {\n              const value = Number(d[key] ?? 0);\n              const x = stacked ? g.xAt(n) - groupW / 2 : g.xAt(n) - groupW / 2 + k * (barW + gap);\n              const y0 = g.yAt(acc);\n              const y1 = g.yAt(acc + value);\n              if (stacked) acc += value;\n              const top = !stacked || k === g.visible.length - 1;\n              return (\n                <rect\n                  key={key}\n                  x={x}\n                  y={y1}\n                  width={barW}\n                  height={Math.max(0, y0 - y1)}\n                  rx={top ? Math.min(radius, barW / 2) : 0}\n                  fill={colorOf(g.chart.config, key, g.keys.indexOf(key))}\n                  style={{ transformOrigin: `0 ${g.yAt(0)}px`, animationDelay: `${n * 30}ms` }}\n                  className=\"animate-chart-bar\"\n                />\n              );\n            })}\n          </g>\n        );\n      })}\n    </Frame>\n  );\n}\n\nfunction PieChart({ data, index, value, innerRadius = 0.6, className, ...props }: { data: Datum[]; index: string; value: string; innerRadius?: number; className?: string; \"aria-label\"?: string }) {\n  const chart = useChart();\n  const { setData } = chart;\n  React.useEffect(() => setData(data, index, [value]), [data, index, value, setData]);\n  const [ref, size] = useSize<HTMLDivElement>();\n  const shown = data.filter((d) => !chart.hidden.has(String(d[index])));\n  const total = shown.reduce((s, d) => s + Number(d[value] ?? 0), 0) || 1;\n  const r = Math.max(0, Math.min(size.width, size.height) / 2 - 8);\n  const cx = size.width / 2;\n  const cy = size.height / 2;\n  let angle = -Math.PI / 2;\n  return (\n    <div ref={ref} className={cn(\"relative h-full min-h-0 w-full flex-1\", className)}>\n      {r > 0 && (\n        <svg width={size.width} height={size.height} role=\"img\" aria-label={props[\"aria-label\"] ?? \"Chart\"} className=\"block animate-pop-in origin-center\" onPointerLeave={() => chart.setActive(null)}>\n          {shown.map((d) => {\n            const key = String(d[index]);\n            const n = data.indexOf(d);\n            const sweep = (Number(d[value] ?? 0) / total) * Math.PI * 2;\n            const a0 = angle;\n            const a1 = angle + sweep;\n            angle = a1;\n            const ri = r * innerRadius;\n            const large = sweep > Math.PI ? 1 : 0;\n            const p = (a: number, rr: number) => `${cx + rr * Math.cos(a)},${cy + rr * Math.sin(a)}`;\n            const dPath = sweep >= Math.PI * 2 - 1e-6\n              ? `M${cx - r},${cy}a${r},${r} 0 1,0 ${r * 2},0a${r},${r} 0 1,0 ${-r * 2},0${ri ? `M${cx - ri},${cy}a${ri},${ri} 0 1,1 ${ri * 2},0a${ri},${ri} 0 1,1 ${-ri * 2},0` : \"\"}`\n              : `M${p(a0, r)}A${r},${r} 0 ${large} 1 ${p(a1, r)}${ri ? `L${p(a1, ri)}A${ri},${ri} 0 ${large} 0 ${p(a0, ri)}` : `L${cx},${cy}`}Z`;\n            return (\n              <path\n                key={key}\n                d={dPath}\n                fillRule=\"evenodd\"\n                fill={colorOf(chart.config, key, n)}\n                stroke=\"var(--ds-color-bg-default)\"\n                strokeWidth={2}\n                className={cn(\"transition-opacity duration-150 ease-out\", chart.active && chart.active.index !== n && \"opacity-60\")}\n                onPointerMove={(e) => {\n                  const rect = e.currentTarget.ownerSVGElement!.getBoundingClientRect();\n                  chart.setActive({ index: n, x: e.clientX - rect.left, y: e.clientY - rect.top });\n                }}\n              />\n            );\n          })}\n        </svg>\n      )}\n    </div>\n  );\n}\n\ntype TooltipItem = { key: string; name: React.ReactNode; value: number; color: string };\n\n/** Where the tooltip shows: pass `content` (usually <ChartTooltipContent />). It follows the hovered point. */\nfunction ChartTooltip({ content, cursor: _cursor }: { content?: React.ReactElement<ChartTooltipContentProps>; cursor?: boolean }) {\n  const chart = useChart();\n  if (!chart.active || !chart.data.length) return null;\n  const datum = chart.data[chart.active.index];\n  if (!datum) return null;\n  const pie = chart.keys.length === 1 && !(chart.keys[0] in chart.config) && String(datum[chart.index]) in chart.config;\n  const payload: TooltipItem[] = pie\n    ? [{ key: String(datum[chart.index]), name: chart.config[String(datum[chart.index])]?.label ?? String(datum[chart.index]), value: Number(datum[chart.keys[0]] ?? 0), color: colorOf(chart.config, String(datum[chart.index]), chart.active.index) }]\n    : chart.keys.filter((k) => !chart.hidden.has(k)).map((k, i) => ({ key: k, name: chart.config[k]?.label ?? k, value: Number(datum[k] ?? 0), color: colorOf(chart.config, k, chart.keys.indexOf(k) ?? i) }));\n  const element = content ?? <ChartTooltipContent />;\n  return (\n    <div\n      data-slot=\"chart-tooltip\"\n      role=\"status\"\n      className=\"pointer-events-none absolute z-10 animate-fade-in\"\n      style={{ left: chart.active.x, top: chart.active.y, transform: `translate(${chart.active.x > 160 ? \"calc(-100% - 12px)\" : \"12px\"}, 0)` }}\n    >\n      {React.cloneElement(element, { payload, label: pie ? undefined : String(datum[chart.index]) })}\n    </div>\n  );\n}\n\ntype ChartTooltipContentProps = React.ComponentProps<\"div\"> & {\n  payload?: TooltipItem[];\n  label?: string;\n  indicator?: \"dot\" | \"line\" | \"dashed\";\n  hideLabel?: boolean;\n  hideIndicator?: boolean;\n  labelFormatter?: (label: string) => React.ReactNode;\n  formatter?: (value: number, name: React.ReactNode, key: string) => React.ReactNode;\n};\n\n/** The tooltip's panel: white, 8px corners, the floating shadow, the label in 13px medium ink, a row per series. */\nfunction ChartTooltipContent({ payload = [], label, indicator = \"dot\", hideLabel = false, hideIndicator = false, labelFormatter, formatter, className, ...props }: ChartTooltipContentProps) {\n  return (\n    <div data-slot=\"chart-tooltip-content\" className={cn(\"grid min-w-32 gap-1.5 rounded-control bg-surface px-3 py-2 text-caption leading-tight text-fg shadow-floating\", className)} {...props}>\n      {!hideLabel && label !== undefined && <p className=\"text-body-sm font-medium text-fg\">{labelFormatter ? labelFormatter(label) : label}</p>}\n      <div className=\"grid gap-1\">\n        {payload.map((item) => (\n          <div key={item.key} className=\"flex items-center gap-2\">\n            {!hideIndicator && (\n              <span\n                aria-hidden\n                className={cn(\"shrink-0\", indicator === \"dot\" ? \"size-2.5 rounded-xs\" : \"h-3 w-1 rounded-xs\", indicator === \"dashed\" && \"border border-dashed bg-transparent\")}\n                style={indicator === \"dashed\" ? { borderColor: item.color } : { background: item.color }}\n              />\n            )}\n            {formatter ? formatter(item.value, item.name, item.key) : (\n              <>\n                <span className=\"text-fg-muted\">{item.name}</span>\n                <span className=\"ml-auto pl-3 font-medium text-fg tabular-nums\">{item.value.toLocaleString()}</span>\n              </>\n            )}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n\n/** Where the legend shows: pass `content` (usually <ChartLegendContent />). */\nfunction ChartLegend({ content, verticalAlign = \"bottom\" }: { content?: React.ReactElement<React.ComponentProps<typeof ChartLegendContent>>; verticalAlign?: \"top\" | \"bottom\" }) {\n  const element = content ?? <ChartLegendContent />;\n  return <div className={cn(verticalAlign === \"top\" ? \"order-first pb-3\" : \"pt-3\")}>{element}</div>;\n}\n\n/** A row of 12px muted labels with their color keys; each toggles its series when `interactive`. */\nfunction ChartLegendContent({ className, hideIcon = false, interactive = true, ...props }: React.ComponentProps<\"div\"> & { hideIcon?: boolean; interactive?: boolean }) {\n  const chart = useChart();\n  const keys = chart.keys.length === 1 && !(chart.keys[0] in chart.config) ? chart.data.map((d) => String(d[chart.index])) : chart.keys;\n  return (\n    <div data-slot=\"chart-legend-content\" className={cn(\"flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5\", className)} {...props}>\n      {keys.map((key, i) => {\n        const item = chart.config[key];\n        const Icon = item?.icon;\n        const off = chart.hidden.has(key);\n        const body = (\n          <>\n            {Icon && !hideIcon ? <Icon className=\"size-3.5\" /> : <span aria-hidden className=\"size-2.5 rounded-xs\" style={{ background: colorOf(chart.config, key, i) }} />}\n            {item?.label ?? key}\n          </>\n        );\n        return interactive ? (\n          <button key={key} type=\"button\" aria-pressed={!off} onClick={() => chart.toggle(key)} className={cn(\"inline-flex items-center gap-1.5 rounded-xs text-caption leading-tight text-fg-secondary outline-none transition-opacity duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring motion-reduce:transition-none\", off && \"opacity-40\")}>\n            {body}\n          </button>\n        ) : (\n          <span key={key} className=\"inline-flex items-center gap-1.5 text-caption leading-tight text-fg-secondary\">\n            {body}\n          </span>\n        );\n      })}\n    </div>\n  );\n}\n\nexport { AreaChart, BarChart, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, LineChart, PieChart, useChart };\n"
    }
  ]
}
