Skip to content

Commit cb96993

Browse files
committed
✨: Add unselect feature
1 parent f2ed768 commit cb96993

File tree

10 files changed

+89
-26
lines changed

10 files changed

+89
-26
lines changed

src/features/app/app.page.css

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
main {
22
display: grid;
33
height: 100%;
4-
grid-template-rows: auto 1fr;
4+
grid-template-rows: auto max-content 1fr;
55
padding: 20px;
66
}
7-
8-
main > div {
9-
position: relative;
10-
}

src/features/app/app.page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import { MyTree } from "./components/my-tree";
55
const App = () => (
66
<main>
77
<h1>TREE COMPONENT</h1>
8-
<div>
9-
<MyTree />
10-
</div>
8+
<MyTree />
119
</main>
1210
);
1311

14-
export default App;
12+
export default App;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.tree-container {
2+
position: relative;
3+
}

src/features/app/components/my-tree.tsx

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
import './my-tree-node.css'
2+
import './my-tree.css'
3+
14
import { MyTreeNode, MyTreeNodeData } from "./my-tree-node";
25
import {
36
Tree,
7+
TreeNodeComponentRef,
48
TreeOnDragType,
59
TreeOnLoadType,
610
TreeOnRemoveType,
711
TreeOnSelectType,
812
} from "../../../shared/components/tree";
9-
10-
import { useCallback } from "react";
13+
import { useCallback, useRef } from "react";
1114

1215
export const MyTree = () => {
16+
const treeRef = useRef<TreeNodeComponentRef>(null);
17+
1318
const handleLoad: TreeOnLoadType<MyTreeNodeData> = useCallback((node) => {
1419
const DELAY_IN_MS = 500;
1520
const NUMBER_OF_ELEMENTS_FOR_LEVEL = 10;
@@ -59,14 +64,26 @@ export const MyTree = () => {
5964
[]
6065
);
6166

67+
const handleUnSelect = () => {
68+
treeRef.current?.unSelect();
69+
}
70+
6271
return (
63-
<Tree
64-
nodeHeigth={40}
65-
Node={MyTreeNode}
66-
onLoad={handleLoad}
67-
onDrag={handleDrag}
68-
onRemove={handleRemove}
69-
onSelect={handleOnSelect}
70-
/>
72+
<>
73+
<div>
74+
<button type="button" onClick={handleUnSelect} >UnSelect</button>
75+
</div>
76+
<div className='tree-container'>
77+
<Tree
78+
ref={treeRef}
79+
nodeHeigth={40}
80+
Node={MyTreeNode}
81+
onLoad={handleLoad}
82+
onDrag={handleDrag}
83+
onRemove={handleRemove}
84+
onSelect={handleOnSelect}
85+
/>
86+
</div>
87+
</>
7188
);
7289
};

src/shared/components/tree/hooks/tree.hook.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,16 @@ export const useTree = <TData>({ onLoad, onRemove, onSelect }: UseTreeProps<TDat
7474
onSelect(node);
7575
};
7676

77+
const handleUnSelect = () => {
78+
dispatch({type: 'UNSELECT_NODE'});
79+
}
80+
7781
return {
7882
tree,
7983
dispatch,
8084
handleToggle,
8185
handleRemove,
82-
handleSelect
86+
handleSelect,
87+
handleUnSelect
8388
};
8489
};

src/shared/components/tree/models/tree.model.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,14 @@ export const selectNode = <TData>(
139139
children: rootNode.children.map((rootNode) => selectNode(rootNode, node)),
140140
} as TreeNodeInternal<TData>;
141141
};
142+
143+
export const unSelectNode = <TData>(
144+
node: TreeNodeInternal<TData>,
145+
): TreeNodeInternal<TData> => {
146+
return {
147+
...node,
148+
selected: false,
149+
children: node.children.map(unSelectNode),
150+
} as TreeNodeInternal<TData>;
151+
};
152+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { TreeState } from "../tree.state";
2+
import { unSelectNode } from "../../models/tree.model";
3+
4+
export type UnSelectNodeAction = {
5+
type: "UNSELECT_NODE";
6+
};
7+
8+
export const unSelectNodeActionHandler = <TData>(
9+
state: TreeState<TData>,
10+
): TreeState<TData> => {
11+
12+
return {
13+
...state,
14+
nodes: state.nodes.map((rootNode) =>
15+
unSelectNode(rootNode)
16+
),
17+
};
18+
};

