{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sparkline-table",
  "type": "registry:component",
  "title": "Sparkline Table",
  "description": "A dense endpoint table where every row carries its own 24-hour sparkline, a delta that knows lower is better, and a status; sort any column and the rows glide into order.",
  "categories": [
    "component",
    "Numbers"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/sparkline-table.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { ArrowDownIcon, ArrowUpIcon, ChevronsUpDownIcon } from \"lucide-react\";\nimport { AnimatePresence, LayoutGroup, motion, useReducedMotion } from \"motion/react\";\nimport { type KeyboardEvent, type PointerEvent, useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type RowStatus = \"healthy\" | \"slow\" | \"failing\";\n\nexport interface SparklineRow {\n  id: string;\n  name: string;\n  tag?: string;\n  series: number[];\n  previous: number;\n  status: RowStatus;\n}\n\nexport type SortKey = \"name\" | \"value\" | \"delta\" | \"status\";\n\nexport interface SparklineTableProps {\n  title?: string;\n  caption?: string;\n  rows?: SparklineRow[];\n  unit?: string;\n  lowerIsBetter?: boolean;\n  pointLabels?: string[];\n  defaultSort?: { key: SortKey; dir: \"asc\" | \"desc\" };\n  locale?: string;\n  className?: string;\n}\n\nconst STATUS: Record<RowStatus, { label: string; dot: string; text: string; stroke: string; rank: number }> = {\n  healthy: { label: \"Healthy\", dot: \"bg-success\", text: \"text-muted-foreground\", stroke: \"var(--color-muted-foreground)\", rank: 0 },\n  slow: { label: \"Slow\", dot: \"bg-warning\", text: \"text-warning-foreground\", stroke: \"var(--color-warning)\", rank: 1 },\n  failing: { label: \"Erroring\", dot: \"bg-destructive\", text: \"text-destructive-foreground\", stroke: \"var(--color-destructive)\", rank: 2 },\n};\n\nfunction mulberry(seed: number) {\n  return () => {\n    seed |= 0;\n    seed = (seed + 0x6d2b79f5) | 0;\n    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nfunction walk(seed: number, base: number, drift: number, spike?: number) {\n  const rand = mulberry(seed);\n  return Array.from({ length: 24 }, (_, i) => {\n    const daily = 1 + 0.18 * Math.sin(((i - 6) / 24) * Math.PI * 2);\n    const trend = 1 + (drift * i) / 23;\n    const bump = spike !== undefined && i >= spike ? 1.6 + (i - spike) * 0.12 : 1;\n    return Math.round(base * daily * trend * bump * (0.92 + rand() * 0.16));\n  });\n}\n\nconst defaultRows: SparklineRow[] = [\n  { id: \"orders\", tag: \"GET\", name: \"/v1/orders\", series: walk(3, 142, -0.08), previous: 112, status: \"healthy\" },\n  { id: \"checkout\", tag: \"POST\", name: \"/v1/checkout\", series: walk(5, 310, 0.05, 19), previous: 318, status: \"failing\" },\n  { id: \"search\", tag: \"GET\", name: \"/v1/search\", series: walk(8, 420, 0.32), previous: 401, status: \"slow\" },\n  { id: \"token\", tag: \"POST\", name: \"/v1/auth/token\", series: walk(13, 88, -0.02), previous: 74, status: \"healthy\" },\n  { id: \"catalog\", tag: \"GET\", name: \"/v1/catalog/items\", series: walk(21, 196, 0.02), previous: 188, status: \"healthy\" },\n  { id: \"webhooks\", tag: \"POST\", name: \"/v1/webhooks/deliver\", series: walk(34, 640, 0.18), previous: 702, status: \"slow\" },\n];\nconst defaultLabels = Array.from({ length: 24 }, (_, i) => `${String(i).padStart(2, \"0\")}:00`);\n\nfunction Sparkline({\n  row,\n  labels,\n  unit,\n  fmt,\n}: {\n  row: SparklineRow;\n  labels: string[];\n  unit: string;\n  fmt: Intl.NumberFormat;\n}) {\n  const uid = useId().replace(/:/g, \"\");\n  const reduce = useReducedMotion();\n  const [hover, setHover] = useState<number | null>(null);\n  const boxRef = useRef<HTMLDivElement>(null);\n  const [W, setW] = useState(88);\n  const H = 28;\n  useEffect(() => {\n    const parent = boxRef.current?.parentElement;\n    if (!parent) return;\n    const ro = new ResizeObserver(([entry]) => setW(Math.max(40, Math.round(entry.contentRect.width))));\n    ro.observe(parent);\n    return () => ro.disconnect();\n  }, []);\n  const s = row.series;\n  const min = Math.min(...s);\n  const max = Math.max(...s);\n  const x = (i: number) => (i / (s.length - 1)) * (W - 4) + 2;\n  const y = (v: number) => H - 3 - ((v - min) / (max - min || 1)) * (H - 6);\n  const line = s.map((v, i) => `${i ? \"L\" : \"M\"}${x(i).toFixed(1)} ${y(v).toFixed(1)}`).join(\" \");\n  const stroke = STATUS[row.status].stroke;\n  const at = hover ?? s.length - 1;\n\n  function onMove(e: PointerEvent<HTMLDivElement>) {\n    const r = e.currentTarget.getBoundingClientRect();\n    setHover(Math.max(0, Math.min(s.length - 1, Math.round(((e.clientX - r.left - 2) / (W - 4)) * (s.length - 1)))));\n  }\n  function onKey(e: KeyboardEvent<HTMLDivElement>) {\n    const cur = hover ?? s.length - 1;\n    const next = e.key === \"ArrowLeft\" ? cur - 1 : e.key === \"ArrowRight\" ? cur + 1 : e.key === \"Home\" ? 0 : e.key === \"End\" ? s.length - 1 : null;\n    if (e.key === \"Escape\") return setHover(null);\n    if (next === null) return;\n    e.preventDefault();\n    setHover(Math.max(0, Math.min(s.length - 1, next)));\n  }\n\n  return (\n    <div\n      aria-label={`${row.name} trend, ${fmt.format(min)} to ${fmt.format(max)} ${unit}. Arrow keys to inspect.`}\n      className=\"relative -my-1 cursor-crosshair rounded-md py-1 outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n      onBlur={() => setHover(null)}\n      onFocus={(e) => e.currentTarget.matches(\":focus-visible\") && setHover(s.length - 1)}\n      onKeyDown={onKey}\n      onPointerLeave={() => setHover(null)}\n      onPointerMove={onMove}\n      ref={boxRef}\n      role=\"img\"\n      style={{ width: W }}\n      tabIndex={0}\n    >\n      <svg aria-hidden className=\"block overflow-visible\" height={H} width={W}>\n        <defs>\n          <linearGradient id={`${uid}-a`} x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">\n            <stop offset=\"0%\" style={{ stopColor: stroke, stopOpacity: 0.18 }} />\n            <stop offset=\"100%\" style={{ stopColor: stroke, stopOpacity: 0 }} />\n          </linearGradient>\n        </defs>\n        <path d={`${line} L${x(s.length - 1)} ${H} L${x(0)} ${H} Z`} fill={`url(#${uid}-a)`} />\n        <motion.path\n          animate={{ pathLength: 1 }}\n          d={line}\n          fill=\"none\"\n          initial={{ pathLength: 0 }}\n          stroke={stroke}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          strokeWidth={1.5}\n          transition={{ duration: reduce ? 0 : 0.7, ease: [0.23, 1, 0.32, 1] }}\n        />\n        {hover !== null ? <line className=\"stroke-foreground/20\" x1={x(at)} x2={x(at)} y1={0} y2={H} /> : null}\n        <circle className=\"stroke-card\" cx={x(at)} cy={y(s[at])} fill={stroke} r={hover !== null ? 3 : 2.25} strokeWidth={1.5} />\n      </svg>\n      <AnimatePresence>\n        {hover !== null ? (\n          <motion.div\n            animate={{ opacity: 1 }}\n            className=\"pointer-events-none absolute bottom-full z-20 mb-1 -translate-x-1/2 whitespace-nowrap rounded-md border bg-popover px-2 py-1 text-popover-foreground text-xs shadow-md/5\"\n            exit={{ opacity: 0, transition: { duration: 0.08 } }}\n            initial={{ opacity: 0 }}\n            style={{ left: x(at) }}\n            transition={{ duration: reduce ? 0 : 0.12 }}\n          >\n            <span className=\"text-muted-foreground\">{labels[at] ?? `#${at + 1}`}</span>{\" \"}\n            <span className=\"font-medium tabular-nums\">\n              {fmt.format(s[at])}\n              {unit ? ` ${unit}` : \"\"}\n            </span>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  );\n}\n\nexport function SparklineTable({\n  title = \"Endpoints\",\n  caption = \"p95 latency, last 24 hours\",\n  rows = defaultRows,\n  unit = \"ms\",\n  lowerIsBetter = true,\n  pointLabels = defaultLabels,\n  defaultSort = { key: \"status\", dir: \"desc\" },\n  locale = \"en-US\",\n  className,\n}: SparklineTableProps) {\n  const reduce = useReducedMotion();\n  const [sort, setSort] = useState(defaultSort);\n  const fmt = useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }), [locale]);\n\n  const enriched = rows.map((r) => {\n    const value = r.series[r.series.length - 1] ?? 0;\n    return { ...r, value, delta: r.previous ? (value - r.previous) / r.previous : 0 };\n  });\n  const sorted = [...enriched].sort((a, b) => {\n    const sign = sort.dir === \"asc\" ? 1 : -1;\n    if (sort.key === \"name\") return a.name.localeCompare(b.name) * sign;\n    if (sort.key === \"status\") return (STATUS[a.status].rank - STATUS[b.status].rank || a.value - b.value) * sign;\n    return (a[sort.key] - b[sort.key]) * sign;\n  });\n  const counts = rows.reduce<Record<RowStatus, number>>(\n    (acc, r) => {\n      acc[r.status] += 1;\n      return acc;\n    },\n    { healthy: 0, slow: 0, failing: 0 },\n  );\n\n  function toggle(key: SortKey) {\n    setSort((s) => (s.key === key ? { key, dir: s.dir === \"asc\" ? \"desc\" : \"asc\" } : { key, dir: key === \"name\" ? \"asc\" : \"desc\" }));\n  }\n\n  const head = (key: SortKey | null, label: string, align: \"start\" | \"end\" = \"start\", extra?: string) => {\n    const on = key !== null && sort.key === key;\n    return (\n      <div\n        aria-sort={on ? (sort.dir === \"asc\" ? \"ascending\" : \"descending\") : key ? \"none\" : undefined}\n        className={cn(\"flex\", align === \"end\" ? \"justify-end\" : \"justify-start\", extra)}\n        role=\"columnheader\"\n      >\n        {key ? (\n          <button\n            className={cn(\n              \"-mx-1.5 inline-flex h-7 cursor-pointer items-center gap-1 rounded-md px-1.5 outline-none transition-colors duration-150 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\",\n              on && \"text-foreground\",\n            )}\n            onClick={() => toggle(key)}\n            type=\"button\"\n          >\n            {label}\n            {on ? (\n              sort.dir === \"asc\" ? (\n                <ArrowUpIcon className=\"size-3\" />\n              ) : (\n                <ArrowDownIcon className=\"size-3\" />\n              )\n            ) : (\n              <ChevronsUpDownIcon className=\"size-3 opacity-50\" />\n            )}\n          </button>\n        ) : (\n          <span className=\"inline-flex h-7 items-center\">{label}</span>\n        )}\n      </div>\n    );\n  };\n\n  const cols =\n    \"grid grid-cols-[minmax(0,1fr)_52px_60px] items-center gap-x-2.5 @min-[30rem]:grid-cols-[minmax(0,1fr)_88px_64px_64px_80px] @min-[30rem]:gap-x-3\";\n\n  return (\n    <section\n      aria-label={title}\n      className={cn(\"@container w-full max-w-[600px] rounded-2xl border bg-card p-5 text-card-foreground shadow-xs/5\", className)}\n    >\n      <header className=\"flex flex-wrap items-start justify-between gap-x-3 gap-y-2\">\n        <div className=\"flex min-w-0 flex-col gap-0.5\">\n          <h3 className=\"font-medium text-sm\">{title}</h3>\n          <p className=\"text-muted-foreground text-xs\">{caption}</p>\n        </div>\n        <div className=\"flex items-center gap-3 text-muted-foreground text-xs tabular-nums\">\n          {([\"failing\", \"slow\", \"healthy\"] as RowStatus[]).map((s) =>\n            counts[s] ? (\n              <span className=\"inline-flex items-center gap-1.5\" key={s}>\n                <span className={cn(\"size-1.5 rounded-full\", STATUS[s].dot)} />\n                {counts[s]} {STATUS[s].label.toLowerCase()}\n              </span>\n            ) : null,\n          )}\n        </div>\n      </header>\n\n      <div className=\"mt-4 text-xs\" role=\"table\">\n        <div className={cn(cols, \"mb-1 border-b pb-1.5 text-muted-foreground\")} role=\"row\">\n          {head(\"name\", \"Endpoint\")}\n          {head(null, \"Trend\")}\n          {head(\"value\", \"p95\", \"end\")}\n          {head(\"delta\", \"Δ 24h\", \"end\", \"hidden @min-[30rem]:flex\")}\n          {head(\"status\", \"Status\", \"start\", \"hidden @min-[30rem]:flex\")}\n        </div>\n        <LayoutGroup>\n          <div role=\"rowgroup\">\n            {sorted.length === 0 ? (\n              <p className=\"py-6 text-center text-muted-foreground\">Nothing reported in this window yet.</p>\n            ) : null}\n            {sorted.map((r) => {\n              const good = lowerIsBetter ? r.delta < 0 : r.delta > 0;\n              const flat = Math.abs(r.delta) < 0.005;\n              const deltaTone = flat ? \"text-muted-foreground\" : good ? \"text-success-foreground\" : \"text-destructive-foreground\";\n              const delta = (\n                <span className=\"inline-flex items-center gap-0.5\">\n                  {flat ? null : r.delta > 0 ? <ArrowUpIcon className=\"size-3\" /> : <ArrowDownIcon className=\"size-3\" />}\n                  {Math.abs(r.delta * 100).toFixed(1)}%\n                </span>\n              );\n              return (\n                <motion.div\n                  className={cn(cols, \"relative -mx-2 h-11 rounded-lg px-2 transition-colors duration-150 hover:bg-accent/60\")}\n                  key={r.id}\n                  layout=\"position\"\n                  role=\"row\"\n                  transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 500, damping: 40 }}\n                >\n                  <div className=\"flex min-w-0 items-center gap-2\" role=\"cell\">\n                    <span\n                      aria-hidden\n                      className={cn(\"size-1.5 shrink-0 rounded-full @min-[30rem]:hidden\", STATUS[r.status].dot)}\n                    />\n                    {r.tag ? (\n                      <span className=\"hidden w-9 shrink-0 font-medium font-mono text-[10px] text-muted-foreground @min-[30rem]:inline\">\n                        {r.tag}\n                      </span>\n                    ) : null}\n                    <span className=\"truncate font-mono text-foreground\">{r.name}</span>\n                  </div>\n                  <div role=\"cell\">\n                    <Sparkline fmt={fmt} labels={pointLabels} row={r} unit={unit} />\n                  </div>\n                  <div className=\"flex flex-col items-end text-right tabular-nums\" role=\"cell\">\n                    <span className=\"font-medium text-foreground\">\n                      {fmt.format(r.value)}\n                      <span className=\"ms-0.5 font-normal text-muted-foreground\">{unit}</span>\n                    </span>\n                    <span className={cn(\"@min-[30rem]:hidden\", deltaTone)}>{delta}</span>\n                  </div>\n                  <div className={cn(\"hidden justify-end tabular-nums @min-[30rem]:flex\", deltaTone)} role=\"cell\">\n                    {delta}\n                  </div>\n                  <div className={cn(\"hidden items-center gap-1.5 @min-[30rem]:flex\", STATUS[r.status].text)} role=\"cell\">\n                    <span className={cn(\"size-1.5 rounded-full\", STATUS[r.status].dot)} />\n                    {STATUS[r.status].label}\n                  </div>\n                </motion.div>\n              );\n            })}\n          </div>\n        </LayoutGroup>\n      </div>\n    </section>\n  );\n}\n"
    }
  ]
}