{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "draw-chart",
  "title": "Draw Chart",
  "description": "One timeline: the line draws, points drop in and the headline number counts up.",
  "dependencies": [
    "animejs"
  ],
  "registryDependencies": [
    "https://spotcheckui.com/r/spotcheck-base.json",
    "https://spotcheckui.com/r/use-in-view.json",
    "https://spotcheckui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/spotcheck/components/spotcheck/draw-chart.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { createTimeline, stagger, svg, type Timeline } from \"animejs\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useInView } from \"@/registry/spotcheck/hooks/use-in-view\"\nimport { useReducedMotion } from \"@/registry/spotcheck/hooks/use-reduced-motion\"\n\nconst WIDTH = 520\nconst HEIGHT = 180\nconst LEFT = 24\nconst BOTTOM = 24\nconst TOP = 20\nconst POINT_SIZE = 7\nconst POINT_MS = 300\nconst POINT_STAGGER_MS = 55\nconst POINT_DROP = 8\nconst POINT_OVERLAP = \"-=1100\"\n\nexport function chartPoints(series: number[], max: number) {\n  return series.map((value, index) => [\n    LEFT + (index / Math.max(1, series.length - 1)) * (WIDTH - LEFT * 2),\n    HEIGHT - BOTTOM - (value / max) * (HEIGHT - BOTTOM - TOP),\n  ] as const)\n}\n\ntype DrawChartProps = {\n  series: number[]\n  /** Summarises the trend for screen readers, such as \"Accuracy rose from 38% to 83%\". */\n  label: string\n  /** The heading above the number. */\n  title?: string\n  /** The top of the scale. Defaults to the highest value. */\n  max?: number\n  /** Values for the horizontal guide lines. */\n  ticks?: number[]\n  /** Formats the headline number. Pass it from a client component. */\n  format?: (value: number) => string\n  /** How long the line takes to draw, in milliseconds. */\n  duration?: number\n  className?: string\n}\n\nexport function DrawChart({\n  series,\n  label,\n  title,\n  max = Math.max(...series),\n  ticks = [],\n  format = (value) => String(Math.round(value)),\n  duration = 1300,\n  className,\n}: DrawChartProps) {\n  const host = React.useRef<HTMLDivElement>(null)\n  const line = React.useRef<SVGPathElement>(null)\n  const dots = React.useRef<SVGGElement>(null)\n  const headline = React.useRef<HTMLDivElement>(null)\n  const inView = useInView(host)\n  const reduced = useReducedMotion()\n  const formatRef = React.useRef(format)\n  const last = series.at(-1) ?? 0\n  const points = chartPoints(series, max)\n  const path = points.map(([x, y], index) => `${index ? \"L\" : \"M\"}${x.toFixed(1)} ${y.toFixed(1)}`).join(\" \")\n\n  React.useEffect(() => {\n    formatRef.current = format\n  })\n\n  React.useEffect(() => {\n    if (!inView || reduced || !line.current || !dots.current) return\n    const number = headline.current\n    const counter = { value: 0 }\n    const drawable = svg.createDrawable(line.current)\n    const timeline: Timeline = createTimeline({ defaults: { ease: \"inOutQuad\" } })\n      .add(drawable, { draw: [\"0 0\", \"0 1\"], duration })\n      // translateY, because animating an SVG rect's `y` would overwrite its attribute.\n      .add(\n        dots.current.querySelectorAll(\"rect\"),\n        {\n          opacity: [0, 1],\n          translateY: [POINT_DROP, 0],\n          duration: POINT_MS,\n          delay: stagger(POINT_STAGGER_MS),\n          ease: \"outBack(2)\",\n        },\n        POINT_OVERLAP,\n      )\n      .add(\n        counter,\n        {\n          value: last,\n          duration,\n          ease: \"outExpo\",\n          onUpdate: () => {\n            if (number) number.textContent = formatRef.current(counter.value)\n          },\n        },\n        0,\n      )\n    return () => {\n      timeline.revert()\n      if (number) number.textContent = formatRef.current(last)\n    }\n  }, [duration, inView, last, reduced])\n\n  return (\n    <div ref={host} className={cn(\"flex flex-col gap-3.5 font-(family-name:--f-body)\", className)}>\n      <div>\n        {title && <div className=\"spotcheck-label\">{title}</div>}\n        <div ref={headline} data-testid=\"draw-chart-value\" className=\"spotcheck-display text-[64px] leading-[.9] tabular-nums\">\n          {format(last)}\n        </div>\n      </div>\n      <svg viewBox={`0 0 ${WIDTH} ${HEIGHT}`} role=\"img\" aria-label={label} className=\"block h-auto w-full\">\n        {ticks.map((tick) => {\n          const y = HEIGHT - BOTTOM - (tick / max) * (HEIGHT - BOTTOM - TOP)\n          return (\n            <g key={tick}>\n              <line x1={LEFT} x2={WIDTH - LEFT} y1={y} y2={y} stroke=\"var(--wash)\" strokeWidth=\"1\" />\n              <text x={LEFT - 6} y={y + 4} textAnchor=\"end\" fontSize=\"10\" fontFamily=\"var(--f-mono)\" fill=\"var(--mute)\">\n                {tick}\n              </text>\n            </g>\n          )\n        })}\n        <path ref={line} d={path} fill=\"none\" stroke=\"var(--accent-ink)\" strokeWidth=\"3\" strokeLinejoin=\"round\" />\n        <g ref={dots}>\n          {points.map(([x, y], index) => (\n            <rect\n              key={index}\n              x={x - POINT_SIZE / 2}\n              y={y - POINT_SIZE / 2}\n              width={POINT_SIZE}\n              height={POINT_SIZE}\n              fill=\"var(--ink)\"\n            />\n          ))}\n        </g>\n      </svg>\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "timelines"
  ],
  "type": "registry:component"
}