Skip to content

Commit 4d23d65

Browse files
feat(NavigationTree): add collapseChildlessNodes option
1 parent 3f62244 commit 4d23d65

File tree

4 files changed

+103
-77
lines changed

4 files changed

+103
-77
lines changed

src/components/NavigationTree/NavigationTree.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {EmptyView} from './EmptyView/EmptyView';
66
import {ErrorView} from './ErrorView/ErrorView';
77
import {LoaderView} from './LoaderView/LoaderView';
88
import {NavigationTreeNode} from './NavigationTreeNode';
9-
import {getNodeState, reducer, selectTreeAsList} from './state';
9+
import {createNavigationTreeReducer, getNodeState, selectTreeAsList} from './state';
1010
import type {
1111
NavigationTreeNodeState,
1212
NavigationTreeProps,
@@ -40,10 +40,16 @@ export function NavigationTree({
4040
onActivePathUpdate,
4141
cache = true,
4242
virtualize = false,
43+
// Temporary feature until all stable ydb versions have ChildrenExist flag
44+
// https://github.com/ydb-platform/ydb/pull/13307
45+
collapseChildlessNodes = false,
4346
}: NavigationTreeProps) {
44-
const [state, dispatch] = React.useReducer(reducer, {
45-
[partialRootState.path]: getNodeState(partialRootState),
46-
});
47+
const [state, dispatch] = React.useReducer(
48+
createNavigationTreeReducer({collapseChildlessNodes}),
49+
{
50+
[partialRootState.path]: getNodeState(partialRootState),
51+
},
52+
);
4753
const nodesList = React.useMemo(
4854
() => selectTreeAsList(state, partialRootState.path),
4955
[partialRootState.path, state],

src/components/NavigationTree/__stories__/NavigationTree.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default {
1616
args: {
1717
cache: true,
1818
virtualize: false,
19+
collapseChildlessNodes: false,
1920
rootState: {
2021
path: '',
2122
name: 'ru/maps/maps_prod',

src/components/NavigationTree/state.ts

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -70,82 +70,100 @@ export function getNodeState(
7070
};
7171
}
7272

73-
export function reducer(state: NavigationTreeState = {}, action: NavigationTreeAction) {
74-
switch (action.type) {
75-
case NavigationTreeActionType.ToggleCollapsed:
76-
return {
77-
...state,
78-
[action.payload.path]: {
79-
...state[action.payload.path],
80-
collapsed: !state[action.payload.path].collapsed,
81-
},
82-
};
83-
case NavigationTreeActionType.StartLoading:
84-
return {
85-
...state,
86-
[action.payload.path]: {
87-
...state[action.payload.path],
88-
loading: true,
89-
loaded: false,
90-
error: false,
91-
children: [],
92-
},
93-
};
94-
case NavigationTreeActionType.FinishLoading: {
95-
const newState: NavigationTreeState = {
96-
...state,
97-
[action.payload.path]: {
98-
...state[action.payload.path],
99-
loading: false,
100-
loaded: Boolean(action.payload.data),
101-
error: false,
102-
},
103-
};
104-
105-
if (action.payload.data) {
106-
newState[action.payload.path].children = action.payload.data.map(
107-
({name}: {name: string}) => name,
108-
);
109-
110-
for (const item of action.payload.data) {
111-
const path = `${action.payload.path}/${item.name}`;
112-
113-
// expand the tree according to the active path
114-
// prioritize the existing state to expand the tree only on first load
115-
const {activePath = ''} = action.payload;
116-
const collapsed = state[path]?.collapsed ?? !activePath.startsWith(`${path}/`);
117-
118-
newState[path] = getNodeState({
119-
...item,
120-
collapsed,
121-
path,
122-
});
73+
interface NavigationTreeReducerOptions {
74+
collapseChildlessNodes?: boolean;
75+
}
76+
77+
export function createNavigationTreeReducer(options: NavigationTreeReducerOptions) {
78+
return function reducer(state: NavigationTreeState = {}, action: NavigationTreeAction) {
79+
switch (action.type) {
80+
case NavigationTreeActionType.ToggleCollapsed:
81+
return {
82+
...state,
83+
[action.payload.path]: {
84+
...state[action.payload.path],
85+
collapsed: !state[action.payload.path].collapsed,
86+
},
87+
};
88+
case NavigationTreeActionType.StartLoading:
89+
return {
90+
...state,
91+
[action.payload.path]: {
92+
...state[action.payload.path],
93+
loading: true,
94+
loaded: false,
95+
error: false,
96+
children: [],
97+
},
98+
};
99+
case NavigationTreeActionType.FinishLoading: {
100+
const newState: NavigationTreeState = {
101+
...state,
102+
[action.payload.path]: {
103+
...state[action.payload.path],
104+
loading: false,
105+
loaded: Boolean(action.payload.data),
106+
error: false,
107+
},
108+
};
109+
110+
if (action.payload.data) {
111+
newState[action.payload.path].children = action.payload.data.map(
112+
({name}: {name: string}) => name,
113+
);
114+
115+
for (const item of action.payload.data) {
116+
const path = `${action.payload.path}/${item.name}`;
117+
118+
// expand the tree according to the active path
119+
// prioritize the existing state to expand the tree only on first load
120+
const {activePath = ''} = action.payload;
121+
const collapsed =
122+
state[path]?.collapsed ?? !activePath.startsWith(`${path}/`);
123+
124+
newState[path] = getNodeState({
125+
...item,
126+
collapsed,
127+
path,
128+
});
129+
}
130+
}
131+
132+
if (
133+
(!action.payload.data || action.payload.data.length === 0) &&
134+
options.collapseChildlessNodes
135+
) {
136+
newState[action.payload.path] = {
137+
...newState[action.payload.path],
138+
expandable: false,
139+
collapsed: true,
140+
};
123141
}
124-
}
125142

126-
return newState;
143+
return newState;
144+
}
145+
case NavigationTreeActionType.ErrorLoading:
146+
return {
147+
...state,
148+
[action.payload.path]: {
149+
...state[action.payload.path],
150+
loading: false,
151+
loaded: false,
152+
error: true,
153+
},
154+
};
155+
case NavigationTreeActionType.ResetNode:
156+
return {
157+
...state,
158+
[action.payload.path]: {
159+
...state[action.payload.path],
160+
...getDefaultNodeState(),
161+
},
162+
};
163+
default:
164+
return state;
127165
}
128-
case NavigationTreeActionType.ErrorLoading:
129-
return {
130-
...state,
131-
[action.payload.path]: {
132-
...state[action.payload.path],
133-
loading: false,
134-
loaded: false,
135-
error: true,
136-
},
137-
};
138-
case NavigationTreeActionType.ResetNode:
139-
return {
140-
...state,
141-
[action.payload.path]: {
142-
...state[action.payload.path],
143-
...getDefaultNodeState(),
144-
},
145-
};
146-
default:
147-
return state;
148-
}
166+
};
149167
}
150168

151169
export function selectTreeAsList(state: NavigationTreeState, rootPath: string) {

src/components/NavigationTree/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ export interface NavigationTreeProps<D = any> {
6767
onActivePathUpdate?: (activePath: string) => void;
6868
cache?: boolean;
6969
virtualize?: boolean;
70+
collapseChildlessNodes?: boolean;
7071
}

0 commit comments

Comments
 (0)