diff --git a/src/components/atoms/ComponentTreeNode.tsx b/src/components/atoms/ComponentTreeNode.tsx
index 186bede..8b42047 100644
--- a/src/components/atoms/ComponentTreeNode.tsx
+++ b/src/components/atoms/ComponentTreeNode.tsx
@@ -17,6 +17,9 @@ interface ComponentTreeNodeProps {
onDragLeave: (e: React.DragEvent) => void
onDrop: (e: React.DragEvent) => void
depth?: number
+ hasChildren?: boolean
+ isExpanded?: boolean
+ onToggleExpand?: () => void
}
export function ComponentTreeNode({
@@ -33,10 +36,12 @@ export function ComponentTreeNode({
onDragLeave,
onDrop,
depth = 0,
+ hasChildren = false,
+ isExpanded = false,
+ onToggleExpand,
}: ComponentTreeNodeProps) {
const def = getComponentDef(component.type)
const IconComponent = def ? (Icons as any)[def.icon] || Icons.Cube : Icons.Cube
- const hasChildren = Array.isArray(component.children) && component.children.length > 0
return (
@@ -67,7 +72,19 @@ export function ComponentTreeNode({
)}
>
{hasChildren ? (
-
+
) : (
)}
diff --git a/src/components/molecules/ComponentTree.tsx b/src/components/molecules/ComponentTree.tsx
index 72cd9e6..6290fd4 100644
--- a/src/components/molecules/ComponentTree.tsx
+++ b/src/components/molecules/ComponentTree.tsx
@@ -1,9 +1,10 @@
+import { useState, useCallback } from 'react'
import { UIComponent } from '@/types/json-ui'
import { ComponentTreeNode } from '@/components/atoms/ComponentTreeNode'
import { PanelHeader } from '@/components/atoms'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Button } from '@/components/ui/button'
-import { Tree, Plus } from '@phosphor-icons/react'
+import { Tree, CaretDown, CaretRight } from '@phosphor-icons/react'
interface ComponentTreeProps {
components: UIComponent[]
@@ -34,39 +35,108 @@ export function ComponentTree({
onDragLeave,
onDrop,
}: ComponentTreeProps) {
+ const [expandedIds, setExpandedIds] = useState
>(new Set())
+
+ const getAllComponentIds = useCallback((comps: UIComponent[]): string[] => {
+ const ids: string[] = []
+ const traverse = (components: UIComponent[]) => {
+ components.forEach((comp) => {
+ if (Array.isArray(comp.children) && comp.children.length > 0) {
+ ids.push(comp.id)
+ traverse(comp.children)
+ }
+ })
+ }
+ traverse(comps)
+ return ids
+ }, [])
+
+ const handleExpandAll = useCallback(() => {
+ const allIds = getAllComponentIds(components)
+ setExpandedIds(new Set(allIds))
+ }, [components, getAllComponentIds])
+
+ const handleCollapseAll = useCallback(() => {
+ setExpandedIds(new Set())
+ }, [])
+
+ const toggleExpand = useCallback((id: string) => {
+ setExpandedIds((prev) => {
+ const next = new Set(prev)
+ if (next.has(id)) {
+ next.delete(id)
+ } else {
+ next.add(id)
+ }
+ return next
+ })
+ }, [])
+
const renderTree = (comps: UIComponent[], depth = 0) => {
- return comps.map((comp) => (
-
-
onSelect(comp.id)}
- onHover={() => onHover(comp.id)}
- onHoverEnd={onHoverEnd}
- onDragStart={(e) => onDragStart(comp.id, e)}
- onDragOver={(e) => onDragOver(comp.id, e)}
- onDragLeave={onDragLeave}
- onDrop={(e) => onDrop(comp.id, e)}
- depth={depth}
- />
- {Array.isArray(comp.children) && comp.children.length > 0 && (
- {renderTree(comp.children, depth + 1)}
- )}
-
- ))
+ return comps.map((comp) => {
+ const hasChildren = Array.isArray(comp.children) && comp.children.length > 0
+ const isExpanded = expandedIds.has(comp.id)
+
+ return (
+
+
onSelect(comp.id)}
+ onHover={() => onHover(comp.id)}
+ onHoverEnd={onHoverEnd}
+ onDragStart={(e) => onDragStart(comp.id, e)}
+ onDragOver={(e) => onDragOver(comp.id, e)}
+ onDragLeave={onDragLeave}
+ onDrop={(e) => onDrop(comp.id, e)}
+ depth={depth}
+ hasChildren={hasChildren}
+ isExpanded={isExpanded}
+ onToggleExpand={() => toggleExpand(comp.id)}
+ />
+ {hasChildren && isExpanded && comp.children && (
+ {renderTree(comp.children, depth + 1)}
+ )}
+
+ )
+ })
}
return (
-
}
- />
+
+
}
+ />
+ {components.length > 0 && (
+
+
+
+
+ )}
+