{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "points-chart",
  "title": "Points Chart",
  "description": "React chart for points over time. Displays trends, campaigns, and seasonality in gamification analytics views. Complements badges and levels. Built on Recharts + shadcn/ui + Tailwind. Open source.",
  "dependencies": [
    "recharts"
  ],
  "files": [
    {
      "path": "registry/trophy/ui/points-chart.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Star } from \"lucide-react\"\nimport {\n  CartesianGrid,\n  Line,\n  LineChart,\n  ReferenceLine,\n  ResponsiveContainer,\n  Tooltip,\n  XAxis,\n  YAxis,\n} from \"recharts\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PointsChartDataPoint {\n  date: string\n  total: number\n  change: number\n}\n\ninterface PointsChartLevel {\n  value: number\n  color: string\n}\n\ninterface PointsChartProps extends React.HTMLAttributes<HTMLDivElement> {\n  data: PointsChartDataPoint[]\n  height?: number\n  title?: string\n  headerRight?: React.ReactNode\n  yAxisLabel?: string\n  levels?: PointsChartLevel[]\n}\n\nfunction formatValue(value: number) {\n  return Math.round(value).toLocaleString()\n}\n\nfunction LevelReferenceStarLabel({\n  viewBox,\n  color,\n}: {\n  viewBox?: { x?: number; y?: number } | null\n  color: string\n}) {\n  const x = viewBox?.x\n  const y = viewBox?.y\n\n  if (typeof x !== \"number\" || typeof y !== \"number\") {\n    return null\n  }\n\n  return (\n    <g transform={`translate(${x - 14},${y})`}>\n      <Star\n        x={-5}\n        y={-5}\n        width={10}\n        height={10}\n        fill={color}\n        stroke={color}\n        strokeWidth={1.75}\n      />\n    </g>\n  )\n}\n\nfunction PointsChart({\n  data,\n  height = 260,\n  title = \"Your points\",\n  headerRight,\n  yAxisLabel,\n  levels,\n  className,\n  ...props\n}: PointsChartProps) {\n  const yDomain = React.useMemo<[number, number]>(() => {\n    const values = [\n      ...data.map((item) => item.total),\n      ...(levels?.map((level) => level.value) ?? []),\n    ]\n\n    if (values.length === 0) return [0, 100]\n\n    const minValue = Math.min(...values)\n    const maxValue = Math.max(...values)\n    const range = maxValue - minValue\n\n    if (range === 0) {\n      const padding = Math.max(maxValue * 0.15, 10)\n      return [Math.max(0, minValue - padding), maxValue + padding]\n    }\n\n    const padding = Math.max(range * 0.12, 10)\n    return [Math.max(0, minValue - padding), maxValue + padding]\n  }, [data, levels])\n\n  return (\n    <div className={cn(\"bg-card rounded-2xl border p-4\", className)} {...props}>\n      <div className=\"mb-3 flex items-center justify-between gap-3\">\n        <p className=\"text-md text-foreground font-semibold\">{title}</p>\n        {headerRight ? <div className=\"shrink-0\">{headerRight}</div> : null}\n      </div>\n      <div style={{ height }}>\n        <ResponsiveContainer width=\"100%\" height=\"100%\">\n          <LineChart\n            data={data}\n            margin={{ top: 12, right: 12, left: 0, bottom: 4 }}\n          >\n            <CartesianGrid stroke=\"var(--border)\" strokeDasharray=\"3 3\" />\n            <XAxis\n              dataKey=\"date\"\n              tickLine={false}\n              axisLine={false}\n              tick={{ fill: \"var(--muted-foreground)\", fontSize: 12 }}\n            />\n            <YAxis\n              tickLine={false}\n              axisLine={false}\n              domain={yDomain}\n              tick={{ fill: \"var(--muted-foreground)\", fontSize: 12 }}\n              tickFormatter={formatValue}\n              width={64}\n              label={\n                yAxisLabel\n                  ? {\n                      value: yAxisLabel,\n                      angle: -90,\n                      position: \"insideLeft\",\n                      fill: \"var(--muted-foreground)\",\n                      fontSize: 12,\n                      dx: -18,\n                    }\n                  : undefined\n              }\n            />\n            {levels?.map((level) => (\n              <ReferenceLine\n                key={level.value}\n                y={level.value}\n                stroke={level.color}\n                strokeDasharray=\"6 6\"\n                strokeWidth={2}\n                label={{\n                  position: \"left\",\n                  content: (labelProps: { viewBox?: unknown }) => (\n                    <LevelReferenceStarLabel\n                      viewBox={\n                        (labelProps.viewBox as {\n                          x?: number\n                          y?: number\n                        } | null) ?? null\n                      }\n                      color={level.color}\n                    />\n                  ),\n                }}\n              />\n            ))}\n            <Tooltip\n              cursor={{ stroke: \"var(--primary)\", strokeDasharray: \"4 4\" }}\n              content={({ active, payload, label }) => {\n                if (!active || !payload?.length) return null\n                const row = payload[0].payload as PointsChartDataPoint\n                const changePrefix = row.change > 0 ? \"+\" : \"\"\n                return (\n                  <div\n                    className=\"bg-popover text-popover-foreground rounded-lg border px-3 py-2 text-sm shadow-md\"\n                    style={{\n                      borderColor: \"var(--border)\",\n                    }}\n                  >\n                    <p className=\"text-muted-foreground mb-1\">{label}</p>\n                    <p className=\"font-medium tabular-nums\">\n                      Total {formatValue(row.total)}\n                    </p>\n                    <p className=\"text-muted-foreground text-xs tabular-nums\">\n                      {changePrefix}\n                      {formatValue(row.change)}\n                    </p>\n                  </div>\n                )\n              }}\n            />\n            <Line\n              type=\"monotone\"\n              dataKey=\"total\"\n              stroke=\"var(--primary)\"\n              strokeWidth={2}\n              connectNulls\n              dot={{ r: 3, fill: \"var(--primary)\" }}\n              activeDot={{ r: 6 }}\n            />\n          </LineChart>\n        </ResponsiveContainer>\n      </div>\n    </div>\n  )\n}\n\nexport { PointsChart }\nexport type { PointsChartDataPoint, PointsChartProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}