{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tree-view",
  "type": "registry:ui",
  "description": "Hierarchical tree view with expand/collapse, selection, and checkboxes",
  "dependencies": [
    "@radix-ui/react-collapsible",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "checkbox",
    "collapsible"
  ],
  "files": [
    {
      "path": "registry/default/ui/tree-view.tsx",
      "content": "import * as React from 'react'\nimport { ChevronRight, Folder, File } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Checkbox } from '@/components/ui/checkbox'\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from '@/components/ui/collapsible'\n\nexport interface TreeNode {\n  id: string\n  label: string\n  icon?: React.ReactNode\n  children?: TreeNode[]\n  disabled?: boolean\n}\n\nexport interface TreeViewProps extends React.HTMLAttributes<HTMLDivElement> {\n  data: TreeNode[]\n  expandedIds?: string[]\n  onExpandedChange?: (ids: string[]) => void\n  selectedIds?: string[]\n  onSelectedChange?: (ids: string[]) => void\n  selectionMode?: 'none' | 'single' | 'multiple'\n  showCheckboxes?: boolean\n  showIcons?: boolean\n  defaultExpandedIds?: string[]\n  defaultSelectedIds?: string[]\n}\n\n// Context\ninterface TreeViewContextValue {\n  expandedIds: Set<string>\n  toggleExpanded: (id: string) => void\n  selectedIds: Set<string>\n  toggleSelected: (id: string) => void\n  selectionMode: 'none' | 'single' | 'multiple'\n  showCheckboxes: boolean\n  showIcons: boolean\n}\n\nconst TreeViewContext = React.createContext<TreeViewContextValue | null>(null)\n\nfunction useTreeView() {\n  const context = React.useContext(TreeViewContext)\n  if (!context) {\n    throw new Error('TreeView components must be used within a <TreeView />')\n  }\n  return context\n}\n\nconst TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(\n  (\n    {\n      data,\n      expandedIds: controlledExpandedIds,\n      onExpandedChange,\n      selectedIds: controlledSelectedIds,\n      onSelectedChange,\n      selectionMode = 'none',\n      showCheckboxes = false,\n      showIcons = true,\n      defaultExpandedIds = [],\n      defaultSelectedIds = [],\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const [uncontrolledExpandedIds, setUncontrolledExpandedIds] = React.useState<Set<string>>(\n      new Set(defaultExpandedIds)\n    )\n    const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = React.useState<Set<string>>(\n      new Set(defaultSelectedIds)\n    )\n\n    const isExpandedControlled = controlledExpandedIds !== undefined\n    const isSelectedControlled = controlledSelectedIds !== undefined\n\n    const expandedIds = isExpandedControlled\n      ? new Set(controlledExpandedIds)\n      : uncontrolledExpandedIds\n\n    const selectedIds = isSelectedControlled\n      ? new Set(controlledSelectedIds)\n      : uncontrolledSelectedIds\n\n    const toggleExpanded = React.useCallback(\n      (id: string) => {\n        const newExpandedIds = new Set(expandedIds)\n        if (newExpandedIds.has(id)) {\n          newExpandedIds.delete(id)\n        } else {\n          newExpandedIds.add(id)\n        }\n\n        if (!isExpandedControlled) {\n          setUncontrolledExpandedIds(newExpandedIds)\n        }\n        onExpandedChange?.(Array.from(newExpandedIds))\n      },\n      [expandedIds, isExpandedControlled, onExpandedChange]\n    )\n\n    const toggleSelected = React.useCallback(\n      (id: string) => {\n        if (selectionMode === 'none') return\n\n        let newSelectedIds: Set<string>\n\n        if (selectionMode === 'single') {\n          newSelectedIds = selectedIds.has(id) ? new Set() : new Set([id])\n        } else {\n          newSelectedIds = new Set(selectedIds)\n          if (newSelectedIds.has(id)) {\n            newSelectedIds.delete(id)\n          } else {\n            newSelectedIds.add(id)\n          }\n        }\n\n        if (!isSelectedControlled) {\n          setUncontrolledSelectedIds(newSelectedIds)\n        }\n        onSelectedChange?.(Array.from(newSelectedIds))\n      },\n      [selectedIds, selectionMode, isSelectedControlled, onSelectedChange]\n    )\n\n    return (\n      <TreeViewContext.Provider\n        value={{\n          expandedIds,\n          toggleExpanded,\n          selectedIds,\n          toggleSelected,\n          selectionMode,\n          showCheckboxes,\n          showIcons,\n        }}\n      >\n        <div\n          ref={ref}\n          role=\"tree\"\n          className={cn(\n            'border-3 border-foreground bg-background p-2',\n            'shadow-[4px_4px_0px_hsl(var(--shadow-color))]',\n            className\n          )}\n          {...props}\n        >\n          {data.map((node) => (\n            <TreeNode key={node.id} node={node} level={0} />\n          ))}\n        </div>\n      </TreeViewContext.Provider>\n    )\n  }\n)\nTreeView.displayName = 'TreeView'\n\n// Tree Node Component\ninterface TreeNodeProps {\n  node: TreeNode\n  level: number\n}\n\nfunction TreeNode({ node, level }: TreeNodeProps) {\n  const {\n    expandedIds,\n    toggleExpanded,\n    selectedIds,\n    toggleSelected,\n    selectionMode,\n    showCheckboxes,\n    showIcons,\n  } = useTreeView()\n\n  const hasChildren = node.children && node.children.length > 0\n  const isExpanded = expandedIds.has(node.id)\n  const isSelected = selectedIds.has(node.id)\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    switch (e.key) {\n      case 'Enter':\n      case ' ':\n        e.preventDefault()\n        if (selectionMode !== 'none') {\n          toggleSelected(node.id)\n        } else if (hasChildren) {\n          toggleExpanded(node.id)\n        }\n        break\n      case 'ArrowRight':\n        if (hasChildren && !isExpanded) {\n          e.preventDefault()\n          toggleExpanded(node.id)\n        }\n        break\n      case 'ArrowLeft':\n        if (hasChildren && isExpanded) {\n          e.preventDefault()\n          toggleExpanded(node.id)\n        }\n        break\n    }\n  }\n\n  const content = (\n    <div\n      role=\"treeitem\"\n      aria-selected={isSelected}\n      aria-expanded={hasChildren ? isExpanded : undefined}\n      aria-disabled={node.disabled}\n      tabIndex={node.disabled ? -1 : 0}\n      onKeyDown={handleKeyDown}\n      onClick={() => {\n        if (node.disabled) return\n        if (selectionMode !== 'none') {\n          toggleSelected(node.id)\n        }\n      }}\n      className={cn(\n        'flex items-center gap-2 px-2 py-1.5 cursor-pointer transition-colors',\n        'hover:bg-muted focus:outline-none focus:bg-muted',\n        isSelected && 'bg-accent',\n        node.disabled && 'opacity-50 cursor-not-allowed'\n      )}\n      style={{ paddingLeft: `${level * 16 + 8}px` }}\n    >\n      {/* Expand/collapse button */}\n      {hasChildren ? (\n        <button\n          type=\"button\"\n          onClick={(e) => {\n            e.stopPropagation()\n            toggleExpanded(node.id)\n          }}\n          className=\"p-0.5 hover:bg-muted-foreground/20 transition-colors\"\n          aria-label={isExpanded ? 'Collapse' : 'Expand'}\n        >\n          <ChevronRight\n            className={cn(\n              'h-4 w-4 stroke-[3] transition-transform duration-200',\n              isExpanded && 'rotate-90'\n            )}\n          />\n        </button>\n      ) : (\n        <span className=\"w-5\" />\n      )}\n\n      {/* Checkbox */}\n      {showCheckboxes && selectionMode !== 'none' && (\n        <Checkbox\n          checked={isSelected}\n          onCheckedChange={() => toggleSelected(node.id)}\n          onClick={(e) => e.stopPropagation()}\n          disabled={node.disabled}\n          className=\"h-5 w-5 border-2 border-foreground data-[state=checked]:bg-primary data-[state=checked]:shadow-[2px_2px_0px_hsl(var(--shadow-color))]\"\n        />\n      )}\n\n      {/* Icon */}\n      {showIcons && (\n        <span className=\"shrink-0\">\n          {node.icon || (hasChildren ? (\n            <Folder className=\"h-4 w-4\" />\n          ) : (\n            <File className=\"h-4 w-4\" />\n          ))}\n        </span>\n      )}\n\n      {/* Label */}\n      <span className=\"text-sm truncate\">{node.label}</span>\n    </div>\n  )\n\n  if (!hasChildren) {\n    return content\n  }\n\n  return (\n    <Collapsible open={isExpanded} onOpenChange={() => toggleExpanded(node.id)}>\n      <CollapsibleTrigger asChild className=\"w-full\">\n        {content}\n      </CollapsibleTrigger>\n      <CollapsibleContent>\n        <div role=\"group\">\n          {node.children!.map((child) => (\n            <TreeNode key={child.id} node={child} level={level + 1} />\n          ))}\n        </div>\n      </CollapsibleContent>\n    </Collapsible>\n  )\n}\n\nexport { TreeView, useTreeView }\n",
      "type": "registry:ui",
      "target": "components/ui/tree-view.tsx"
    }
  ]
}