{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "checkbox-tree", "type": "registry:ui", "title": "Checkbox Tree", "description": "A hierarchical tree checkbox for nested selections with expand/collapse.", "dependencies": [ "motion" ], "files": [ { "path": "registry/ruixenui/checkbox-tree.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\ninterface TreeNode {\n id: string;\n label: string;\n children?: TreeNode[];\n}\n\ninterface CheckboxTreeProps {\n nodes: TreeNode[];\n value?: string[];\n defaultValue?: string[];\n onValueChange?: (value: string[]) => void;\n className?: string;\n}\n\nfunction getAllChildIds(node: TreeNode): string[] {\n const ids = [node.id];\n if (node.children) {\n node.children.forEach((child) => {\n ids.push(...getAllChildIds(child));\n });\n }\n return ids;\n}\n\nfunction getNodeState(\n node: TreeNode,\n selectedIds: string[],\n): \"checked\" | \"indeterminate\" | \"unchecked\" {\n if (!node.children || node.children.length === 0) {\n return selectedIds.includes(node.id) ? \"checked\" : \"unchecked\";\n }\n\n const childIds = node.children.flatMap((child) => getAllChildIds(child));\n const selectedChildCount = childIds.filter((id) =>\n selectedIds.includes(id),\n ).length;\n\n if (selectedChildCount === 0) return \"unchecked\";\n if (selectedChildCount === childIds.length) return \"checked\";\n return \"indeterminate\";\n}\n\ninterface TreeNodeItemProps {\n node: TreeNode;\n level: number;\n selectedIds: string[];\n onToggle: (node: TreeNode, checked: boolean) => void;\n}\n\nfunction TreeNodeItem({\n node,\n level,\n selectedIds,\n onToggle,\n}: TreeNodeItemProps) {\n const [isExpanded, setIsExpanded] = React.useState(true);\n const hasChildren = node.children && node.children.length > 0;\n const state = getNodeState(node, selectedIds);\n const isChecked = state === \"checked\";\n const isIndeterminate = state === \"indeterminate\";\n const isFilled = isChecked || isIndeterminate;\n\n const handleChange = () => {\n onToggle(node, state !== \"checked\");\n };\n\n return (\n