{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"sparkline","type":"registry:ui","title":"Sparkline","description":"A tiny line, area or bar chart without axes, for a trend next to a number or in a table cell.","dependencies":[],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/lib-locale.json"],"files":[{"path":"registry/dashral/sparkline.tsx","type":"registry:file","target":"components/dashral/sparkline.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\n\nimport { useLocale } from \"./lib/locale\"\nimport { cn } from \"./lib/utils\"\n\ntype SparklineProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** The values, oldest first. */\n  data: number[]\n  /** A line, a line with a soft fill under it, or bars. */\n  variant?: \"line\" | \"area\" | \"bar\"\n  /** Mark the latest value with a dot (line and area). */\n  showLast?: boolean\n  /** Lowest and highest values of the scale; by default the data's own. Set min={0} to start the scale at zero. */\n  min?: number\n  max?: number\n  /** What the trend shows, read by screen readers, e.g. \"Revenue, last 30 days: rising from $1.2K to $2.4K\". */\n  \"aria-label\"?: string\n}\n\nconst W = 100\nconst H = 100\n\n/**\n * A tiny chart without axes, for a trend next to a number (in a MetricCard, a table cell). It fills its box: size it\n * with className (\"h-8 w-24\"), color it with a text color (\"text-success\"). Draws itself in when it first shows.\n */\nfunction Sparkline({ data, variant = \"line\", showLast = false, min, max, className, \"aria-label\": ariaLabel, ...props }: SparklineProps) {\n  const { t } = useLocale()\n  const [drawn, setDrawn] = React.useState(false)\n  React.useEffect(() => setDrawn(true), [])\n\n  const low = min ?? Math.min(...data)\n  const high = max ?? Math.max(...data)\n  const span = high - low || 1\n  const x = (i: number) => (data.length > 1 ? (i / (data.length - 1)) * W : W / 2)\n  const y = (v: number) => H - ((v - low) / span) * H\n  const label = ariaLabel ?? (data.length ? t(\"trend\", { from: data[0], to: data.at(-1) }) : t(\"noData\"))\n\n  const line = data.map((v, i) => `${i ? \"L\" : \"M\"}${x(i).toFixed(2)},${y(v).toFixed(2)}`).join(\" \")\n  const last = data.length ? { left: (x(data.length - 1) / W) * 100, top: (y(data.at(-1)!) / H) * 100 } : null\n\n  return (\n    <div data-slot=\"sparkline\" role=\"img\" aria-label={label} className={cn(\"relative h-8 w-full text-primary\", className)} {...props}>\n      {/* Lines and areas are revealed left to right (a clip that opens), which works however the chart is stretched. */}\n      <svg\n        viewBox={`0 0 ${W} ${H}`}\n        preserveAspectRatio=\"none\"\n        className={cn(\n          \"size-full overflow-visible\",\n          variant !== \"bar\" && \"transition-[clip-path] duration-(--duration-considered) ease-enter motion-reduce:transition-none\"\n        )}\n        style={variant !== \"bar\" ? { clipPath: drawn ? \"inset(-4px -4px -4px -4px)\" : \"inset(-4px 100% -4px -4px)\" } : undefined}\n        aria-hidden\n      >\n        {variant === \"bar\"\n          ? data.map((v, i) => {\n              const width = W / data.length\n              const base = y(Math.max(low, Math.min(0, high)))\n              const top = y(v)\n              return (\n                <rect\n                  key={i}\n                  x={i * width + width * 0.15}\n                  width={width * 0.7}\n                  y={Math.min(top, base)}\n                  height={Math.max(Math.abs(base - top), 1)}\n                  rx={1}\n                  fill=\"currentColor\"\n                  className=\"origin-bottom transition-transform duration-(--duration-considered) ease-spring motion-reduce:transition-none\"\n                  style={{ transform: drawn ? undefined : \"scaleY(0)\", transformBox: \"fill-box\", transitionDelay: `${i * 15}ms` }}\n                />\n              )\n            })\n          : data.length > 1 && (\n              <>\n                {variant === \"area\" && (\n                  <path d={`${line} L${W},${H} L0,${H} Z`} fill=\"currentColor\" opacity={0.14} />\n                )}\n                <path\n                  d={line}\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  strokeWidth={2}\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  vectorEffect=\"non-scaling-stroke\"\n                />\n              </>\n            )}\n      </svg>\n      {/* The dot is HTML, so it stays round however the chart is stretched. */}\n      {showLast && last && variant !== \"bar\" && (\n        <span\n          aria-hidden\n          className=\"absolute size-2 -translate-1/2 rounded-full bg-current ring-2 ring-card transition-[opacity,scale] delay-(--duration-considered) duration-(--duration-standard) ease-spring motion-reduce:delay-0 motion-reduce:transition-none\"\n          // It pops in once the line has reached it.\n          style={{ left: `${last.left}%`, top: `${last.top}%`, opacity: drawn ? 1 : 0, scale: drawn ? \"1\" : \"0.4\" }}\n        />\n      )}\n    </div>\n  )\n}\n\nexport { Sparkline }\n"}],"cssVars":{"theme":{"color-success":"var(--success)","color-success-foreground":"var(--success-foreground)","color-success-text":"var(--success-text)","ease-standard":"var(--ease-standard)","ease-enter":"var(--ease-enter)","ease-exit":"var(--ease-exit)","ease-spring":"var(--ease-spring)"},"light":{"success":"oklch(0.508 0.118 165.612)","success-foreground":"oklch(0.985 0 0)","success-text":"oklch(0.432 0.095 166.913)","duration-fast":"160ms","duration-standard":"240ms","duration-considered":"480ms","ease-standard":"cubic-bezier(0.2, 0, 0, 1)","ease-enter":"cubic-bezier(0, 0, 0, 1)","ease-exit":"cubic-bezier(0.3, 0, 1, 1)","ease-spring":"linear(0, 0.071 4%, 0.234 8%, 0.433 12%, 0.626 17%, 0.791 21%, 0.917 25%, 1.004 29%, 1.055 33%, 1.079 38%, 1.083 42%, 1.075 46%, 1.06 50%, 1.043 54%, 1.027 58%, 1.015 62%, 1.005 67%, 0.998 71%, 0.995 75%, 0.993 79%, 0.993 83%, 0.994 88%, 0.995 92%, 0.997 96%, 1)"},"dark":{"success":"oklch(0.696 0.17 162.48)","success-foreground":"oklch(0.2 0.04 160)","success-text":"oklch(0.765 0.177 163.223)"}}}