{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar-tabs",
  "title": "Sidebar tabs",
  "description": "getSidebarTabs and SidebarTabsDropdown: switch between root folders of the page tree from a dropdown in the sidebar.",
  "dependencies": [
    "cn",
    "fumadocs-core@^16.17.8",
    "lucide-react"
  ],
  "registryDependencies": [
    "dropdown-menu",
    "sidebar",
    "@docscn/tree-context",
    "@docscn/layout-shared",
    "@docscn/docs-utils"
  ],
  "files": [
    {
      "path": "registry/base/docs/components/sidebar/tabs/index.tsx",
      "content": "// Adapted from Fumadocs UI (https://github.com/fuma-nama/fumadocs)\n// Copyright (c) 2023 Fuma, MIT License\nimport type { LayoutTab } from \"../../../layouts/shared\";\nimport type * as PageTree from \"fumadocs-core/page-tree\";\n\nexport type SidebarTab = LayoutTab;\n\nexport interface GetSidebarTabsOptions {\n  transform?: (option: SidebarTab, node: PageTree.Folder) => SidebarTab | null;\n}\n\nconst defaultTransform: GetSidebarTabsOptions[\"transform\"] = (option, node) => {\n  if (!node.icon) return option;\n\n  return {\n    ...option,\n    icon: (\n      <div className=\"size-full max-md:rounded-md max-md:border max-md:bg-secondary max-md:p-1.5 [&_svg]:size-full\">\n        {node.icon}\n      </div>\n    ),\n  };\n};\n\nexport function getSidebarTabs(\n  tree: PageTree.Root,\n  { transform = defaultTransform }: GetSidebarTabsOptions = {},\n): SidebarTab[] {\n  const results: SidebarTab[] = [];\n\n  function scanOptions(\n    node: PageTree.Root | PageTree.Folder,\n    unlisted?: boolean,\n  ) {\n    if (\"root\" in node && node.root) {\n      const urls = getFolderUrls(node);\n\n      if (urls.size > 0) {\n        const option: SidebarTab = {\n          url: urls.values().next().value ?? \"\",\n          title: node.name,\n          icon: node.icon,\n          unlisted,\n          description: node.description,\n          urls,\n        };\n\n        const mapped = transform ? transform(option, node) : option;\n        if (mapped) results.push(mapped);\n      }\n    }\n\n    for (const child of node.children) {\n      if (child.type === \"folder\") scanOptions(child, unlisted);\n    }\n  }\n\n  scanOptions(tree);\n  if (tree.fallback) scanOptions(tree.fallback, true);\n\n  return results;\n}\n\nfunction getFolderUrls(\n  folder: PageTree.Folder,\n  output: Set<string> = new Set(),\n): Set<string> {\n  if (folder.index) output.add(folder.index.url);\n\n  for (const child of folder.children) {\n    if (child.type === \"page\" && !child.external) output.add(child.url);\n    if (child.type === \"folder\") getFolderUrls(child, output);\n  }\n\n  return output;\n}\n",
      "type": "registry:component",
      "target": "@components/docs/components/sidebar/tabs/index.tsx"
    },
    {
      "path": "registry/base/docs/components/sidebar/tabs/dropdown.tsx",
      "content": "// Adapted from Fumadocs UI (https://github.com/fuma-nama/fumadocs)\n// Copyright (c) 2023 Fuma, MIT License\n\"use client\";\n\nimport { cn } from \"cn\";\nimport { usePathname } from \"fumadocs-core/framework\";\nimport Link from \"fumadocs-core/link\";\nimport { Check, ChevronsUpDown } from \"lucide-react\";\nimport { type ComponentProps, type ReactNode, useMemo } from \"react\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { useSidebar } from \"@/components/ui/sidebar\";\nimport { useTabsGroups, useTreePath } from \"../../../contexts/tree\";\nimport { isLayoutTabActive, type LayoutTab } from \"../../../layouts/shared\";\nimport { findLast } from \"../../../utils/array\";\n\nexport type SidebarTabWithProps = LayoutTab;\n\n/**\n * Renders the given tabs as a dropdown per tabs group, one for each root folder on the\n * current page's path, letting users switch between root folders of the same type.\n */\nexport function SidebarTabsDropdown({\n  options,\n  placeholder,\n  ...props\n}: {\n  placeholder?: ReactNode;\n  options: LayoutTab[];\n} & ComponentProps<\"button\">) {\n  const groups = useTabsGroups(options);\n\n  return groups.map((group, i) => (\n    <Dropdown\n      key={i}\n      options={group.options}\n      placeholder={placeholder}\n      {...props}\n    />\n  ));\n}\n\nfunction Dropdown({\n  options,\n  placeholder,\n  className,\n  ...props\n}: {\n  placeholder?: ReactNode;\n  options: LayoutTab[];\n} & ComponentProps<\"button\">) {\n  const { isMobile, setOpenMobile } = useSidebar();\n  const pathname = usePathname();\n  const path = useTreePath();\n\n  const selected = useMemo(() => {\n    return findLast(options, (item) => isLayoutTabActive(item, path, pathname));\n  }, [options, path, pathname]);\n\n  const item = selected ? (\n    <>\n      <div className=\"size-8 shrink-0 empty:hidden md:size-5 [&_svg]:size-full\">\n        {selected.icon}\n      </div>\n      <div className=\"min-w-0\">\n        <p className=\"truncate text-sm font-medium\">{selected.title}</p>\n        <p className=\"text-sm text-muted-foreground empty:hidden md:hidden\">\n          {selected.description}\n        </p>\n      </div>\n    </>\n  ) : (\n    placeholder\n  );\n\n  return (\n    <DropdownMenu>\n      {item && (\n        <DropdownMenuTrigger\n          {...props}\n          className={cn(\n            \"flex w-full items-center gap-2 rounded-lg border bg-background p-2 text-start shadow-xs transition-colors outline-none hover:bg-sidebar-accent focus-visible:ring-2 focus-visible:ring-sidebar-ring data-popup-open:bg-sidebar-accent\",\n            className,\n          )}\n        >\n          {item}\n          <ChevronsUpDown className=\"ms-auto size-4 shrink-0 text-muted-foreground\" />\n        </DropdownMenuTrigger>\n      )}\n      <DropdownMenuContent className=\"w-(--anchor-width) min-w-56\">\n        {options.map((item) => {\n          const isActive = selected && item.url === selected.url;\n          if (!isActive && item.unlisted) return;\n\n          return (\n            <DropdownMenuItem\n              key={item.url}\n              className=\"items-start gap-2 p-1.5\"\n              render={\n                <Link\n                  href={item.url}\n                  {...item.props}\n                  onClick={(e) => {\n                    item.props?.onClick?.(e);\n                    if (isMobile) setOpenMobile(false);\n                  }}\n                />\n              }\n            >\n              <div className=\"size-8 shrink-0 empty:hidden md:size-5 [&_svg]:size-full\">\n                {item.icon}\n              </div>\n              <div className=\"min-w-0\">\n                <p className=\"text-sm leading-5 font-medium\">{item.title}</p>\n                <p className=\"text-[0.8125rem] text-muted-foreground empty:hidden\">\n                  {item.description}\n                </p>\n              </div>\n              <Check\n                className={cn(\n                  \"ms-auto mt-0.5 size-3.5 shrink-0\",\n                  !isActive && \"invisible\",\n                )}\n              />\n            </DropdownMenuItem>\n          );\n        })}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/docs/components/sidebar/tabs/dropdown.tsx"
    }
  ],
  "type": "registry:component"
}