{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table-of-contents-panel",
  "type": "registry:component",
  "title": "Table Of Contents Panel",
  "description": "Side panel rendering a table of contents for page navigation.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/table-of-contents-panel/table-of-contents-panel.tsx",
      "content": "\"use client\";\n\nimport { memo, useEffect, useRef } from \"react\";\n\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\nexport type TOCSection = {\n  id: string;\n  title: string;\n};\n\nexport type TableOfContentsPanelProps = {\n  className?: string;\n  closeIcon?: ReactNode;\n  completedSections: Set<string>;\n  completionCount: number;\n  currentSectionIndex: number;\n  isOpen: boolean;\n  onClose: () => void;\n  onReset?: () => void;\n  onSelectSection: (index: number) => void;\n  progressLabel?: string;\n  resetLabel?: string;\n  sections: TOCSection[];\n  title?: string;\n  totalSections: number;\n};\n\n// eslint-disable-next-line max-lines-per-function -- Complex panel with progress and section list\nfunction TableOfContentsPanelImpl({\n  className,\n  closeIcon,\n  completedSections,\n  completionCount,\n  currentSectionIndex,\n  isOpen,\n  onClose,\n  onReset,\n  onSelectSection,\n  progressLabel = \"Progress\",\n  resetLabel = \"Reset Progress\",\n  sections,\n  title = \"Table of Contents\",\n  totalSections,\n}: TableOfContentsPanelProps): React.ReactNode {\n  const panelRef = useRef<HTMLDivElement>(null);\n  const closeButtonRef = useRef<HTMLButtonElement>(null);\n\n  // Focus trap and close on Escape\n  useEffect(() => {\n    if (!isOpen) return;\n\n    closeButtonRef.current?.focus();\n\n    const handleKeyDown = (event: KeyboardEvent): void => {\n      if (event.key === \"Escape\") {\n        event.preventDefault();\n        onClose();\n      }\n    };\n\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => {\n      window.removeEventListener(\"keydown\", handleKeyDown);\n    };\n  }, [isOpen, onClose]);\n\n  // Prevent body scroll when open\n  useEffect(() => {\n    document.body.style.overflow = isOpen ? \"hidden\" : \"\";\n    return () => {\n      document.body.style.overflow = \"\";\n    };\n  }, [isOpen]);\n\n  if (!isOpen) return null;\n\n  const completionPercent =\n    totalSections > 0 ? Math.round((completionCount / totalSections) * 100) : 0;\n\n  return (\n    <div\n      aria-labelledby=\"toc-title\"\n      aria-modal=\"true\"\n      className=\"fixed inset-0 z-50\"\n      role=\"dialog\"\n    >\n      {/* Backdrop */}\n      <div\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 bg-black/50 backdrop-blur-sm\"\n        onClick={onClose}\n      />\n\n      {/* Panel */}\n      <div\n        className={cn(\n          \"absolute right-0 top-0 h-full w-full max-w-md bg-background shadow-xl\",\n          className,\n        )}\n        ref={panelRef}\n      >\n        <div className=\"flex h-full flex-col\">\n          {/* Header */}\n          <div className=\"flex items-center justify-between border-b border-border px-4 py-3\">\n            <h2 className=\"text-lg font-semibold\" id=\"toc-title\">\n              {title}\n            </h2>\n            <button\n              aria-label=\"Close table of contents\"\n              className=\"flex size-8 items-center justify-center rounded-md hover:bg-muted\"\n              onClick={onClose}\n              ref={closeButtonRef}\n              type=\"button\"\n            >\n              {closeIcon ?? (\n                <svg\n                  className=\"size-5\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  viewBox=\"0 0 24 24\"\n                >\n                  <path\n                    d=\"M6 18L18 6M6 6l12 12\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    strokeWidth={2}\n                  />\n                </svg>\n              )}\n            </button>\n          </div>\n\n          {/* Progress */}\n          <div className=\"border-b border-border px-4 py-3\">\n            <div className=\"flex items-center justify-between text-sm\">\n              <span className=\"text-muted-foreground\">{progressLabel}</span>\n              <span className=\"font-medium\">\n                {completionCount} / {totalSections} ({completionPercent}%)\n              </span>\n            </div>\n            <div className=\"mt-2 h-2 rounded-full bg-muted\">\n              <div\n                className=\"h-full rounded-full bg-primary transition-all duration-300\"\n                style={{ width: `${completionPercent}%` }}\n              />\n            </div>\n          </div>\n\n          {/* Sections List */}\n          <nav\n            aria-label=\"Sections\"\n            className=\"flex-1 overflow-y-auto px-4 py-3\"\n          >\n            <ol className=\"space-y-1\">\n              {sections.map((section, index) => {\n                const isCompleted = completedSections.has(section.id);\n                const isCurrent = index === currentSectionIndex;\n\n                return (\n                  <li key={section.id}>\n                    <button\n                      className={cn(\n                        \"flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm transition-colors\",\n                        isCurrent\n                          ? \"bg-primary/10 text-primary font-medium\"\n                          : \"hover:bg-muted text-foreground\",\n                      )}\n                      onClick={() => {\n                        onSelectSection(index);\n                        onClose();\n                      }}\n                      type=\"button\"\n                    >\n                      <span\n                        className={cn(\n                          \"flex size-5 shrink-0 items-center justify-center rounded-full border\",\n                          isCompleted\n                            ? \"border-primary bg-primary text-primary-foreground\"\n                            : \"border-muted-foreground\",\n                        )}\n                      >\n                        {isCompleted ? (\n                          <svg\n                            className=\"size-3\"\n                            fill=\"none\"\n                            stroke=\"currentColor\"\n                            viewBox=\"0 0 24 24\"\n                          >\n                            <path\n                              d=\"M5 13l4 4L19 7\"\n                              strokeLinecap=\"round\"\n                              strokeLinejoin=\"round\"\n                              strokeWidth={2}\n                            />\n                          </svg>\n                        ) : (\n                          <span className=\"text-xs\">{index + 1}</span>\n                        )}\n                      </span>\n                      <span\n                        className={isCompleted ? \"line-through opacity-60\" : \"\"}\n                      >\n                        {section.title}\n                      </span>\n                    </button>\n                  </li>\n                );\n              })}\n            </ol>\n          </nav>\n\n          {/* Footer */}\n          {completionCount > 0 && onReset ? (\n            <div className=\"border-t border-border px-4 py-3\">\n              <button\n                className=\"w-full rounded-md border border-border px-3 py-2 text-sm text-muted-foreground hover:bg-muted hover:text-foreground transition-colors\"\n                onClick={onReset}\n                type=\"button\"\n              >\n                {resetLabel}\n              </button>\n            </div>\n          ) : null}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const TableOfContentsPanel = memo(TableOfContentsPanelImpl);\nTableOfContentsPanel.displayName = \"TableOfContentsPanel\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
