{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "collapsible-file-tree",
  "title": "Collapsible File Tree",
  "description": "A file tree component with collapsible folders and file badges.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@lumi-ui/collapsible"
  ],
  "files": [
    {
      "path": "registry/components/collapsible-file-tree.tsx",
      "content": "\"use client\";\n\nimport {\n  CheckCircle2,\n  ChevronRight,\n  File,\n  FileJson,\n  FileText,\n  Folder,\n  FolderOpen,\n  Image as ImageIcon,\n  Loader2,\n} from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/registry/ui/button\";\nimport {\n  Collapsible,\n  CollapsiblePanel,\n  CollapsibleTrigger,\n} from \"@/registry/ui/collapsible\";\n\nexport type TreeItemType = {\n  id: string;\n  name: string;\n  type: \"folder\" | \"file\";\n  children?: TreeItemType[];\n  icon?: React.ReactNode;\n  badge?: string | number | React.ReactNode;\n  badgeColor?: \"default\" | \"blue\" | \"red\" | \"green\";\n  isOpen?: boolean;\n};\n\ninterface TreeItemProps {\n  item: TreeItemType;\n  depth?: number;\n}\n\nfunction getFileIcon(name: string, customIcon?: React.ReactNode) {\n  if (customIcon) return customIcon;\n  if (name.endsWith(\".tsx\") || name.endsWith(\".ts\"))\n    return <FileText className=\"size-4 text-blue-400\" />;\n  if (name.endsWith(\".css\"))\n    return <FileText className=\"size-4 text-sky-300\" />;\n  if (name.endsWith(\".json\"))\n    return <FileJson className=\"size-4 text-yellow-400\" />;\n  if (name.endsWith(\".png\") || name.endsWith(\".jpg\"))\n    return <ImageIcon className=\"size-4 text-purple-400\" />;\n  if (name.endsWith(\".md\"))\n    return <FileText className=\"size-4 text-gray-400\" />;\n  return <File className=\"size-4 text-muted-foreground\" />;\n}\n\nfunction TreeItem({ item, depth = 0 }: TreeItemProps) {\n  const isFolder = item.type === \"folder\";\n  const paddingLeft = depth * 16 + 12;\n\n  const renderBadge = () => {\n    if (!item.badge) return null;\n    if (React.isValidElement(item.badge)) {\n      return <div className=\"ml-auto flex items-center\">{item.badge}</div>;\n    }\n\n    const colorStyles = {\n      blue: \"bg-blue-600 text-white\",\n      default: \"bg-muted text-muted-foreground\",\n      green: \"bg-green-900/50 text-green-200 border border-green-900\",\n      red: \"bg-red-900/50 text-red-200 border border-red-900\",\n    };\n\n    return (\n      <span\n        className={cn(\n          \"ml-auto text-[10px] px-1.5 py-0.5 rounded-full font-medium leading-none\",\n          colorStyles[item.badgeColor || \"default\"],\n        )}\n      >\n        {item.badge}\n      </span>\n    );\n  };\n\n  if (isFolder) {\n    return (\n      <Collapsible className=\"w-full\">\n        <CollapsibleTrigger\n          render={\n            <Button\n              className={cn(\n                \"group flex w-full items-center gap-2 py-1.5 pr-3 text-sm hover:bg-accent/50 text-foreground transition-colors rounded-sm justify-start\",\n              )}\n              style={{ paddingLeft: `${paddingLeft}px` }}\n              variant=\"ghost\"\n            >\n              <ChevronRight className=\"size-4 text-muted-foreground shrink-0 transition-transform duration-200 group-data-panel-open:rotate-90\" />\n              <Folder className=\"size-4 text-blue-500 fill-blue-500/20 group-data-panel-open:hidden\" />\n              <FolderOpen className=\"size-4 text-blue-500 fill-blue-500/20 hidden group-data-panel-open:block\" />\n              <span className=\"truncate font-medium\">{item.name}</span>\n              {renderBadge()}\n            </Button>\n          }\n        />\n        <CollapsiblePanel className=\"relative\">\n          <div\n            className=\"absolute left-0 w-px bg-border/40 h-full\"\n            style={{ left: `${paddingLeft + 7}px` }}\n          />\n          {item.children?.map((child) => (\n            <TreeItem depth={depth + 1} item={child} key={child.id} />\n          ))}\n        </CollapsiblePanel>\n      </Collapsible>\n    );\n  }\n\n  return (\n    <div\n      className=\"flex items-center gap-2 py-1.5 pr-3 text-sm text-muted-foreground hover:bg-accent/50 hover:text-foreground transition-colors cursor-pointer rounded-sm\"\n      style={{ paddingLeft: `${paddingLeft + 20}px` }}\n    >\n      {getFileIcon(item.name, item.icon)}\n      <span className=\"truncate\">{item.name}</span>\n      {renderBadge()}\n    </div>\n  );\n}\n\nconst exampleData: TreeItemType[] = [\n  {\n    badge: \"New\",\n    children: [\n      {\n        badge: 9,\n        badgeColor: \"blue\",\n        children: [\n          {\n            children: [\n              { id: \"button\", name: \"button.tsx\", type: \"file\" },\n              { id: \"collapsible\", name: \"collapsible.tsx\", type: \"file\" },\n              {\n                badge: \"Error\",\n                badgeColor: \"red\",\n                id: \"dialog\",\n                name: \"dialog.tsx\",\n                type: \"file\",\n              },\n            ],\n            id: \"ui\",\n            isOpen: true,\n            name: \"ui\",\n            type: \"folder\",\n          },\n          {\n            badge: <CheckCircle2 className=\"size-3 text-green-500\" />,\n            id: \"header\",\n            name: \"header.tsx\",\n            type: \"file\",\n          },\n          { id: \"footer\", name: \"footer.tsx\", type: \"file\" },\n        ],\n        id: \"components\",\n        isOpen: true,\n        name: \"components\",\n        type: \"folder\",\n      },\n      {\n        children: [\n          { id: \"utils\", name: \"utils.ts\", type: \"file\" },\n          {\n            icon: (\n              <Loader2 className=\"size-4 animate-spin text-muted-foreground\" />\n            ),\n            id: \"hooks\",\n            name: \"hooks.ts\",\n            type: \"file\",\n          },\n        ],\n        id: \"lib\",\n        name: \"lib\",\n        type: \"folder\",\n      },\n      {\n        badge: 2,\n        children: [\n          { id: \"page\", name: \"page.tsx\", type: \"file\" },\n          { id: \"layout\", name: \"layout.tsx\", type: \"file\" },\n        ],\n        id: \"app\",\n        name: \"app\",\n        type: \"folder\",\n      },\n    ],\n    id: \"src\",\n    isOpen: true,\n    name: \"src\",\n    type: \"folder\",\n  },\n  {\n    children: [\n      {\n        children: [{ id: \"logo\", name: \"logo.png\", type: \"file\" }],\n        id: \"images\",\n        name: \"images\",\n        type: \"folder\",\n      },\n    ],\n    id: \"public\",\n    name: \"public\",\n    type: \"folder\",\n  },\n  {\n    icon: (\n      <div className=\"size-4 text-red-500 font-bold text-[10px] leading-4 text-center\">\n        npm\n      </div>\n    ),\n    id: \"pkg\",\n    name: \"package.json\",\n    type: \"file\",\n  },\n  {\n    badge: \"Docs\",\n    badgeColor: \"default\",\n    id: \"readme\",\n    name: \"README.md\",\n    type: \"file\",\n  },\n];\n\nexport function CollapsibleFileTreeDemo() {\n  return (\n    <div className=\"w-full max-w-lg my-12 rounded-lg border bg-background/50 p-2 font-mono text-sm shadow-sm h-full\">\n      {exampleData.map((item) => (\n        <TreeItem item={item} key={item.id} />\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "layout-navigation"
  ],
  "type": "registry:component"
}