{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tree",
  "title": "Tree",
  "description": "Hierarchical collapsible structure.",
  "dependencies": [
    "@radix-ui/react-slot"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/tree.tsx",
      "content": "\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { ChevronRight, File, Folder, FolderOpen } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface TreeContextType {\n  expandedIds: Set<string>;\n  selectedIds: string[];\n  toggleExpanded: (nodeId: string) => void;\n  handleSelection: (nodeId: string, ctrlKey?: boolean) => void;\n  showLines: boolean;\n  showIcons: boolean;\n  selectable: boolean;\n  multiSelect: boolean;\n  animateExpand: boolean;\n  indent: number;\n  onNodeClick?: (nodeId: string, data?: any) => void;\n  onNodeExpand?: (nodeId: string, expanded: boolean) => void;\n}\n\nconst TreeContext = React.createContext<TreeContextType | null>(null);\n\nconst useTree = () => {\n  const context = React.useContext(TreeContext);\n  if (!context)\n    throw new Error(\"Tree components must be used within a TreeProvider\");\n  return context;\n};\n\nconst treeVariants = cva(\n  \"w-full rounded-ele border border-border bg-background\",\n  {\n    variants: {\n      variant: {\n        default: \"\",\n        outline: \"border-2\",\n        ghost: \"border-transparent bg-transparent\",\n      },\n      size: {\n        sm: \"text-sm\",\n        default: \"\",\n        lg: \"text-lg\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nconst treeItemVariants = cva(\n  \"group relative flex cursor-pointer select-none items-center rounded-[calc(var(--card-radius)-8px)] px-3 py-2 outline-none transition-colors motion-safe:duration-200\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n        ghost: \"hover:bg-accent/50\",\n        subtle: \"hover:bg-muted/50\",\n      },\n      selected: {\n        true: \"bg-accent text-accent-foreground\",\n        false: \"\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      selected: false,\n    },\n  }\n);\n\nexport interface TreeProviderProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof treeVariants> {\n  defaultExpandedIds?: string[];\n  selectedIds?: string[];\n  onSelectionChange?: (selectedIds: string[]) => void;\n  onNodeClick?: (nodeId: string, data?: any) => void;\n  onNodeExpand?: (nodeId: string, expanded: boolean) => void;\n  showLines?: boolean;\n  showIcons?: boolean;\n  selectable?: boolean;\n  multiSelect?: boolean;\n  animateExpand?: boolean;\n  indent?: number;\n}\n\nconst TreeProvider = React.forwardRef<HTMLDivElement, TreeProviderProps>(\n  (\n    {\n      className,\n      variant,\n      size,\n      children,\n      defaultExpandedIds = [],\n      selectedIds = [],\n      onSelectionChange,\n      onNodeClick,\n      onNodeExpand,\n      showLines = true,\n      showIcons = true,\n      selectable = true,\n      multiSelect = false,\n      animateExpand = true,\n      indent = 20,\n      ...props\n    },\n    ref\n  ) => {\n    const [expandedIds, setExpandedIds] = React.useState<Set<string>>(\n      new Set(defaultExpandedIds)\n    );\n    const [internalSelectedIds, setInternalSelectedIds] =\n      React.useState<string[]>(selectedIds);\n\n    const isControlled = onSelectionChange !== undefined;\n    const currentSelectedIds = isControlled ? selectedIds : internalSelectedIds;\n\n    const toggleExpanded = React.useCallback(\n      (nodeId: string) => {\n        setExpandedIds((prev) => {\n          const newSet = new Set(prev);\n          const isExpanded = newSet.has(nodeId);\n          isExpanded ? newSet.delete(nodeId) : newSet.add(nodeId);\n          onNodeExpand?.(nodeId, !isExpanded);\n          return newSet;\n        });\n      },\n      [onNodeExpand]\n    );\n\n    const handleSelection = React.useCallback(\n      (nodeId: string, ctrlKey = false) => {\n        if (!selectable) return;\n        let newSelection: string[];\n        if (multiSelect && ctrlKey) {\n          newSelection = currentSelectedIds.includes(nodeId)\n            ? currentSelectedIds.filter((id) => id !== nodeId)\n            : [...currentSelectedIds, nodeId];\n        } else {\n          newSelection = currentSelectedIds.includes(nodeId) ? [] : [nodeId];\n        }\n        isControlled\n          ? onSelectionChange?.(newSelection)\n          : setInternalSelectedIds(newSelection);\n      },\n      [\n        selectable,\n        multiSelect,\n        currentSelectedIds,\n        isControlled,\n        onSelectionChange,\n      ]\n    );\n\n    const contextValue: TreeContextType = {\n      expandedIds,\n      selectedIds: currentSelectedIds,\n      toggleExpanded,\n      handleSelection,\n      showLines,\n      showIcons,\n      selectable,\n      multiSelect,\n      animateExpand,\n      indent,\n      onNodeClick,\n      onNodeExpand,\n    };\n\n    return (\n      <TreeContext.Provider value={contextValue}>\n        <div\n          className={cn(\n            treeVariants({ variant, size, className }),\n            \"motion-safe:duration-200 motion-reduce:animate-none\"\n          )}\n          ref={ref}\n          {...props}\n        >\n          <div className=\"p-2\">{children}</div>\n        </div>\n      </TreeContext.Provider>\n    );\n  }\n);\nTreeProvider.displayName = \"TreeProvider\";\n\nexport interface TreeProps extends React.HTMLAttributes<HTMLDivElement> {\n  asChild?: boolean;\n}\n\nconst Tree = React.forwardRef<HTMLDivElement, TreeProps>(\n  ({ className, asChild = false, children, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"div\";\n    return (\n      <Comp\n        className={cn(\"flex flex-col gap-1\", className)}\n        ref={ref}\n        {...props}\n      >\n        {children}\n      </Comp>\n    );\n  }\n);\nTree.displayName = \"Tree\";\n\nexport interface TreeItemProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof treeItemVariants> {\n  nodeId: string;\n  label: string;\n  icon?: React.ReactNode;\n  data?: any;\n  level?: number;\n  isLast?: boolean;\n  parentPath?: boolean[];\n  hasChildren?: boolean;\n  asChild?: boolean;\n}\n\nconst TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(\n  (\n    {\n      className,\n      variant,\n      nodeId,\n      label,\n      icon,\n      data,\n      level = 0,\n      isLast = false,\n      parentPath = [],\n      hasChildren = false,\n      asChild = false,\n      children,\n      onClick,\n      ...props\n    },\n    ref\n  ) => {\n    const {\n      expandedIds,\n      selectedIds,\n      toggleExpanded,\n      handleSelection,\n      showLines,\n      showIcons,\n      animateExpand,\n      indent,\n      onNodeClick,\n    } = useTree();\n\n    const isExpanded = expandedIds.has(nodeId);\n    const isSelected = selectedIds.includes(nodeId);\n    const currentPath = [...parentPath, isLast];\n\n    const getDefaultIcon = () =>\n      hasChildren ? (\n        isExpanded ? (\n          <FolderOpen aria-hidden=\"true\" className=\"size-4\" />\n        ) : (\n          <Folder aria-hidden=\"true\" className=\"size-4\" />\n        )\n      ) : (\n        <File aria-hidden=\"true\" className=\"size-4\" />\n      );\n\n    const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {\n      if (hasChildren) toggleExpanded(nodeId);\n      handleSelection(nodeId, e.ctrlKey || e.metaKey);\n      onNodeClick?.(nodeId, data);\n      onClick?.(e);\n    };\n\n    const Comp = asChild ? Slot : \"div\";\n\n    return (\n      <div className=\"select-none\" role=\"none\">\n        <div\n          aria-expanded={hasChildren ? isExpanded : undefined}\n          aria-selected={isSelected || undefined}\n          className={cn(\n            treeItemVariants({ variant, selected: isSelected, className })\n          )}\n          onClick={handleClick}\n          ref={ref}\n          role=\"treeitem\"\n          style={{ paddingLeft: level * indent + 8 }}\n          tabIndex={0}\n          {...props}\n        >\n          {showLines && level > 0 && (\n            <div className=\"pointer-events-none absolute inset-y-0 left-0\">\n              {currentPath.map((isLastInPath, pathIndex) => (\n                <div\n                  className=\"absolute inset-y-0 border-border/40 border-l\"\n                  key={pathIndex}\n                  style={{\n                    left: pathIndex * indent + 12,\n                    display:\n                      pathIndex === currentPath.length - 1 && isLastInPath\n                        ? \"none\"\n                        : \"block\",\n                  }}\n                />\n              ))}\n              <div\n                className=\"absolute top-1/2 border-border/40 border-t\"\n                style={{\n                  left: (level - 1) * indent + 12,\n                  width: indent - 4,\n                  transform: \"translateY(-1px)\",\n                }}\n              />\n              {isLast && (\n                <div\n                  className=\"absolute top-0 border-border/40 border-l\"\n                  style={{ left: (level - 1) * indent + 12, height: \"50%\" }}\n                />\n              )}\n            </div>\n          )}\n\n          <div\n            aria-hidden=\"true\"\n            className={cn(\n              \"flex size-4 items-center justify-center text-muted-foreground transition-transform motion-safe:duration-200\",\n              hasChildren && isExpanded ? \"rotate-90\" : \"rotate-0\"\n            )}\n          >\n            {hasChildren && <ChevronRight className=\"size-3\" />}\n          </div>\n\n          {showIcons && (\n            <div className=\"mx-2 flex size-4 items-center justify-center text-muted-foreground\">\n              {icon || getDefaultIcon()}\n            </div>\n          )}\n\n          <span className=\"flex-1 truncate text-foreground text-sm\">\n            {label}\n          </span>\n        </div>\n\n        {hasChildren && (\n          <div\n            aria-hidden={!isExpanded}\n            className={cn(\n              \"overflow-hidden\",\n              animateExpand\n                ? \"transition-[max-height,opacity] motion-safe:duration-200\"\n                : \"\",\n              isExpanded ? \"max-h-[2000px] opacity-100\" : \"max-h-0 opacity-0\"\n            )}\n          >\n            <div className=\"translate-y-0 will-change-transform motion-safe:duration-200\">\n              {children}\n            </div>\n          </div>\n        )}\n      </div>\n    );\n  }\n);\nTreeItem.displayName = \"TreeItem\";\n\nexport { TreeProvider, Tree, TreeItem, treeVariants, treeItemVariants };\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "component"
  ],
  "type": "registry:ui"
}