Skip to content

Commit 0440c0d

Browse files
authored
Merge pull request #100 from GamerGirlandCo/feat/custom-ids
[feat] allow any type for tree node IDs
2 parents d45c4d4 + bae2b43 commit 0440c0d

22 files changed

+352
-176
lines changed

example/src/App.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from './screens/SelectionPropagationScreens';
1818
import packageJson from '../../package.json';
1919
import { TwoTreeViewsScreen } from "./screens/TwoTreeViewsScreen";
20+
import CustomNodeID from './screens/CustomNodeIDScreen';
2021

2122
const data: ShowcaseExampleScreenSectionType[] = [
2223
{
@@ -57,6 +58,11 @@ const data: ShowcaseExampleScreenSectionType[] = [
5758
slug: 'custom-row-item',
5859
getScreen: () => CustomNodeRowViewScreen,
5960
},
61+
{
62+
name: 'Custom Node ID',
63+
slug: 'custom-node-id',
64+
getScreen: () => CustomNodeID
65+
}
6066
],
6167
},
6268
{
@@ -104,4 +110,4 @@ export default function App() {
104110
data={data}
105111
/>
106112
);
107-
}
113+
}

example/src/components/CustomNodeRowView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { TouchableOpacity, View, Text, StyleSheet } from "react-native";
33
import { NodeRowProps } from "react-native-tree-multi-select";
44
import Icon from 'react-native-vector-icons/FontAwesome';
55

6-
export const CustomNodeRowView = React.memo(_CustomNodeRowView);
6+
export const CustomNodeRowView = React.memo(_CustomNodeRowView) as typeof _CustomNodeRowView;
77