src/shared/components/tree/state/tree.reducer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { removeNodeActionHandler } from "./actions/remove-nodeAction";
55
import { selectNodeActionHandler } from "./actions/select-node.action";
66
import { setStatusNodeActionHandler } from "./actions/set-status-node.action";
77
import { toggleActionHandler } from "./actions/toggle-node.action";
8+
import { unSelectNodeActionHandler } from "./actions/unselect-node.action";
89
import { TreeAction, TreeState } from "./tree.state";
910

1011
export type TreeReducer<TData> = typeof treeReducer<TData>;
@@ -24,6 +25,7 @@ export const treeReducer = <TData>(
2425
MOVE_NODE: moveNodeActionHandler,
2526
SET_STATUS_NODE: setStatusNodeActionHandler,
2627
SELECT_NODE: selectNodeActionHandler,
28+
UNSELECT_NODE: unSelectNodeActionHandler
2729
};
2830

2931
return actionFn[action.type]?.(state, action) ?? state;

src/shared/components/tree/state/tree.state.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SelectNodeAction } from "./actions/select-node.action";
66
import { SetStatusNodeAction } from "./actions/set-status-node.action";
77
import { ToggleNodeAction } from "./actions/toggle-node.action";
88
import { TreeNodeInternal } from "../models/tree.model";
9+
import { UnSelectNodeAction } from './actions/unselect-node.action';
910

1011
export type TreeState<TData> = {
1112
nodes: TreeNodeInternal<TData>[];
@@ -18,4 +19,5 @@ export type TreeAction<TData> =
1819
| RemoveNodeAction<TData>
1920
| MoveNodeAction<TData>
2021
| SetStatusNodeAction<TData>
21-
| SelectNodeAction<TData>;
22+
| SelectNodeAction<TData>
23+
| UnSelectNodeAction;

src/shared/components/tree/tree.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import React, { Ref, RefAttributes, forwardRef, useImperativeHandle } from "react";
12
import { TreeOnDragType, useTreeDraggable } from "./hooks/tree-draggable.hook";
23
import { TreeOnLoadType, TreeOnRemoveType, TreeOnSelectType, useTree } from "./hooks/tree.hook";
34

45
import AutoSizer from "react-virtualized-auto-sizer";
56
import { FixedSizeList } from "react-window";
6-
import React from "react";
77
import { TreeNode } from "./models/tree.model";
88

9+
export type TreeNodeComponentRef = {
10+
unSelect: () => void;
11+
}
12+
913
export type TreeNodeComponent<TData> = (_: {
1014
level: number;
1115
loading: boolean;
@@ -27,20 +31,27 @@ type TreeProps<TData> = {
2731
onSelect: TreeOnSelectType<TData>;
2832
};
2933

30-
export function Tree<TData>({
34+
export const Tree = forwardRef(<TData extends object>({
3135
nodeHeigth,
3236
Node,
3337
onDrag,
3438
...props
35-
}: TreeProps<TData>) {
36-
const { tree, dispatch, handleToggle, handleRemove, handleSelect } = useTree(props);
39+
}: TreeProps<TData>, ref: Ref<TreeNodeComponentRef>) => {
40+
const { tree, dispatch, handleToggle, handleRemove, handleSelect, handleUnSelect } = useTree(props);
3741

3842
const draggablePropsFn = useTreeDraggable<TData>((node, parentNode) => {
3943
onDrag(node, parentNode).then(() => {
4044
dispatch({ type: "MOVE_NODE", payload: { node, parentNode } });
4145
});
4246
});
4347

48+
useImperativeHandle(
49+
ref,
50+
() => ({
51+
unSelect: handleUnSelect
52+
})
53+
)
54+
4455
return (
4556
<AutoSizer>
4657
{({ width, height }) => (
@@ -73,4 +84,4 @@ export function Tree<TData>({
7384
)}
7485
</AutoSizer>
7586
);
76-
}
87+
}) as <TData extends object> (props: TreeProps<TData> & RefAttributes<TreeNodeComponentRef>) => JSX.Element;

0 commit comments

Comments
 (0)