Skip to content

Commit 345b27c

Browse files
authored
feat: add animation and fix a content wrapping issue in the tree component (#43)
1 parent 81fc66a commit 345b27c

File tree

2 files changed

+52
-26
lines changed

2 files changed

+52
-26
lines changed

packages/ui/src/components/Tree/Tree.tsx

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const TreeNodeComponent = component$(
2323
expandLevel: number;
2424
onNodeClick: QRL<(node: TreeNode) => void>;
2525
renderNode?: QRL<(node: TreeNode) => JSXOutput>;
26+
animate?: boolean;
27+
animationDuration?: number;
2628
}) => {
2729
const isExpanded = useSignal(props.expandLevel <= props.level); // Default to expanded
2830
const hasChildren = props.node.children && props.node.children.length > 0;
@@ -50,10 +52,28 @@ const TreeNodeComponent = component$(
5052
const isActive = props.isHover
5153
? props.node.id === props.activeNodeId
5254
: false;
55+
const duration = props.animationDuration ?? 200;
56+
const shouldShowChildren = hasChildren && !isExpanded.value;
57+
const renderChildren = props.node.children?.map((child) => (
58+
<TreeNodeComponent
59+
isHover={props.isHover}
60+
key={child.id}
61+
node={child}
62+
gap={props.gap}
63+
expandLevel={props.expandLevel}
64+
level={props.level + 1}
65+
activeNodeId={props.activeNodeId}
66+
onNodeClick={props.onNodeClick}
67+
renderNode={props.renderNode}
68+
animate={props.animate}
69+
animationDuration={props.animationDuration}
70+
/>
71+
));
5372
return (
54-
<div style={{ paddingLeft: `${props.level * props.gap}px` }}>
73+
<div class="w-full">
5574
<div
56-
class={`flex cursor-pointer items-center rounded-md p-1 transition-colors duration-150
75+
style={{ paddingLeft: `${props.level * props.gap}px` }}
76+
class={`flex w-full cursor-pointer items-center rounded-md p-1 transition-colors duration-150
5777
${
5878
isActive
5979
? 'bg-primary text-white '
@@ -68,31 +88,30 @@ const TreeNodeComponent = component$(
6888
) : (
6989
<div class="mr-2 w-4 flex-shrink-0"></div>
7090
)}
71-
<span class="text-sm">
91+
<div class="text-sm whitespace-nowrap cursor-pointer">
7292
{props.renderNode ? (
7393
<>{props.renderNode(props.node)}</>
7494
) : (
7595
`<${props.node.label || props.node.name} ${iterateProps(props.node.props! || {})}>`
7696
)}
77-
</span>
97+
</div>
7898
</div>
79-
{!isExpanded.value && hasChildren && (
80-
<>
81-
{props.node.children?.map((child) => (
82-
<TreeNodeComponent
83-
isHover={props.isHover}
84-
key={child.id}
85-
node={child}
86-
gap={props.gap}
87-
expandLevel={props.expandLevel}
88-
level={props.level + 1}
89-
activeNodeId={props.activeNodeId}
90-
onNodeClick={props.onNodeClick}
91-
renderNode={props.renderNode}
92-
/>
93-
))}
94-
</>
95-
)}
99+
{hasChildren ? (
100+
props.animate ? (
101+
<div
102+
class={`overflow-hidden transition-all ease-in-out`}
103+
style={{
104+
maxHeight: shouldShowChildren ? '1000px' : '0px',
105+
opacity: shouldShowChildren ? '1' : '0',
106+
transition: `max-height ${duration}ms ease-in-out, opacity ${duration}ms ease-in-out`,
107+
}}
108+
>
109+
{renderChildren}
110+
</div>
111+
) : (
112+
shouldShowChildren && <>{renderChildren}</>
113+
)
114+
) : null}
96115
</div>
97116
);
98117
},
@@ -105,6 +124,9 @@ export const Tree = component$(
105124
renderNode?: QRL<(node: TreeNode) => JSXOutput>;
106125
gap?: number;
107126
isHover?: boolean;
127+
animate?: boolean;
128+
animationDuration?: number;
129+
expandLevel?: number;
108130
}) => {
109131
const ref = useSignal<HTMLElement | undefined>();
110132
const store = props.data;
@@ -120,15 +142,17 @@ export const Tree = component$(
120142
<div class="h-full w-full overflow-x-auto overflow-y-auto" ref={ref}>
121143
{store.value.map((rootNode) => (
122144
<TreeNodeComponent
123-
isHover={props.isHover ?? true}
145+
isHover={props.isHover === false ? false : true}
124146
gap={props.gap || 20}
125147
key={rootNode.id}
126148
node={rootNode}
127149
level={0}
128-
expandLevel={2}
150+
expandLevel={props.expandLevel ?? 2}
129151
activeNodeId={activeNodeId.value}
130152
onNodeClick={setActiveNode}
131153
renderNode={props.renderNode}
154+
animate={props.animate ?? true}
155+
animationDuration={props.animationDuration}
132156
/>
133157
))}
134158
</div>

packages/ui/src/features/RenderTree/RenderTree.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,11 @@ export const RenderTree = component$(() => {
125125
return (
126126
<div class="h-full w-full flex-1 overflow-hidden rounded-md border border-border">
127127
<div class="flex h-full w-full">
128-
<div class="w-[50%] overflow-hidden p-4">
128+
<div class="w-1/2 overflow-hidden p-4" style={{minWidth: '400px'}}>
129129
<Tree data={data} onNodeClick={onNodeClick}></Tree>
130130
</div>
131131
<div class="border-l border-border"></div>
132-
<div class="flex h-full w-[50%] flex-col p-4">
132+
<div class="flex h-full w-1/2 flex-col p-4">
133133
<div class="border-b border-border">
134134
<div class="flex space-x-4 border-b border-border">
135135
<button
@@ -162,7 +162,9 @@ export const RenderTree = component$(() => {
162162
<Tree
163163
data={stateTree}
164164
gap={10}
165-
isHover={false}
165+
animate
166+
animationDuration={200}
167+
isHover
166168
renderNode={$((node) => {
167169
const label = node.label || node.name || '';
168170
const isProperty = label.split(':');

0 commit comments

Comments
 (0)