88
const VerticalLine = () => (
99
<View style={styles.verticalLineStyle} />
@@ -23,7 +23,7 @@ const Levels = ({
2323
);
2424
};
2525

26-
function _CustomNodeRowView(props: NodeRowProps) {
26+
function _CustomNodeRowView<ID = string>(props: NodeRowProps<ID>) {
2727
const { node, level, checkedValue, isExpanded, onCheck, onExpand } = props;
2828

2929
const backgroundColor =

example/src/screens/CustomArrowScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import {
1616
} from 'react-native-tree-multi-select';
1717

1818
import { styles } from './screens.styles';
19-
import { generateTreeList } from '../utils/sampleDataGenerator';
19+
import { defaultID, generateTreeList } from '../utils/sampleDataGenerator';
2020
import { CustomArrow } from '../components/CustomArrow';
2121

2222
export default function CustomArrowScreen() {
23-
const sampleData = React.useRef(generateTreeList(50, 4, 5));
23+
const sampleData = React.useRef(generateTreeList(50, 4, 5, defaultID, "1"));
2424
const treeViewRef = React.useRef<TreeViewRef | null>(null);
2525

2626
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -99,4 +99,4 @@ export default function CustomArrowScreen() {
9999
</View>
100100
</SafeAreaView>
101101
);
102-
}
102+
}

example/src/screens/CustomCheckboxScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import {
1616
} from 'react-native-tree-multi-select';
1717

1818
import { styles } from './screens.styles';
19-
import { generateTreeList } from '../utils/sampleDataGenerator';
19+
import { defaultID, generateTreeList } from '../utils/sampleDataGenerator';
2020
import { CustomCheckboxView } from '../components/CustomCheckboxView';
2121

2222
export default function CustomCheckboxScreen() {
23-
const sampleData = React.useRef(generateTreeList(50, 4, 5));
23+
const sampleData = React.useRef(generateTreeList(50, 4, 5, defaultID, "1"));
2424
const treeViewRef = React.useRef<TreeViewRef | null>(null);
2525

2626
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -99,4 +99,4 @@ export default function CustomCheckboxScreen() {
9999
</View>
100100
</SafeAreaView>
101101
);
102-
}
102+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { debounce } from "lodash";
2+
import React, { useEffect, useRef } from "react";
3+
import { SafeAreaView, View, Button } from "react-native";
4+
import { SelectionPropagation, TreeViewRef, TreeView } from "react-native-tree-multi-select";
5+
import SearchInput from "../components/SearchInput";
6+
import { generateTreeList, TreeNode } from "../utils/sampleDataGenerator";
7+
import { styles } from "./screens.styles";
8+
import { CustomNodeRowView } from "../components/CustomNodeRowView";
9+
10+
interface Props {
11+
selectionPropagation?: SelectionPropagation;
12+
}
13+
14+
const customMapper: (parentName?: string) => (it: TreeNode<number>, idx: number) => TreeNode<number> = (parentStr?: string) => (it: TreeNode<number>, idx: number) => {
15+
const name = `${parentStr ? `${parentStr}.` : ''}${idx + 1}`;
16+
return {
17+
...it,
18+
name,
19+
children: it.children?.map(customMapper(name)) ?? []
20+
} as TreeNode<number>
21+
}
22+
23+
export default function CustomNodeID(props: Props) {
24+
const { selectionPropagation } = props;
25+
26+
const idRef = useRef<number>(1);
27+
28+
useEffect(() => {
29+
return () => {
30+
idRef.current = 1
31+
};
32+
}, [])
33+
34+
const sampleData = React.useMemo(() => generateTreeList<number>(30, 4, 5, (_prev, _idx) => idRef.current++, 1).map(customMapper()), []);
35+
console.log(sampleData);
36+
const treeViewRef = React.useRef<TreeViewRef<number> | null>(null);
37+
38+
// eslint-disable-next-line react-hooks/exhaustive-deps
39+
const debouncedSetSearchText = React.useCallback(
40+
debounce((text) => treeViewRef.current?.setSearchText(text), 375, {
41+
leading: true,
42+
trailing: true,
43+
maxWait: 750
44+
}),
45+
[]
46+
);
47+
48+
const handleSelectionChange = (
49+
_checkedIds: number[],
50+
_indeterminateIds: number[]
51+
) => {
52+
// NOTE: Handle _checkedIds and _indeterminateIds here
53+
};
54+
const handleExpanded = (_expandedIds: number[]) => {
55+
// NOTE: Handle _expandedIds here
56+
};
57+
58+
// Expand collapse calls using ref
59+
const expandAllPress = () => treeViewRef.current?.expandAll?.();
60+
const collapseAllPress = () => treeViewRef.current?.collapseAll?.();
61+
62+
// Multi-select calls using ref
63+
const onSelectAllPress = () => treeViewRef.current?.selectAll?.();
64+
const onUnselectAllPress = () => treeViewRef.current?.unselectAll?.();
65+
const onSelectAllFilteredPress = () => treeViewRef.current?.selectAllFiltered?.();
66+
const onUnselectAllFilteredPress = () => treeViewRef.current?.unselectAllFiltered?.();
67+
68+
return (
69+
<SafeAreaView
70+
style={styles.mainView}>
71+
<SearchInput onChange={debouncedSetSearchText} />
72+
<View
73+
style={styles.selectionButtonRow}>
74+
<Button
75+
title='Select All'
76+
onPress={onSelectAllPress} />
77+
<Button
78+
title='Unselect All'
79+
onPress={onUnselectAllPress} />
80+
</View>
81+
<View
82+
style={styles.selectionButtonRow}>
83+
<Button
84+
title='Select Filtered'
85+
onPress={onSelectAllFilteredPress} />
86+
<Button
87+
title='Unselect Filtered'
88+
onPress={onUnselectAllFilteredPress} />
89+
</View>
90+
91+
<View
92+
style={[styles.selectionButtonRow, styles.selectionButtonBottom]}>
93+
<Button
94+
title='Expand All'
95+
onPress={expandAllPress} />
96+
<Button
97+
title='Collapse All'
98+
onPress={collapseAllPress} />
99+
</View>
100+
101+
<View
102+
style={styles.treeViewParent}>
103+
<TreeView<number>
104+
ref={treeViewRef}
105+
data={sampleData}
106+
onCheck={handleSelectionChange}
107+
onExpand={handleExpanded}
108+
selectionPropagation={selectionPropagation}
109+
CustomNodeRowComponent={CustomNodeRowView}
110+
/>
111+
</View>
112+
</SafeAreaView>
113+
);
114+
}

example/src/screens/CustomNodeRowViewScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import {
1616
} from 'react-native-tree-multi-select';
1717

1818
import { styles } from './screens.styles';
19-
import { generateTreeList } from '../utils/sampleDataGenerator';
19+
import { defaultID, generateTreeList } from '../utils/sampleDataGenerator';
2020
import { CustomNodeRowView } from '../components/CustomNodeRowView';
2121

2222
export default function CustomNodeRowViewScreen() {
23-
const sampleData = React.useRef(generateTreeList(50, 4, 5));
23+
const sampleData = React.useRef(generateTreeList(50, 4, 5, defaultID, "1"));
2424
const treeViewRef = React.useRef<TreeViewRef | null>(null);
2525

2626
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -99,4 +99,4 @@ export default function CustomNodeRowViewScreen() {
9999
</View>
100100
</SafeAreaView>
101101
);
102-
}
102+
}

example/src/screens/LargeDataScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import {
1616
} from 'react-native-tree-multi-select';
1717

1818
import { styles } from './screens.styles';
19-
import { generateTreeList } from '../utils/sampleDataGenerator';
19+
import { defaultID, generateTreeList } from '../utils/sampleDataGenerator';
2020

2121
export default function LargeDataScreen() {
22-
const sampleData = React.useRef(generateTreeList(200, 5, 6));
22+
const sampleData = React.useRef(generateTreeList(200, 5, 6, defaultID, "1"));
2323
const treeViewRef = React.useRef<TreeViewRef | null>(null);
2424

2525
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -95,4 +95,4 @@ export default function LargeDataScreen() {
9595
</View>
9696
</SafeAreaView>
9797
);
98-
}
98+
}

