{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "tree", "title": "Tree", "files": [ { "path": "components/tree/tree.tsx", "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Button } from \"../button\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"../collapsible\";\n\n// Unified TreeTitle component for both leaf and parent nodes\ninterface TreeTitleProps {\n title: React.ReactNode;\n icon?: React.ReactNode;\n isSelected?: boolean;\n isDisabled?: boolean;\n onClick?: React.MouseEventHandler;\n style?: React.CSSProperties;\n showIcon?: boolean;\n}\n\nfunction TreeTitle({\n title,\n icon,\n isSelected,\n isDisabled,\n onClick,\n style,\n showIcon,\n}: TreeTitleProps) {\n return (\n \n {showIcon && icon && (\n {icon}\n )}\n {typeof title === \"string\" ? (\n {title}\n ) : (\n title\n )}\n \n );\n}\n\n/** Provide a wrap type define for developer to wrap with customize fieldNames data type */\nexport type FieldDataNode<\n T,\n ChildFieldName extends string = \"children\",\n> = BasicDataNode &\n T &\n Partial[]>>;\nexport type Key = React.Key;\nexport type DataNode = FieldDataNode<{\n key: Key;\n title?: React.ReactNode | ((data: DataNode) => React.ReactNode);\n}>;\nexport interface TreeNodeProps {\n eventKey?: Key;\n prefixCls?: string;\n className?: string;\n style?: React.CSSProperties;\n id?: string;\n expanded?: boolean;\n selected?: boolean;\n checked?: boolean;\n loaded?: boolean;\n loading?: boolean;\n halfChecked?: boolean;\n title?: React.ReactNode | ((data: TreeDataType) => React.ReactNode);\n dragOver?: boolean;\n dragOverGapTop?: boolean;\n dragOverGapBottom?: boolean;\n pos?: string;\n domRef?: React.Ref;\n /** New added in Tree for easy data access */\n data?: TreeDataType;\n isStart?: boolean[];\n isEnd?: boolean[];\n active?: boolean;\n onMouseMove?: React.MouseEventHandler;\n isLeaf?: boolean;\n checkable?: boolean;\n selectable?: boolean;\n disabled?: boolean;\n disableCheckbox?: boolean;\n icon?: IconType;\n switcherIcon?: IconType;\n children?: React.ReactNode;\n}\nexport type IconType =\n | React.ReactNode\n | ((props: TreeNodeProps) => React.ReactNode);\n\n/** For fieldNames, we provides a abstract interface */\nexport interface BasicDataNode {\n checkable?: boolean;\n disabled?: boolean;\n disableCheckbox?: boolean;\n icon?: IconType;\n isLeaf?: boolean;\n selectable?: boolean;\n switcherIcon?: IconType;\n /** Set style of TreeNode. This is not recommend if you don't have any force requirement */\n className?: string;\n style?: React.CSSProperties;\n}\n\ntype TreeProps = {\n /** The treeNodes data Array, if set it then you need not to construct children TreeNode. (key should be unique across the whole array) */\n treeData?: TreeDataType[];\n /** Tree node data */\n children?: React.ReactNode;\n /** Default expanded keys */\n defaultExpandedKeys?: Key[];\n /** Expanded keys (controlled) */\n expandedKeys?: Key[];\n /** Callback function for when a tree node is expanded/collapsed */\n onExpand?: (\n expandedKeys: Key[],\n info: { expanded: boolean; node: TreeDataType },\n ) => void;\n /** Callback function for when a tree node is selected */\n onSelect?: (\n selectedKeys: Key[],\n info: { selected: boolean; node: TreeDataType },\n ) => void;\n /** Selected keys */\n selectedKeys?: Key[];\n /** Whether to show the connecting line */\n showLine?: boolean;\n /** Whether to show the icon */\n showIcon?: boolean;\n /** Custom icon */\n icon?: IconType;\n /** Custom switcher icon */\n switcherIcon?: IconType;\n\n className?: string;\n};\n\ntype TreeNodeData = {\n key: Key;\n title: React.ReactNode;\n children?: TreeNodeData[];\n icon?: React.ReactNode;\n isLeaf?: boolean;\n disabled?: boolean;\n};\n\ntype InternalTreeProps = TreeProps & {\n /** Internal prop for recursive rendering */\n item?: TreeNodeData;\n /** Internal prop for tracking depth level */\n depth?: number;\n /** Internal prop for tracking if this is the last child */\n isLast?: boolean;\n /** Internal prop for tracking parent line states */\n parentLines?: boolean[];\n _parent?: {\n depth: number;\n isLast: boolean;\n };\n};\n\nfunction Tree(props: InternalTreeProps) {\n const {\n treeData,\n item,\n children,\n selectedKeys,\n onSelect,\n depth = 0,\n isLast = false,\n parentLines = [],\n className,\n ...restProps\n } = props;\n\n // If treeData is provided, render the tree structure\n if (treeData && treeData.length > 0) {\n return (\n
\n {treeData.map((node, index) => (\n \n ))}\n
\n );\n }\n\n // If item is provided (internal recursive call), render single node\n if (item) {\n return (\n \n );\n }\n\n // Fallback to children\n return
{children}
;\n}\n\nfunction TreeNode({\n item,\n selectedKeys,\n onSelect,\n depth = 0,\n isLast = false,\n parentLines = [],\n showIcon = false,\n showLine = false,\n _parent,\n ...props\n}: {\n item: TreeNodeData;\n selectedKeys?: Key[];\n onSelect?: TreeProps[\"onSelect\"];\n depth?: number;\n isLast?: boolean;\n parentLines?: boolean[];\n _parent?: {\n depth: number;\n isLast: boolean;\n };\n showLine?: boolean;\n} & Omit) {\n const [isExpanded, setIsExpanded] = useState(true);\n\n const hasChildren = item.children && item.children.length > 0;\n const isSelected = selectedKeys?.includes(item.key);\n\n const handleClick = () => {\n if (onSelect) {\n const newSelectedKeys = (\n isSelected\n ? (selectedKeys?.filter((key) => key !== item.key) ?? [])\n : [item.key]\n ) as Key[];\n onSelect(newSelectedKeys, {\n selected: !isSelected,\n node: item as any,\n });\n }\n };\n\n const handleToggle = () => {\n setIsExpanded(!isExpanded);\n };\n\n // Add additional margin for nodes with lines to prevent text overlap\n const nodeContentStyle: React.CSSProperties =\n showLine && depth > 0\n ? {\n marginLeft: \"6px\", // Add margin to account for the horizontal line\n }\n : {};\n\n if (!hasChildren) {\n return (\n \n {/* Indent */}\n \n {Array.from({ length: depth }).map((_, i) => (\n \n {/* {_parent?.depth}.{i} */}\n \n \n ))}\n \n {/* Switcher (noop for leaf, with leaf-line) */}\n \n {showLine && (\n \n {/* Horizontal line */}\n \n {/* Vertical line */}\n \n \n )}\n \n {/* Content */}\n \n \n );\n }\n\n return (\n \n \n
\n {/* Indent */}\n \n {Array.from({ length: depth }).map((_, i) => (\n \n \n \n ))}\n \n \n \n \n \n \n \n {/* Horizontal connector line from parent title to child title */}\n {/* {showLine &&\n Array.isArray(item.children) &&\n item.children.length > 0 && (\n \n )} */}\n
\n \n
\n {item.children?.map((subItem, index) => {\n const childCount = item.children ? item.children.length : 0;\n const newParentLines = parentLines.slice(0, depth);\n newParentLines[depth] = index !== childCount - 1;\n\n return (\n \n );\n })}\n
\n
\n
\n \n );\n}\n\nexport type { TreeProps };\n\nexport { Tree };\n", "type": "registry:file", "target": "components/tree/tree.tsx" } ], "type": "registry:component" }