{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "motion-file-tree", "type": "registry:ui", "title": "Motion File Tree", "description": "An animated file tree component with smooth expand/collapse transitions.", "dependencies": [ "motion", "lucide-react" ], "files": [ { "path": "registry/ruixenui/motion-file-tree.tsx", "content": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { ChevronRight, ChevronDown, Folder, File } from \"lucide-react\";\n\n// -------- Types --------\nexport type FileNode = {\n id: string;\n name: string;\n type: \"file\" | \"folder\";\n children?: FileNode[];\n};\n\nexport type MotionFileTreeProps = {\n data: FileNode[];\n defaultExpanded?: Record;\n onSelect?: (node: FileNode) => void;\n};\n\n// -------- Component --------\nexport default function MotionFileTree({\n data,\n defaultExpanded = {},\n onSelect,\n}: MotionFileTreeProps) {\n const [expanded, setExpanded] =\n useState>(defaultExpanded);\n const [selected, setSelected] = useState(null);\n\n const toggle = (id: string) => {\n setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));\n };\n\n const renderNodes = (nodes: FileNode[], level = 0) => {\n return nodes.map((n) => (\n
\n {\n if (n.type === \"folder\") toggle(n.id);\n setSelected(n.id);\n onSelect?.(n);\n }}\n onKeyDown={(e) => {\n if (n.type === \"folder\" && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n toggle(n.id);\n }\n }}\n >\n {n.type === \"folder\" ? (\n <>\n {expanded[n.id] ? (\n \n ) : (\n \n )}\n \n \n ) : (\n \n )}\n {n.name}\n
\n\n {/* Children with smooth animation */}\n \n {n.children && n.children.length > 0 && expanded[n.id] && (\n \n {renderNodes(n.children, level + 1)}\n \n )}\n \n \n ));\n };\n\n return (\n
\n {renderNodes(data)}\n
\n );\n}\n", "type": "registry:ui", "target": "components/ruixen/motion-file-tree.tsx" } ] }