{
  "name": "scroll-sidebar",
  "type": "registry:component",
  "description": "Sidebar layout with synced navigation and content",
  "files": [
    {
      "path": "ui/ScrollSidebar.astro",
      "type": "registry:component",
      "content": "---\nexport interface Props {\n  class?: string;\n  gap?: string;\n}\n\nconst { class: className = '', gap = '2rem' } = Astro.props;\n---\n\n<div\n  class={`scroll-sidebar-root flex flex-row ${className}`}\n  data-scroll-sidebar\n  style={`gap: ${gap};`}\n>\n  <slot />\n</div>\n\n<style>\n  .scroll-sidebar-root {\n    position: relative;\n  }\n</style>\n\n<script>\n  function initScrollSidebar() {\n    const containers = document.querySelectorAll(\"[data-scroll-sidebar]\");\n\n    containers.forEach((container) => {\n      if (!(container instanceof HTMLElement)) return;\n\n      const navItems = container.querySelectorAll(\n        \"[data-scroll-sidebar-nav-item]\",\n      );\n      const sections = container.querySelectorAll(\n        \"[data-scroll-sidebar-section]\",\n      );\n\n      // Function to update active state\n      const updateActiveState = (activeId: string) => {\n        navItems.forEach((item) => {\n          const itemId = item.getAttribute(\"data-scroll-sidebar-nav-item\");\n          if (itemId === activeId) {\n            item.setAttribute(\"data-state\", \"active\");\n          } else {\n            item.setAttribute(\"data-state\", \"inactive\");\n          }\n        });\n      };\n\n      // Click handler for manual navigation\n      navItems.forEach((item) => {\n        item.addEventListener(\"click\", () => {\n          const itemId = item.getAttribute(\"data-scroll-sidebar-nav-item\");\n          if (!itemId) return;\n\n          // Find corresponding section and scroll to it\n          const targetSection = container.querySelector(\n            `[data-scroll-sidebar-section=\"${itemId}\"]`,\n          );\n          if (targetSection) {\n            targetSection.scrollIntoView({\n              behavior: \"smooth\",\n              block: \"center\",\n            });\n          }\n        });\n      });\n\n      // Intersection Observer for scroll-based activation\n      const observerOptions = {\n        root: null,\n        rootMargin: \"-40% 0px -40% 0px\", // Activate when section is in middle 20% of viewport\n        threshold: 0,\n      };\n\n      const observer = new IntersectionObserver((entries) => {\n        entries.forEach((entry) => {\n          if (entry.isIntersecting) {\n            const sectionElement = entry.target;\n            const sectionId = sectionElement.getAttribute(\n              \"data-scroll-sidebar-section\",\n            );\n            if (sectionId) {\n              updateActiveState(sectionId);\n            }\n          }\n        });\n      }, observerOptions);\n\n      // Observe all sections\n      sections.forEach((section) => {\n        observer.observe(section);\n      });\n    });\n  }\n\n  // Initialize on load\n  if (document.readyState === \"loading\") {\n    document.addEventListener(\"DOMContentLoaded\", initScrollSidebar);\n  } else {\n    initScrollSidebar();\n  }\n\n  // Re-initialize after navigation (for view transitions)\n  document.addEventListener(\"astro:after-swap\", initScrollSidebar);\n</script>\n"
    },
    {
      "path": "ui/ScrollSidebarContent.astro",
      "type": "registry:component",
      "content": "---\nexport interface Props {\n  class?: string;\n  gap?: string;\n}\n\nconst { class: className = '', gap = '8rem' } = Astro.props;\n---\n\n<div class={`scroll-sidebar-content flex-1 ${className}`}>\n  <div class=\"flex flex-col\" style={`gap: ${gap};`}>\n    <slot />\n  </div>\n</div>\n\n<style>\n  .scroll-sidebar-content {\n    position: relative;\n  }\n</style>\n"
    },
    {
      "path": "ui/ScrollSidebarNav.astro",
      "type": "registry:component",
      "content": "---\nexport interface Props {\n  class?: string;\n  stickyTop?: string;\n  minWidth?: string;\n  maxWidth?: string;\n}\n\nconst {\n  class: className = '',\n  stickyTop = '6rem',\n  minWidth = '320px',\n  maxWidth = '320px',\n} = Astro.props;\n---\n\n<div\n  class={`scroll-sidebar-nav sticky flex-shrink-0 ${className}`}\n  style={`top: ${stickyTop}; min-width: min(100%, ${minWidth}); max-width: min(100%, ${maxWidth}); height: fit-content;`}\n>\n  <div class=\"flex flex-col\">\n    <slot />\n  </div>\n</div>\n\n"
    },
    {
      "path": "ui/ScrollSidebarNavItem.astro",
      "type": "registry:component",
      "content": "---\nexport interface Props {\n  id: string;\n  class?: string;\n  defaultOpen?: boolean;\n}\n\nconst { id, class: className = '', defaultOpen = false } = Astro.props;\n---\n\n<button\n  type=\"button\"\n  class={`scroll-sidebar-nav-item relative flex flex-col py-4 pr-6 text-left transition-opacity duration-300 hover:opacity-80 ${className}`}\n  data-scroll-sidebar-nav-item={id}\n  data-state={defaultOpen ? \"active\" : \"inactive\"}\n>\n  <div class=\"scroll-sidebar-nav-item-header\">\n    <slot name=\"header\" />\n  </div>\n\n  <div\n    class=\"scroll-sidebar-nav-item-content grid grid-rows-[0fr] overflow-hidden transition-[grid-template-rows] duration-300 ease-in-out\"\n  >\n    <div class=\"min-h-0\">\n      <slot name=\"content\" />\n    </div>\n  </div>\n</button>\n\n<style>\n  .scroll-sidebar-nav-item[data-state=\"active\"] {\n    opacity: 1;\n  }\n\n  .scroll-sidebar-nav-item[data-state=\"inactive\"] {\n    opacity: 0.5;\n  }\n\n  .scroll-sidebar-nav-item[data-state=\"active\"]\n    .scroll-sidebar-nav-item-content {\n    grid-template-rows: 1fr;\n  }\n</style>\n"
    },
    {
      "path": "ui/ScrollSidebarSection.astro",
      "type": "registry:component",
      "content": "---\nexport interface Props {\n  id: string;\n  class?: string;\n  minHeight?: string;\n}\n\nconst { id, class: className = '', minHeight = '600px' } = Astro.props;\n---\n\n<div\n  class={`scroll-sidebar-section ${className}`}\n  data-scroll-sidebar-section={id}\n  style={`min-height: ${minHeight};`}\n>\n  <slot />\n</div>\n\n<style>\n  .scroll-sidebar-section {\n    position: relative;\n  }\n</style>\n"
    }
  ],
  "category": "ui",
  "registryDependencies": [
    "scroll-sidebar-content",
    "scroll-sidebar-nav",
    "scroll-sidebar-nav-item",
    "scroll-sidebar-section"
  ]
}