{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "primitives",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Inline trend, one-shot draw.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useReducedMotion } from \"@/hooks/ledger/use-media-query\";\nimport { cn } from \"@/lib/ledger/cn\";\n\nexport interface SparklineProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  data: number[];\n  height?: number;\n  label?: string;\n  polarity?: \"standard\" | \"inverted\" | \"neutral\";\n  showEndpoint?: boolean;\n}\n\n/**\n * An impression, not a chart. No axes, no gridlines, no scrub — if a user\n * needs values they need `MoneyChart`.\n *\n * Draws once, on first paint or scroll-into-view. Never on data change.\n */\nexport const Sparkline = React.forwardRef<HTMLDivElement, SparklineProps>(\n  ({ className, data, height = 32, polarity = \"standard\", showEndpoint = true, label, ...props }, ref) => {\n    const drawRef = React.useRef<SVGGElement | null>(null);\n    const containerRef = React.useRef<HTMLDivElement | null>(null);\n    const reduced = useReducedMotion();\n    const drawn = React.useRef(false);\n\n    React.useEffect(() => {\n      const node = containerRef.current;\n      const group = drawRef.current;\n      if (!node || !group || reduced || drawn.current) return;\n      const observer = new IntersectionObserver((entries) => {\n        if (!entries[0]?.isIntersecting || drawn.current) return;\n        drawn.current = true;\n        group.style.animation = \"ledger-wipe 600ms var(--ease-out) forwards\";\n        observer.disconnect();\n      });\n      observer.observe(node);\n      return () => observer.disconnect();\n    }, [reduced]);\n\n    if (data.length < 2) return null;\n\n    const width = 120;\n    const min = Math.min(...data);\n    const max = Math.max(...data);\n    const span = max - min || 1;\n    const points = data.map((v, i) => ({\n      x: (i / (data.length - 1)) * width,\n      y: height - 2 - ((v - min) / span) * (height - 4),\n    }));\n    const d = points.map((p, i) => `${i === 0 ? \"M\" : \"L\"}${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(\" \");\n\n    const net = data[data.length - 1] - data[0];\n    const stroke =\n      polarity === \"neutral\"\n        ? \"var(--text-secondary)\"\n        : (polarity === \"standard\") === net >= 0\n          ? \"var(--positive)\"\n          : \"var(--negative)\";\n\n    const last = points[points.length - 1];\n\n    return (\n      <div\n        aria-label={label ?? `Trend, ${net >= 0 ? \"up\" : \"down\"} over ${data.length} points`}\n        className={cn(\"relative w-full\", className)}\n        ref={mergeRefs(ref, containerRef)}\n        role=\"img\"\n        {...props}\n      >\n        <svg\n          aria-hidden=\"true\"\n          className=\"w-full\"\n          preserveAspectRatio=\"none\"\n          style={{ height }}\n          viewBox={`0 0 ${width} ${height}`}\n        >\n          <g ref={drawRef}>\n            <path\n              d={d}\n              fill=\"none\"\n              stroke={stroke}\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={1.5}\n              vectorEffect=\"non-scaling-stroke\"\n            />\n            {showEndpoint ? (\n              <circle cx={last.x} cy={last.y} fill={stroke} r={2} vectorEffect=\"non-scaling-stroke\" />\n            ) : null}\n          </g>\n        </svg>\n      </div>\n    );\n  },\n);\nSparkline.displayName = \"Sparkline\";\n\nfunction mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]) {\n  return (node: T) => {\n    for (const ref of refs) {\n      if (typeof ref === \"function\") ref(node);\n      else if (ref) (ref as React.MutableRefObject<T | null>).current = node;\n    }\n  };\n}\n",
      "path": "components/ledger/sparkline.tsx",
      "target": "components/ledger/sparkline.tsx",
      "type": "registry:ui"
    }
  ],
  "name": "sparkline",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn",
    "@lopifi/use-media-query"
  ],
  "title": "Sparkline",
  "type": "registry:ui"
}