example/src/screens/MediumDataScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import {
1616
} from 'react-native-tree-multi-select';
1717

1818
import { styles } from './screens.styles';
19-
import { generateTreeList } from '../utils/sampleDataGenerator';
19+
import { defaultID, generateTreeList } from '../utils/sampleDataGenerator';
2020

2121
export default function MediumDataScreen() {
22-
const sampleData = React.useRef(generateTreeList(20, 4, 5));
22+
const sampleData = React.useRef(generateTreeList(20, 4, 5, defaultID, "1"));
2323
const treeViewRef = React.useRef<TreeViewRef | null>(null);
2424

2525
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -96,4 +96,4 @@ export default function MediumDataScreen() {
9696
</View>
9797
</SafeAreaView>
9898
);
99-
}
99+
}

example/src/screens/SmallDataScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from 'react-native-tree-multi-select';
1818

1919
import { styles } from './screens.styles';
20-
import { generateTreeList } from '../utils/sampleDataGenerator';
20+
import { defaultID, generateTreeList } from '../utils/sampleDataGenerator';
2121

2222
interface Props {
2323
selectionPropagation?: SelectionPropagation;
@@ -26,7 +26,7 @@ interface Props {
2626
export default function SmallDataScreen(props: Props) {
2727
const { selectionPropagation } = props;
2828

29-
const sampleData = React.useRef(generateTreeList(5, 4, 3));
29+
const sampleData = React.useRef(generateTreeList(5, 4, 3, defaultID, `1`));
3030
const treeViewRef = React.useRef<TreeViewRef | null>(null);
3131

3232
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -104,4 +104,4 @@ export default function SmallDataScreen(props: Props) {
104104
</View>
105105
</SafeAreaView>
106106
);
107-
}
107+
}
Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
1-
interface TreeNode {
2-
id: string;
1+
export interface TreeNode<ID> {
2+
id: ID;
33
name: string;
4-
children?: TreeNode[];
4+
children?: TreeNode<ID>[];
55
}
66

77
// This function generates a TreeNode with a random number of children.
8-
function generateNode(id: string, level: number, maxLevel: number, maxChildren: number): TreeNode {
9-
const node: TreeNode = {
10-
id: id,
11-
name: `Node ${id}`,
8+
function generateNode<ID = string>(id: ID, idx: number, level: number, maxLevel: number, maxChildren: number, nextID: (prev: ID, idx: number, parent?: ID) => ID, parentID?: ID): TreeNode<ID> {
9+
let nid = nextID(id, idx, parentID);
10+
const node: TreeNode<ID> = {
11+
id: nid,
12+
name: `Node ${nid}`,
1213
};
1314

1415
if (level < maxLevel) {
1516
const numChildren = Math.floor(Math.random() * maxChildren) + 1; // generates a random number between 1 and maxChildren
1617
node.children = [];
1718
for (let i = 1; i <= numChildren; i++) {
18-
node.children.push(generateNode(`${id}.${i}`, level + 1, maxLevel, maxChildren));
19+
node.children.push(generateNode(nid, i, level + 1, maxLevel, maxChildren, nextID, node.id));
20+
nid = nextID(nid, idx, node.id);
1921
}
2022
}
2123

2224
return node;
2325
}
2426

2527
// This function generates a list of TreeNodes
26-
export function generateTreeList(num: number, maxLevel: number, maxChildren: number): TreeNode[] {
27-
let result: TreeNode[] = [];
28+
export function generateTreeList<ID = string>(num: number, maxLevel: number, maxChildren: number, nextID: (prev: ID, idx: number) => ID, initialValue: ID): TreeNode<ID>[] {
29+
let result: TreeNode<ID>[] = [];
30+
let curID = initialValue;
2831
for (let i = 1; i <= num; i++) {
29-
result.push(generateNode(`${i}`, 1, maxLevel, maxChildren));
32+
result.push(generateNode(curID, i, 1, maxLevel, maxChildren, nextID));
33+
curID = nextID(curID, i);
3034
}
3135
return result;
32-
}
36+
}
37+
38+
export const defaultID = (_prev: string, idx: number, parentID?: string) => `${parentID ? parentID + '.' : ''}${idx}`

0 commit comments

Comments
 (0)