{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "help-center-toc",
  "title": "Help Center Table of Contents",
  "description": "Scroll-spy table of contents with sliding active indicator. Clerk-style design.",
  "files": [
    {
      "path": "registry/helpbase/components/toc.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport type { TocItem } from \"@/lib/types\"\n\ninterface TableOfContentsProps {\n  items: TocItem[]\n}\n\nexport function TableOfContents({ items }: TableOfContentsProps) {\n  const [activeId, setActiveId] = useState<string>(\"\")\n  const indicatorRef = useRef<HTMLDivElement>(null)\n  const navRef = useRef<HTMLElement>(null)\n\n  // Determine which heading is active based on scroll position.\n  // Uses a \"last heading above the fold\" approach instead of\n  // IntersectionObserver, which fixes both problems:\n  //   1. Works on initial load (no scroll event needed)\n  //   2. Works at page bottom (last heading wins when no heading is below)\n  const updateActiveHeading = useCallback(() => {\n    const headings = items\n      .map((item) => ({\n        id: item.id,\n        el: document.getElementById(item.id),\n      }))\n      .filter((h) => h.el != null) as { id: string; el: HTMLElement }[]\n\n    if (headings.length === 0) return\n\n    // If scrolled to the bottom, activate the last heading\n    const atBottom =\n      window.innerHeight + window.scrollY >=\n      document.documentElement.scrollHeight - 50\n\n    if (atBottom) {\n      setActiveId(headings[headings.length - 1]!.id)\n      return\n    }\n\n    // Otherwise find the last heading that has scrolled past the top threshold\n    const scrollY = window.scrollY + 100\n    let active = headings[0]!.id\n\n    for (const heading of headings) {\n      if (heading.el.offsetTop <= scrollY) {\n        active = heading.id\n      } else {\n        break\n      }\n    }\n\n    setActiveId(active)\n  }, [items])\n\n  useEffect(() => {\n    // Set initial active heading on mount\n    updateActiveHeading()\n\n    window.addEventListener(\"scroll\", updateActiveHeading, { passive: true })\n    return () => window.removeEventListener(\"scroll\", updateActiveHeading)\n  }, [updateActiveHeading])\n\n  // Move the indicator bar using transform (GPU-accelerated)\n  useEffect(() => {\n    if (!activeId || !navRef.current || !indicatorRef.current) return\n\n    const activeLink = navRef.current.querySelector(\n      `[data-toc-id=\"${activeId}\"]`\n    ) as HTMLElement | null\n\n    if (activeLink) {\n      const navRect = navRef.current.getBoundingClientRect()\n      const linkRect = activeLink.getBoundingClientRect()\n      const y = linkRect.top - navRect.top\n      indicatorRef.current.style.transform = `translateY(${y}px)`\n      indicatorRef.current.style.height = `${linkRect.height}px`\n      indicatorRef.current.style.opacity = \"1\"\n    }\n  }, [activeId])\n\n  return (\n    <nav ref={navRef} className=\"relative\">\n      {/* Track line */}\n      <div className=\"absolute left-0 top-0 h-full w-px bg-border\" />\n\n      {/* Active indicator — slides along the track */}\n      <div\n        ref={indicatorRef}\n        className=\"toc-indicator absolute left-0 top-0 w-px bg-foreground opacity-0\"\n      />\n\n      {/* Links */}\n      <div className=\"space-y-0.5\">\n        {items.map((item) => {\n          const isActive = item.id === activeId\n          return (\n            <a\n              key={item.id}\n              href={`#${item.id}`}\n              data-toc-id={item.id}\n              className={cn(\n                \"block border-l border-transparent py-1 pl-3 text-[13px] leading-snug transition-colors duration-150\",\n                isActive\n                  ? \"font-medium text-foreground\"\n                  : \"text-muted-foreground hover:text-foreground\",\n                item.depth === 3 && \"pl-6\"\n              )}\n            >\n              {item.text}\n            </a>\n          )\n        })}\n      </div>\n    </nav>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/toc.tsx"
    }
  ],
  "type": "registry:component"
}