{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"chart","type":"registry:ui","title":"Chart","description":"Chart from react-fancy","package":"react-fancy","dependencies":[],"registryDependencies":[],"files":[{"path":"components\/fancy\/chart\/Chart.tsx","content":"import { ChartBar } from \".\/ChartBar\";\r\nimport { ChartDonut } from \".\/ChartDonut\";\r\nimport { ChartLine } from \".\/ChartLine\";\r\nimport { ChartArea } from \".\/ChartArea\";\r\nimport { ChartPie } from \".\/ChartPie\";\r\nimport { ChartSparkline } from \".\/ChartSparkline\";\r\nimport { ChartHorizontalBar } from \".\/ChartHorizontalBar\";\r\nimport { ChartStackedBar } from \".\/ChartStackedBar\";\r\n\r\nexport const Chart = {\r\n  Bar: ChartBar,\r\n  Donut: ChartDonut,\r\n  Line: ChartLine,\r\n  Area: ChartArea,\r\n  Pie: ChartPie,\r\n  Sparkline: ChartSparkline,\r\n  HorizontalBar: ChartHorizontalBar,\r\n  StackedBar: ChartStackedBar,\r\n};\r\n","type":"registry:ui","target":"components\/fancy\/chart\/Chart.tsx"},{"path":"components\/fancy\/chart\/Chart.types.ts","content":"export interface ChartBarData {\r\n  label: string;\r\n  value: number;\r\n  color?: string;\r\n}\r\n\r\nexport interface ChartBarProps {\r\n  data: ChartBarData[];\r\n  height?: number;\r\n  showValues?: boolean;\r\n  className?: string;\r\n}\r\n\r\nexport interface ChartDonutData {\r\n  label: string;\r\n  value: number;\r\n  color?: string;\r\n}\r\n\r\nexport interface ChartDonutProps {\r\n  data: ChartDonutData[];\r\n  size?: number;\r\n  strokeWidth?: number;\r\n  showLegend?: boolean;\r\n  className?: string;\r\n}\r\n\r\nexport interface ChartSeries {\r\n  label: string;\r\n  data: number[];\r\n  color?: string;\r\n}\r\n\r\nexport interface ChartCommonProps {\r\n  className?: string;\r\n  height?: number;\r\n  xAxis?: boolean | { label?: string; tickCount?: number };\r\n  yAxis?: boolean | { label?: string; tickCount?: number };\r\n  grid?: boolean | { horizontal?: boolean; vertical?: boolean };\r\n  tooltip?: boolean;\r\n  animate?: boolean;\r\n  responsive?: boolean;\r\n}\r\n\r\nexport interface ChartLineProps extends ChartCommonProps {\r\n  labels: string[];\r\n  series: ChartSeries[];\r\n  curve?: \"linear\" | \"monotone\";\r\n  showDots?: boolean;\r\n  fill?: boolean;\r\n  fillOpacity?: number;\r\n}\r\n\r\nexport type ChartAreaProps = Omit<ChartLineProps, \"fill\">;\r\n\r\nexport interface ChartPieData {\r\n  label: string;\r\n  value: number;\r\n  color?: string;\r\n}\r\n\r\nexport interface ChartPieProps {\r\n  data: ChartPieData[];\r\n  size?: number;\r\n  showLabels?: boolean;\r\n  tooltip?: boolean;\r\n  className?: string;\r\n}\r\n\r\nexport interface ChartSparklineProps {\r\n  data: number[];\r\n  width?: number;\r\n  height?: number;\r\n  color?: string;\r\n  className?: string;\r\n}\r\n\r\nexport interface ChartHorizontalBarProps {\r\n  data: ChartBarData[];\r\n  height?: number;\r\n  showValues?: boolean;\r\n  className?: string;\r\n}\r\n\r\nexport interface ChartStackedBarProps extends ChartCommonProps {\r\n  labels: string[];\r\n  series: ChartSeries[];\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/chart\/Chart.types.ts"},{"path":"components\/fancy\/chart\/ChartArea.tsx","content":"import { ChartLine } from \".\/ChartLine\";\r\nimport type { ChartAreaProps } from \".\/Chart.types\";\r\n\r\nexport function ChartArea(props: ChartAreaProps) {\r\n  return <ChartLine {...props} fill fillOpacity={0.15} \/>;\r\n}\r\n\r\nChartArea.displayName = \"ChartArea\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartArea.tsx"},{"path":"components\/fancy\/chart\/ChartAxis.tsx","content":"interface ChartAxisProps {\r\n  orientation: \"x\" | \"y\";\r\n  ticks: number[];\r\n  scale: (value: number) => number;\r\n  x: number;\r\n  y: number;\r\n  length: number;\r\n  label?: string;\r\n  formatTick?: (value: number) => string;\r\n}\r\n\r\nfunction defaultFormat(value: number): string {\r\n  if (Math.abs(value) >= 1000) return `${(value \/ 1000).toFixed(value % 1000 === 0 ? 0 : 1)}k`;\r\n  if (Number.isInteger(value)) return String(value);\r\n  return value.toFixed(1);\r\n}\r\n\r\nexport function ChartAxis({\r\n  orientation,\r\n  ticks,\r\n  scale,\r\n  x,\r\n  y,\r\n  length,\r\n  label,\r\n  formatTick = defaultFormat,\r\n}: ChartAxisProps) {\r\n  if (orientation === \"x\") {\r\n    return (\r\n      <g>\r\n        <line x1={x} y1={y} x2={x + length} y2={y} stroke=\"currentColor\" className=\"text-zinc-200 dark:text-zinc-700\" \/>\r\n        {ticks.map((tick) => {\r\n          const tx = scale(tick);\r\n          return (\r\n            <g key={tick}>\r\n              <line x1={tx} y1={y} x2={tx} y2={y + 4} stroke=\"currentColor\" className=\"text-zinc-300 dark:text-zinc-600\" \/>\r\n              <text x={tx} y={y + 16} textAnchor=\"middle\" className=\"fill-zinc-500 text-[10px] dark:fill-zinc-400\">\r\n                {formatTick(tick)}\r\n              <\/text>\r\n            <\/g>\r\n          );\r\n        })}\r\n        {label && (\r\n          <text x={x + length \/ 2} y={y + 32} textAnchor=\"middle\" className=\"fill-zinc-500 text-[11px] dark:fill-zinc-400\">\r\n            {label}\r\n          <\/text>\r\n        )}\r\n      <\/g>\r\n    );\r\n  }\r\n\r\n  return (\r\n    <g>\r\n      <line x1={x} y1={y} x2={x} y2={y + length} stroke=\"currentColor\" className=\"text-zinc-200 dark:text-zinc-700\" \/>\r\n      {ticks.map((tick) => {\r\n        const ty = scale(tick);\r\n        return (\r\n          <g key={tick}>\r\n            <line x1={x - 4} y1={ty} x2={x} y2={ty} stroke=\"currentColor\" className=\"text-zinc-300 dark:text-zinc-600\" \/>\r\n            <text x={x - 8} y={ty + 3} textAnchor=\"end\" className=\"fill-zinc-500 text-[10px] dark:fill-zinc-400\">\r\n              {formatTick(tick)}\r\n            <\/text>\r\n          <\/g>\r\n        );\r\n      })}\r\n      {label && (\r\n        <text\r\n          x={x - 36}\r\n          y={y + length \/ 2}\r\n          textAnchor=\"middle\"\r\n          transform={`rotate(-90, ${x - 36}, ${y + length \/ 2})`}\r\n          className=\"fill-zinc-500 text-[11px] dark:fill-zinc-400\"\r\n        >\r\n          {label}\r\n        <\/text>\r\n      )}\r\n    <\/g>\r\n  );\r\n}\r\n\r\nChartAxis.displayName = \"ChartAxis\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartAxis.tsx"},{"path":"components\/fancy\/chart\/ChartBar.tsx","content":"import { useMemo } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { DEFAULT_COLORS } from \".\/chart.utils\";\r\nimport type { ChartBarProps } from \".\/Chart.types\";\r\n\r\nexport function ChartBar({\r\n  data,\r\n  height = 200,\r\n  showValues = false,\r\n  className,\r\n}: ChartBarProps) {\r\n  const maxValue = useMemo(\r\n    () => Math.max(...data.map((d) => d.value), 1),\r\n    [data],\r\n  );\r\n\r\n  return (\r\n    <div className={cn(\"w-full\", className)} data-react-fancy-chart-bar=\"\">\r\n      {showValues && (\r\n        <div className=\"mb-1 flex gap-2\">\r\n          {data.map((item) => (\r\n            <div\r\n              key={item.label}\r\n              className=\"flex-1 text-center text-xs font-medium text-zinc-500 dark:text-zinc-400\"\r\n            >\r\n              {item.value}\r\n            <\/div>\r\n          ))}\r\n        <\/div>\r\n      )}\r\n      <div\r\n        className=\"flex items-end gap-2\"\r\n        style={{ height }}\r\n      >\r\n        {data.map((item, i) => {\r\n          const barHeight = (item.value \/ maxValue) * 100;\r\n          const color = item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length];\r\n\r\n          return (\r\n            <div\r\n              key={item.label}\r\n              className=\"flex h-full flex-1 items-end justify-center\"\r\n              data-react-fancy-chart-bar-column=\"\"\r\n            >\r\n              <div\r\n                data-react-fancy-chart-bar-item=\"\"\r\n                className=\"w-full rounded-t-md transition-all duration-500\"\r\n                style={{\r\n                  height: `${barHeight}%`,\r\n                  backgroundColor: color,\r\n                  minHeight: 4,\r\n                }}\r\n              \/>\r\n            <\/div>\r\n          );\r\n        })}\r\n      <\/div>\r\n      <div className=\"mt-2 flex gap-2\">\r\n        {data.map((item) => (\r\n          <div\r\n            key={item.label}\r\n            className=\"flex-1 text-center text-xs text-zinc-500 dark:text-zinc-400\"\r\n          >\r\n            {item.label}\r\n          <\/div>\r\n        ))}\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n\r\nChartBar.displayName = \"ChartBar\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartBar.tsx"},{"path":"components\/fancy\/chart\/ChartDonut.tsx","content":"import { useMemo } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { DEFAULT_COLORS } from \".\/chart.utils\";\r\nimport type { ChartDonutProps } from \".\/Chart.types\";\r\n\r\nexport function ChartDonut({\r\n  data,\r\n  size = 160,\r\n  strokeWidth = 24,\r\n  showLegend = true,\r\n  className,\r\n}: ChartDonutProps) {\r\n  const total = useMemo(() => data.reduce((s, d) => s + d.value, 0), [data]);\r\n  const radius = (size - strokeWidth) \/ 2;\r\n  const circumference = 2 * Math.PI * radius;\r\n\r\n  const segments = useMemo(() => {\r\n    let offset = 0;\r\n    return data.map((item, i) => {\r\n      const pct = total > 0 ? item.value \/ total : 0;\r\n      const dash = pct * circumference;\r\n      const gap = circumference - dash;\r\n      const rotation = (offset \/ total) * 360;\r\n      offset += item.value;\r\n\r\n      return {\r\n        ...item,\r\n        dash,\r\n        gap,\r\n        rotation,\r\n        color: item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length],\r\n      };\r\n    });\r\n  }, [data, total, circumference]);\r\n\r\n  return (\r\n    <div data-react-fancy-chart-donut=\"\" className={cn(\"inline-flex items-center gap-6\", className)}>\r\n      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>\r\n        {segments.map((seg) => (\r\n          <circle\r\n            key={seg.label}\r\n            cx={size \/ 2}\r\n            cy={size \/ 2}\r\n            r={radius}\r\n            fill=\"none\"\r\n            stroke={seg.color}\r\n            strokeWidth={strokeWidth}\r\n            strokeDasharray={`${seg.dash} ${seg.gap}`}\r\n            strokeLinecap=\"round\"\r\n            transform={`rotate(${seg.rotation - 90} ${size \/ 2} ${size \/ 2})`}\r\n          \/>\r\n        ))}\r\n      <\/svg>\r\n      {showLegend && (\r\n        <div className=\"flex flex-col gap-2\">\r\n          {segments.map((seg) => (\r\n            <div key={seg.label} className=\"flex items-center gap-2 text-sm\">\r\n              <div\r\n                className=\"h-3 w-3 rounded-full\"\r\n                style={{ backgroundColor: seg.color }}\r\n              \/>\r\n              <span className=\"text-zinc-600 dark:text-zinc-400\">\r\n                {seg.label}\r\n              <\/span>\r\n              <span className=\"font-medium\">{seg.value}<\/span>\r\n            <\/div>\r\n          ))}\r\n        <\/div>\r\n      )}\r\n    <\/div>\r\n  );\r\n}\r\n\r\nChartDonut.displayName = \"ChartDonut\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartDonut.tsx"},{"path":"components\/fancy\/chart\/ChartGrid.tsx","content":"interface ChartGridProps {\r\n  x: number;\r\n  y: number;\r\n  width: number;\r\n  height: number;\r\n  horizontal?: { ticks: number[]; scale: (v: number) => number };\r\n  vertical?: { ticks: number[]; scale: (v: number) => number };\r\n}\r\n\r\nexport function ChartGrid({ x, y, width, height, horizontal, vertical }: ChartGridProps) {\r\n  return (\r\n    <g>\r\n      {horizontal?.ticks.map((tick) => {\r\n        const ty = horizontal.scale(tick);\r\n        return (\r\n          <line\r\n            key={`h-${tick}`}\r\n            x1={x}\r\n            y1={ty}\r\n            x2={x + width}\r\n            y2={ty}\r\n            stroke=\"currentColor\"\r\n            strokeDasharray=\"4 4\"\r\n            className=\"text-zinc-100 dark:text-zinc-800\"\r\n          \/>\r\n        );\r\n      })}\r\n      {vertical?.ticks.map((tick) => {\r\n        const tx = vertical.scale(tick);\r\n        return (\r\n          <line\r\n            key={`v-${tick}`}\r\n            x1={tx}\r\n            y1={y}\r\n            x2={tx}\r\n            y2={y + height}\r\n            stroke=\"currentColor\"\r\n            strokeDasharray=\"4 4\"\r\n            className=\"text-zinc-100 dark:text-zinc-800\"\r\n          \/>\r\n        );\r\n      })}\r\n    <\/g>\r\n  );\r\n}\r\n\r\nChartGrid.displayName = \"ChartGrid\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartGrid.tsx"},{"path":"components\/fancy\/chart\/ChartHorizontalBar.tsx","content":"import { useMemo } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { DEFAULT_COLORS } from \".\/chart.utils\";\r\nimport type { ChartHorizontalBarProps } from \".\/Chart.types\";\r\n\r\nexport function ChartHorizontalBar({\r\n  data,\r\n  height,\r\n  showValues = true,\r\n  className,\r\n}: ChartHorizontalBarProps) {\r\n  const maxValue = useMemo(\r\n    () => Math.max(...data.map((d) => d.value), 1),\r\n    [data],\r\n  );\r\n\r\n  const barHeight = 28;\r\n  const gap = 8;\r\n  const computedHeight = height ?? data.length * (barHeight + gap) - gap;\r\n\r\n  return (\r\n    <div className={cn(\"w-full\", className)} data-react-fancy-chart-horizontal-bar=\"\">\r\n      <div className=\"flex flex-col gap-2\" style={{ height: computedHeight }}>\r\n        {data.map((item, i) => {\r\n          const widthPct = (item.value \/ maxValue) * 100;\r\n          const color = item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length];\r\n\r\n          return (\r\n            <div key={item.label} className=\"flex items-center gap-3\">\r\n              <span className=\"w-20 shrink-0 truncate text-right text-xs text-zinc-500 dark:text-zinc-400\">\r\n                {item.label}\r\n              <\/span>\r\n              <div className=\"relative flex-1\">\r\n                <div\r\n                  className=\"h-7 rounded-r-md transition-all duration-500\"\r\n                  style={{\r\n                    width: `${widthPct}%`,\r\n                    backgroundColor: color,\r\n                    minWidth: 4,\r\n                  }}\r\n                \/>\r\n                {showValues && (\r\n                  <span className=\"absolute top-1\/2 left-full ml-2 -translate-y-1\/2 text-xs font-medium text-zinc-500\">\r\n                    {item.value}\r\n                  <\/span>\r\n                )}\r\n              <\/div>\r\n            <\/div>\r\n          );\r\n        })}\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n\r\nChartHorizontalBar.displayName = \"ChartHorizontalBar\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartHorizontalBar.tsx"},{"path":"components\/fancy\/chart\/ChartLine.tsx","content":"import { useMemo, useRef, useState } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { DEFAULT_COLORS, createLinearScale, linearPath, monotonePath, niceScale } from \".\/chart.utils\";\r\nimport { ChartAxis } from \".\/ChartAxis\";\r\nimport { ChartGrid } from \".\/ChartGrid\";\r\nimport { ChartTooltip } from \".\/ChartTooltip\";\r\nimport type { ChartLineProps } from \".\/Chart.types\";\r\n\r\nconst PADDING = { top: 16, right: 16, bottom: 40, left: 48 };\r\n\r\nexport function ChartLine({\r\n  labels,\r\n  series,\r\n  height = 240,\r\n  curve = \"monotone\",\r\n  showDots = true,\r\n  fill = false,\r\n  fillOpacity = 0.1,\r\n  xAxis = true,\r\n  yAxis = true,\r\n  grid = true,\r\n  tooltip = true,\r\n  animate = true,\r\n  responsive = false,\r\n  className,\r\n}: ChartLineProps) {\r\n  const containerRef = useRef<HTMLDivElement>(null);\r\n  const [hoverInfo, setHoverInfo] = useState<{ x: number; y: number; labelIdx: number } | null>(null);\r\n  const width = 500;\r\n\r\n  const plotWidth = width - PADDING.left - PADDING.right;\r\n  const plotHeight = height - PADDING.top - PADDING.bottom;\r\n\r\n  const allValues = series.flatMap((s) => s.data);\r\n  const dataMin = Math.min(0, ...allValues);\r\n  const dataMax = Math.max(...allValues);\r\n\r\n  const { min: yMin, max: yMax, ticks: yTicks } = useMemo(\r\n    () => niceScale(dataMin, dataMax, 5),\r\n    [dataMin, dataMax],\r\n  );\r\n\r\n  const xScale = useMemo(\r\n    () => createLinearScale([0, labels.length - 1], [PADDING.left, PADDING.left + plotWidth]),\r\n    [labels.length, plotWidth],\r\n  );\r\n  const yScale = useMemo(\r\n    () => createLinearScale([yMin, yMax], [PADDING.top + plotHeight, PADDING.top]),\r\n    [yMin, yMax, plotHeight],\r\n  );\r\n\r\n  const pathFn = curve === \"monotone\" ? monotonePath : linearPath;\r\n\r\n  const handleMouseMove = (e: React.MouseEvent) => {\r\n    if (!tooltip || !containerRef.current) return;\r\n    const rect = containerRef.current.getBoundingClientRect();\r\n    const svgX = ((e.clientX - rect.left) \/ rect.width) * width;\r\n    const closestIdx = Math.round(((svgX - PADDING.left) \/ plotWidth) * (labels.length - 1));\r\n    const idx = Math.max(0, Math.min(labels.length - 1, closestIdx));\r\n    setHoverInfo({\r\n      x: ((xScale(idx) \/ width) * rect.width),\r\n      y: e.clientY - rect.top,\r\n      labelIdx: idx,\r\n    });\r\n  };\r\n\r\n  const svgProps = responsive\r\n    ? { viewBox: `0 0 ${width} ${height}`, width: \"100%\", preserveAspectRatio: \"xMidYMid meet\" as const }\r\n    : { width, height };\r\n\r\n  return (\r\n    <div\r\n      ref={containerRef}\r\n      data-react-fancy-chart-line=\"\"\r\n      className={cn(\"relative\", className)}\r\n      onMouseMove={handleMouseMove}\r\n      onMouseLeave={() => setHoverInfo(null)}\r\n    >\r\n      <svg {...svgProps} height={responsive ? undefined : height}>\r\n        {grid && (\r\n          <ChartGrid\r\n            x={PADDING.left}\r\n            y={PADDING.top}\r\n            width={plotWidth}\r\n            height={plotHeight}\r\n            horizontal={{ ticks: yTicks, scale: yScale }}\r\n          \/>\r\n        )}\r\n\r\n        {series.map((s, si) => {\r\n          const color = s.color ?? DEFAULT_COLORS[si % DEFAULT_COLORS.length];\r\n          const points: [number, number][] = s.data.map((v, i) => [xScale(i), yScale(v)]);\r\n          const d = pathFn(points);\r\n\r\n          return (\r\n            <g key={s.label}>\r\n              {fill && (\r\n                <path\r\n                  d={`${d} L${points[points.length - 1][0]},${yScale(yMin)} L${points[0][0]},${yScale(yMin)} Z`}\r\n                  fill={color}\r\n                  fillOpacity={fillOpacity}\r\n                \/>\r\n              )}\r\n              <path\r\n                d={d}\r\n                fill=\"none\"\r\n                stroke={color}\r\n                strokeWidth={2}\r\n                className={animate ? \"transition-all duration-500\" : \"\"}\r\n              \/>\r\n              {showDots && points.map(([px, py], i) => (\r\n                <circle\r\n                  key={i}\r\n                  cx={px}\r\n                  cy={py}\r\n                  r={3}\r\n                  fill=\"white\"\r\n                  stroke={color}\r\n                  strokeWidth={2}\r\n                \/>\r\n              ))}\r\n            <\/g>\r\n          );\r\n        })}\r\n\r\n        {yAxis && (\r\n          <ChartAxis orientation=\"y\" ticks={yTicks} scale={yScale} x={PADDING.left} y={PADDING.top} length={plotHeight} \/>\r\n        )}\r\n        {xAxis && (\r\n          <ChartAxis\r\n            orientation=\"x\"\r\n            ticks={labels.map((_, i) => i)}\r\n            scale={xScale}\r\n            x={PADDING.left}\r\n            y={PADDING.top + plotHeight}\r\n            length={plotWidth}\r\n            formatTick={(v) => labels[Math.round(v)] ?? \"\"}\r\n          \/>\r\n        )}\r\n\r\n        {\/* Hover line *\/}\r\n        {hoverInfo !== null && (\r\n          <line\r\n            x1={xScale(hoverInfo.labelIdx)}\r\n            y1={PADDING.top}\r\n            x2={xScale(hoverInfo.labelIdx)}\r\n            y2={PADDING.top + plotHeight}\r\n            stroke=\"currentColor\"\r\n            strokeDasharray=\"4 4\"\r\n            className=\"text-zinc-300 dark:text-zinc-600\"\r\n          \/>\r\n        )}\r\n      <\/svg>\r\n\r\n      {tooltip && hoverInfo !== null && (\r\n        <ChartTooltip\r\n          visible\r\n          x={hoverInfo.x}\r\n          y={hoverInfo.y}\r\n          content={\r\n            <div>\r\n              <div className=\"mb-1 font-medium\">{labels[hoverInfo.labelIdx]}<\/div>\r\n              {series.map((s, si) => (\r\n                <div key={s.label} className=\"flex items-center gap-2\">\r\n                  <span\r\n                    className=\"inline-block h-2 w-2 rounded-full\"\r\n                    style={{ backgroundColor: s.color ?? DEFAULT_COLORS[si % DEFAULT_COLORS.length] }}\r\n                  \/>\r\n                  <span className=\"text-zinc-500\">{s.label}:<\/span>\r\n                  <span className=\"font-medium\">{s.data[hoverInfo.labelIdx]}<\/span>\r\n                <\/div>\r\n              ))}\r\n            <\/div>\r\n          }\r\n        \/>\r\n      )}\r\n\r\n      {\/* Legend *\/}\r\n      {series.length > 1 && (\r\n        <div className=\"mt-2 flex flex-wrap items-center justify-center gap-4\">\r\n          {series.map((s, si) => (\r\n            <div key={s.label} className=\"flex items-center gap-1.5 text-xs\">\r\n              <span\r\n                className=\"inline-block h-2.5 w-2.5 rounded-full\"\r\n                style={{ backgroundColor: s.color ?? DEFAULT_COLORS[si % DEFAULT_COLORS.length] }}\r\n              \/>\r\n              <span className=\"text-zinc-600 dark:text-zinc-400\">{s.label}<\/span>\r\n            <\/div>\r\n          ))}\r\n        <\/div>\r\n      )}\r\n    <\/div>\r\n  );\r\n}\r\n\r\nChartLine.displayName = \"ChartLine\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartLine.tsx"},{"path":"components\/fancy\/chart\/ChartPie.tsx","content":"import { useMemo, useRef, useState } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { DEFAULT_COLORS } from \".\/chart.utils\";\r\nimport { ChartTooltip } from \".\/ChartTooltip\";\r\nimport type { ChartPieProps } from \".\/Chart.types\";\r\n\r\nexport function ChartPie({\r\n  data,\r\n  size = 200,\r\n  showLabels = false,\r\n  tooltip = true,\r\n  className,\r\n}: ChartPieProps) {\r\n  const containerRef = useRef<HTMLDivElement>(null);\r\n  const [hoverIdx, setHoverIdx] = useState<number | null>(null);\r\n  const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });\r\n\r\n  const total = useMemo(() => data.reduce((s, d) => s + d.value, 0), [data]);\r\n  const center = size \/ 2;\r\n  const radius = size \/ 2 - 4;\r\n\r\n  const slices = useMemo(() => {\r\n    let startAngle = -Math.PI \/ 2;\r\n    return data.map((item, i) => {\r\n      const pct = total > 0 ? item.value \/ total : 0;\r\n      const angle = pct * 2 * Math.PI;\r\n      const endAngle = startAngle + angle;\r\n      const largeArc = angle > Math.PI ? 1 : 0;\r\n\r\n      const x1 = center + radius * Math.cos(startAngle);\r\n      const y1 = center + radius * Math.sin(startAngle);\r\n      const x2 = center + radius * Math.cos(endAngle);\r\n      const y2 = center + radius * Math.sin(endAngle);\r\n\r\n      const midAngle = startAngle + angle \/ 2;\r\n      const labelX = center + (radius * 0.65) * Math.cos(midAngle);\r\n      const labelY = center + (radius * 0.65) * Math.sin(midAngle);\r\n\r\n      const d = pct >= 1\r\n        ? `M${center},${center - radius} A${radius},${radius} 0 1 1 ${center},${center + radius} A${radius},${radius} 0 1 1 ${center},${center - radius}`\r\n        : `M${center},${center} L${x1},${y1} A${radius},${radius} 0 ${largeArc} 1 ${x2},${y2} Z`;\r\n\r\n      const color = item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length];\r\n      startAngle = endAngle;\r\n\r\n      return { ...item, d, color, labelX, labelY, pct };\r\n    });\r\n  }, [data, total, center, radius]);\r\n\r\n  const handleMouseMove = (e: React.MouseEvent) => {\r\n    if (!containerRef.current) return;\r\n    const rect = containerRef.current.getBoundingClientRect();\r\n    setTooltipPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });\r\n  };\r\n\r\n  return (\r\n    <div ref={containerRef} data-react-fancy-chart-pie=\"\" className={cn(\"relative inline-flex items-center gap-6\", className)} onMouseMove={handleMouseMove}>\r\n      <svg width={size} height={size}>\r\n        {slices.map((slice, i) => (\r\n          <g key={slice.label}>\r\n            <path\r\n              d={slice.d}\r\n              fill={slice.color}\r\n              stroke=\"white\"\r\n              strokeWidth={2}\r\n              className=\"transition-opacity\"\r\n              opacity={hoverIdx !== null && hoverIdx !== i ? 0.5 : 1}\r\n              onMouseEnter={() => setHoverIdx(i)}\r\n              onMouseLeave={() => setHoverIdx(null)}\r\n            \/>\r\n            {showLabels && slice.pct > 0.05 && (\r\n              <text\r\n                x={slice.labelX}\r\n                y={slice.labelY}\r\n                textAnchor=\"middle\"\r\n                dominantBaseline=\"middle\"\r\n                className=\"fill-white text-[10px] font-medium\"\r\n              >\r\n                {Math.round(slice.pct * 100)}%\r\n              <\/text>\r\n            )}\r\n          <\/g>\r\n        ))}\r\n      <\/svg>\r\n\r\n      {\/* Legend *\/}\r\n      <div className=\"flex flex-col gap-2\">\r\n        {slices.map((slice) => (\r\n          <div key={slice.label} className=\"flex items-center gap-2 text-sm\">\r\n            <div className=\"h-3 w-3 rounded-full\" style={{ backgroundColor: slice.color }} \/>\r\n            <span className=\"text-zinc-600 dark:text-zinc-400\">{slice.label}<\/span>\r\n            <span className=\"font-medium\">{slice.value}<\/span>\r\n          <\/div>\r\n        ))}\r\n      <\/div>\r\n\r\n      {tooltip && hoverIdx !== null && (\r\n        <ChartTooltip\r\n          visible\r\n          x={tooltipPos.x}\r\n          y={tooltipPos.y}\r\n          content={\r\n            <div>\r\n              <span className=\"font-medium\">{slices[hoverIdx].label}<\/span>:{\" \"}\r\n              {slices[hoverIdx].value} ({Math.round(slices[hoverIdx].pct * 100)}%)\r\n            <\/div>\r\n          }\r\n        \/>\r\n      )}\r\n    <\/div>\r\n  );\r\n}\r\n\r\nChartPie.displayName = \"ChartPie\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartPie.tsx"},{"path":"components\/fancy\/chart\/ChartSparkline.tsx","content":"import { useMemo } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { createLinearScale, monotonePath } from \".\/chart.utils\";\r\nimport type { ChartSparklineProps } from \".\/Chart.types\";\r\n\r\nexport function ChartSparkline({\r\n  data,\r\n  width = 120,\r\n  height = 32,\r\n  color = \"#3b82f6\",\r\n  className,\r\n}: ChartSparklineProps) {\r\n  const d = useMemo(() => {\r\n    if (data.length < 2) return \"\";\r\n    const min = Math.min(...data);\r\n    const max = Math.max(...data);\r\n    const xScale = createLinearScale([0, data.length - 1], [1, width - 1]);\r\n    const yScale = createLinearScale([min, max], [height - 2, 2]);\r\n    const points: [number, number][] = data.map((v, i) => [xScale(i), yScale(v)]);\r\n    return monotonePath(points);\r\n  }, [data, width, height]);\r\n\r\n  return (\r\n    <svg\r\n      width={width}\r\n      height={height}\r\n      className={cn(\"inline-block\", className)}\r\n      data-react-fancy-chart-sparkline=\"\"\r\n    >\r\n      <path d={d} fill=\"none\" stroke={color} strokeWidth={1.5} strokeLinecap=\"round\" \/>\r\n    <\/svg>\r\n  );\r\n}\r\n\r\nChartSparkline.displayName = \"ChartSparkline\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartSparkline.tsx"},{"path":"components\/fancy\/chart\/ChartStackedBar.tsx","content":"import { useMemo, useRef, useState } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { DEFAULT_COLORS, createLinearScale, niceScale } from \".\/chart.utils\";\r\nimport { ChartAxis } from \".\/ChartAxis\";\r\nimport { ChartGrid } from \".\/ChartGrid\";\r\nimport { ChartTooltip } from \".\/ChartTooltip\";\r\nimport type { ChartStackedBarProps } from \".\/Chart.types\";\r\n\r\nconst PADDING = { top: 16, right: 16, bottom: 40, left: 48 };\r\n\r\nexport function ChartStackedBar({\r\n  labels,\r\n  series,\r\n  height = 240,\r\n  xAxis = true,\r\n  yAxis = true,\r\n  grid = true,\r\n  tooltip = true,\r\n  responsive = false,\r\n  className,\r\n}: ChartStackedBarProps) {\r\n  const containerRef = useRef<HTMLDivElement>(null);\r\n  const [hoverIdx, setHoverIdx] = useState<number | null>(null);\r\n  const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });\r\n  const width = 500;\r\n\r\n  const plotWidth = width - PADDING.left - PADDING.right;\r\n  const plotHeight = height - PADDING.top - PADDING.bottom;\r\n\r\n  const stackTotals = useMemo(\r\n    () => labels.map((_, li) => series.reduce((sum, s) => sum + (s.data[li] ?? 0), 0)),\r\n    [labels, series],\r\n  );\r\n\r\n  const maxTotal = Math.max(...stackTotals, 1);\r\n  const { min: yMin, max: yMax, ticks: yTicks } = useMemo(\r\n    () => niceScale(0, maxTotal, 5),\r\n    [maxTotal],\r\n  );\r\n\r\n  const yScale = useMemo(\r\n    () => createLinearScale([yMin, yMax], [PADDING.top + plotHeight, PADDING.top]),\r\n    [yMin, yMax, plotHeight],\r\n  );\r\n\r\n  const barWidth = Math.max(8, (plotWidth \/ labels.length) * 0.6);\r\n  const barGap = (plotWidth - barWidth * labels.length) \/ (labels.length + 1);\r\n\r\n  const handleMouseMove = (e: React.MouseEvent) => {\r\n    if (!tooltip || !containerRef.current) return;\r\n    const rect = containerRef.current.getBoundingClientRect();\r\n    setTooltipPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });\r\n  };\r\n\r\n  const svgProps = responsive\r\n    ? { viewBox: `0 0 ${width} ${height}`, width: \"100%\", preserveAspectRatio: \"xMidYMid meet\" as const }\r\n    : { width, height };\r\n\r\n  return (\r\n    <div ref={containerRef} data-react-fancy-chart-stacked-bar=\"\" className={cn(\"relative\", className)} onMouseMove={handleMouseMove}>\r\n      <svg {...svgProps} height={responsive ? undefined : height}>\r\n        {grid && (\r\n          <ChartGrid\r\n            x={PADDING.left}\r\n            y={PADDING.top}\r\n            width={plotWidth}\r\n            height={plotHeight}\r\n            horizontal={{ ticks: yTicks, scale: yScale }}\r\n          \/>\r\n        )}\r\n\r\n        {labels.map((label, li) => {\r\n          const x = PADDING.left + barGap * (li + 1) + barWidth * li;\r\n          let cumulative = 0;\r\n\r\n          return (\r\n            <g\r\n              key={label}\r\n              onMouseEnter={() => setHoverIdx(li)}\r\n              onMouseLeave={() => setHoverIdx(null)}\r\n            >\r\n              {series.map((s, si) => {\r\n                const val = s.data[li] ?? 0;\r\n                const y0 = yScale(cumulative);\r\n                const y1 = yScale(cumulative + val);\r\n                cumulative += val;\r\n                const color = s.color ?? DEFAULT_COLORS[si % DEFAULT_COLORS.length];\r\n\r\n                return (\r\n                  <rect\r\n                    key={s.label}\r\n                    x={x}\r\n                    y={y1}\r\n                    width={barWidth}\r\n                    height={Math.max(0, y0 - y1)}\r\n                    fill={color}\r\n                    rx={si === series.length - 1 ? 3 : 0}\r\n                    className=\"transition-opacity\"\r\n                    opacity={hoverIdx !== null && hoverIdx !== li ? 0.5 : 1}\r\n                  \/>\r\n                );\r\n              })}\r\n            <\/g>\r\n          );\r\n        })}\r\n\r\n        {yAxis && (\r\n          <ChartAxis orientation=\"y\" ticks={yTicks} scale={yScale} x={PADDING.left} y={PADDING.top} length={plotHeight} \/>\r\n        )}\r\n        {xAxis && (\r\n          <ChartAxis\r\n            orientation=\"x\"\r\n            ticks={labels.map((_, i) => i)}\r\n            scale={(i) => PADDING.left + barGap * (i + 1) + barWidth * i + barWidth \/ 2}\r\n            x={PADDING.left}\r\n            y={PADDING.top + plotHeight}\r\n            length={plotWidth}\r\n            formatTick={(v) => labels[Math.round(v)] ?? \"\"}\r\n          \/>\r\n        )}\r\n      <\/svg>\r\n\r\n      {tooltip && hoverIdx !== null && (\r\n        <ChartTooltip\r\n          visible\r\n          x={tooltipPos.x}\r\n          y={tooltipPos.y}\r\n          content={\r\n            <div>\r\n              <div className=\"mb-1 font-medium\">{labels[hoverIdx]}<\/div>\r\n              {series.map((s, si) => (\r\n                <div key={s.label} className=\"flex items-center gap-2\">\r\n                  <span\r\n                    className=\"inline-block h-2 w-2 rounded-full\"\r\n                    style={{ backgroundColor: s.color ?? DEFAULT_COLORS[si % DEFAULT_COLORS.length] }}\r\n                  \/>\r\n                  <span className=\"text-zinc-500\">{s.label}:<\/span>\r\n                  <span className=\"font-medium\">{s.data[hoverIdx] ?? 0}<\/span>\r\n                <\/div>\r\n              ))}\r\n              <div className=\"mt-1 border-t border-zinc-200 pt-1 text-zinc-500 dark:border-zinc-700\">\r\n                Total: <span className=\"font-medium text-zinc-900 dark:text-white\">{stackTotals[hoverIdx]}<\/span>\r\n              <\/div>\r\n            <\/div>\r\n          }\r\n        \/>\r\n      )}\r\n\r\n      {\/* Legend *\/}\r\n      <div className=\"mt-2 flex flex-wrap items-center justify-center gap-4\">\r\n        {series.map((s, si) => (\r\n          <div key={s.label} className=\"flex items-center gap-1.5 text-xs\">\r\n            <span\r\n              className=\"inline-block h-2.5 w-2.5 rounded-full\"\r\n              style={{ backgroundColor: s.color ?? DEFAULT_COLORS[si % DEFAULT_COLORS.length] }}\r\n            \/>\r\n            <span className=\"text-zinc-600 dark:text-zinc-400\">{s.label}<\/span>\r\n          <\/div>\r\n        ))}\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n\r\nChartStackedBar.displayName = \"ChartStackedBar\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartStackedBar.tsx"},{"path":"components\/fancy\/chart\/ChartTooltip.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\n\r\ninterface ChartTooltipProps {\r\n  visible: boolean;\r\n  x: number;\r\n  y: number;\r\n  content: React.ReactNode;\r\n  className?: string;\r\n}\r\n\r\nexport function ChartTooltip({ visible, x, y, content, className }: ChartTooltipProps) {\r\n  if (!visible) return null;\r\n\r\n  return (\r\n    <div\r\n      className={cn(\r\n        \"pointer-events-none absolute z-50 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs shadow-lg dark:border-zinc-700 dark:bg-zinc-900\",\r\n        className,\r\n      )}\r\n      style={{\r\n        left: x,\r\n        top: y,\r\n        transform: \"translate(-50%, -100%) translateY(-8px)\",\r\n      }}\r\n    >\r\n      {content}\r\n    <\/div>\r\n  );\r\n}\r\n\r\nChartTooltip.displayName = \"ChartTooltip\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/ChartTooltip.tsx"},{"path":"components\/fancy\/chart\/chart.utils.ts","content":"export const DEFAULT_COLORS = [\r\n  \"#3b82f6\", \"#10b981\", \"#f59e0b\", \"#ef4444\", \"#8b5cf6\",\r\n  \"#06b6d4\", \"#ec4899\", \"#f97316\",\r\n];\r\n\r\nexport function createLinearScale(\r\n  domain: [number, number],\r\n  range: [number, number],\r\n): (value: number) => number {\r\n  const [d0, d1] = domain;\r\n  const [r0, r1] = range;\r\n  const span = d1 - d0 || 1;\r\n  return (value: number) => r0 + ((value - d0) \/ span) * (r1 - r0);\r\n}\r\n\r\nexport function linearPath(points: [number, number][]): string {\r\n  if (points.length === 0) return \"\";\r\n  return points.map((p, i) => `${i === 0 ? \"M\" : \"L\"}${p[0]},${p[1]}`).join(\" \");\r\n}\r\n\r\nexport function monotonePath(points: [number, number][]): string {\r\n  if (points.length < 2) return linearPath(points);\r\n  if (points.length === 2) return linearPath(points);\r\n\r\n  const n = points.length;\r\n  const dx: number[] = [];\r\n  const dy: number[] = [];\r\n  const m: number[] = [];\r\n\r\n  for (let i = 0; i < n - 1; i++) {\r\n    dx.push(points[i + 1][0] - points[i][0]);\r\n    dy.push(points[i + 1][1] - points[i][1]);\r\n    m.push(dy[i] \/ (dx[i] || 1));\r\n  }\r\n\r\n  \/\/ Fritsch-Carlson tangents\r\n  const tangents: number[] = [m[0]];\r\n  for (let i = 1; i < n - 1; i++) {\r\n    if (m[i - 1] * m[i] <= 0) {\r\n      tangents.push(0);\r\n    } else {\r\n      tangents.push((m[i - 1] + m[i]) \/ 2);\r\n    }\r\n  }\r\n  tangents.push(m[n - 2]);\r\n\r\n  \/\/ Clamp tangents\r\n  for (let i = 0; i < n - 1; i++) {\r\n    if (Math.abs(m[i]) < 1e-6) {\r\n      tangents[i] = 0;\r\n      tangents[i + 1] = 0;\r\n    } else {\r\n      const a = tangents[i] \/ m[i];\r\n      const b = tangents[i + 1] \/ m[i];\r\n      const s = a * a + b * b;\r\n      if (s > 9) {\r\n        const t = 3 \/ Math.sqrt(s);\r\n        tangents[i] = t * a * m[i];\r\n        tangents[i + 1] = t * b * m[i];\r\n      }\r\n    }\r\n  }\r\n\r\n  let d = `M${points[0][0]},${points[0][1]}`;\r\n  for (let i = 0; i < n - 1; i++) {\r\n    const p0 = points[i];\r\n    const p1 = points[i + 1];\r\n    const dxi = dx[i] \/ 3;\r\n    const cp1x = p0[0] + dxi;\r\n    const cp1y = p0[1] + tangents[i] * dxi;\r\n    const cp2x = p1[0] - dxi;\r\n    const cp2y = p1[1] - tangents[i + 1] * dxi;\r\n    d += ` C${cp1x},${cp1y} ${cp2x},${cp2y} ${p1[0]},${p1[1]}`;\r\n  }\r\n\r\n  return d;\r\n}\r\n\r\nexport function areaPath(points: [number, number][], baseline: number): string {\r\n  if (points.length === 0) return \"\";\r\n  const top = monotonePath(points);\r\n  const lastPoint = points[points.length - 1];\r\n  const firstPoint = points[0];\r\n  return `${top} L${lastPoint[0]},${baseline} L${firstPoint[0]},${baseline} Z`;\r\n}\r\n\r\nexport function computeTicks(min: number, max: number, count: number = 5): number[] {\r\n  if (count < 2) return [min];\r\n  const ticks: number[] = [];\r\n  const step = (max - min) \/ (count - 1);\r\n  for (let i = 0; i < count; i++) {\r\n    ticks.push(min + step * i);\r\n  }\r\n  return ticks;\r\n}\r\n\r\nexport function niceNum(range: number, round: boolean): number {\r\n  const exponent = Math.floor(Math.log10(range));\r\n  const fraction = range \/ Math.pow(10, exponent);\r\n  let niceFraction: number;\r\n  if (round) {\r\n    if (fraction < 1.5) niceFraction = 1;\r\n    else if (fraction < 3) niceFraction = 2;\r\n    else if (fraction < 7) niceFraction = 5;\r\n    else niceFraction = 10;\r\n  } else {\r\n    if (fraction <= 1) niceFraction = 1;\r\n    else if (fraction <= 2) niceFraction = 2;\r\n    else if (fraction <= 5) niceFraction = 5;\r\n    else niceFraction = 10;\r\n  }\r\n  return niceFraction * Math.pow(10, exponent);\r\n}\r\n\r\nexport function niceScale(min: number, max: number, tickCount: number = 5): { min: number; max: number; ticks: number[] } {\r\n  if (min === max) {\r\n    return { min: min - 1, max: max + 1, ticks: computeTicks(min - 1, max + 1, tickCount) };\r\n  }\r\n  const range = niceNum(max - min, false);\r\n  const tickSpacing = niceNum(range \/ (tickCount - 1), true);\r\n  const niceMin = Math.floor(min \/ tickSpacing) * tickSpacing;\r\n  const niceMax = Math.ceil(max \/ tickSpacing) * tickSpacing;\r\n  const ticks: number[] = [];\r\n  for (let v = niceMin; v <= niceMax + tickSpacing * 0.5; v += tickSpacing) {\r\n    ticks.push(Math.round(v * 1e10) \/ 1e10);\r\n  }\r\n  return { min: niceMin, max: niceMax, ticks };\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/chart\/chart.utils.ts"},{"path":"components\/fancy\/chart\/index.ts","content":"export { Chart } from \".\/Chart\";\r\nexport type {\r\n  ChartBarProps,\r\n  ChartBarData,\r\n  ChartDonutProps,\r\n  ChartDonutData,\r\n  ChartSeries,\r\n  ChartCommonProps,\r\n  ChartLineProps,\r\n  ChartAreaProps,\r\n  ChartPieData,\r\n  ChartPieProps,\r\n  ChartSparklineProps,\r\n  ChartHorizontalBarProps,\r\n  ChartStackedBarProps,\r\n} from \".\/Chart.types\";\r\n","type":"registry:ui","target":"components\/fancy\/chart\/index.ts"}]}