{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "astro-tabs",
  "title": "Tabs Block (Astro)",
  "description": "Tabbed switcher for alternative methods.",
  "dependencies": [
    "@contentbit/core",
    "@contentbit/blocks",
    "@contentbit/astro"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "src/astro/tabs.astro",
      "content": "---\nimport type { TabData, TabsData } from '@contentbit/blocks'\nimport type { ValidatedBlockNode } from '@contentbit/core'\nimport type { AstroBlockContext } from '@contentbit/astro'\n\nimport { isValidatedBlock } from '@contentbit/core'\n\ninterface Props {\n  node: ValidatedBlockNode<unknown>\n  ctx: AstroBlockContext\n}\n\nconst { node, ctx } = Astro.props\nconst data = node.data as TabsData\n// The source offset alone collides when two documents render on one page, so\n// a per-render suffix keeps tab/panel ids (and their ARIA wiring) unique.\nconst id = `cb-tabs-${node.position.start.offset}-${Math.random().toString(36).slice(2, 8)}`\nconst tabs = await Promise.all(\n  data.blocks.map(async (tab, i) => ({\n    title: String(tab.props.title),\n    html: await ctx.renderMarkdown(isValidatedBlock(tab) ? (tab.data as TabData).markdown : tab.body),\n    selected: i === 0,\n  })),\n)\n---\n\n<div data-cb-styled data-cb-tabs class=\"my-6\">\n  <div role=\"tablist\" class=\"bg-muted text-muted-foreground inline-flex h-9 items-center justify-center rounded-lg p-1\">\n    {\n      tabs.map((tab, i) => (\n        <button\n          type=\"button\"\n          role=\"tab\"\n          id={`${id}-tab-${i}`}\n          aria-controls={`${id}-panel-${i}`}\n          aria-selected={tab.selected ? 'true' : 'false'}\n          tabindex={tab.selected ? 0 : -1}\n          class=\"aria-selected:bg-background aria-selected:text-foreground inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap transition-all aria-selected:shadow-sm\"\n        >\n          {tab.title}\n        </button>\n      ))\n    }\n  </div>\n  {\n    tabs.map((tab, i) => (\n      <div\n        role=\"tabpanel\"\n        id={`${id}-panel-${i}`}\n        aria-labelledby={`${id}-tab-${i}`}\n        hidden={!tab.selected}\n        class=\"mt-2 text-sm leading-relaxed\"\n        set:html={tab.html}\n      />\n    ))\n  }\n</div>\n\n<script>\n  // One handler per page; activates a tab within its [data-cb-tabs] container.\n  function activate(container: Element, index: number): void {\n    const tabs = container.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n    const panels = container.querySelectorAll<HTMLElement>('[role=\"tabpanel\"]')\n    tabs.forEach((tab, i) => {\n      tab.setAttribute('aria-selected', i === index ? 'true' : 'false')\n      tab.tabIndex = i === index ? 0 : -1\n    })\n    panels.forEach((panel, i) => {\n      panel.hidden = i !== index\n    })\n    tabs[index]?.focus()\n  }\n\n  function init(): void {\n    document.querySelectorAll('[data-cb-tabs]').forEach((container) => {\n      if (container instanceof HTMLElement && container.dataset.cbTabsReady) return\n      if (container instanceof HTMLElement) container.dataset.cbTabsReady = 'true'\n      const tabs = [...container.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')]\n      tabs.forEach((tab, i) => {\n        tab.addEventListener('click', () => activate(container, i))\n        tab.addEventListener('keydown', (event) => {\n          if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return\n          event.preventDefault()\n          const delta = event.key === 'ArrowRight' ? 1 : -1\n          activate(container, (i + delta + tabs.length) % tabs.length)\n        })\n      })\n    })\n  }\n\n  init()\n  // Re-bind after Astro view-transition navigations (no-op without <ClientRouter />).\n  document.addEventListener('astro:page-load', init)\n</script>\n",
      "type": "registry:component",
      "target": "components/content-blocks/tabs.astro"
    }
  ],
  "type": "registry:component"
}