Skip to content

Commit a3b5b7f

Browse files
committed
fix(tree): Optimize the code
1 parent c8282d6 commit a3b5b7f

File tree

2 files changed

+109
-117
lines changed

2 files changed

+109
-117
lines changed

packages/module/src/DataViewTableHeader/DataViewTableHeader.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22
import {
33
Th,
44
Thead,
@@ -26,19 +26,25 @@ export const DataViewTableHeader: React.FC<DataViewTableHeaderProps> = ({
2626
const { selection } = useInternalContext();
2727
const { onSelect, isSelected } = selection ?? {};
2828

29+
const cells = useMemo(() => [
30+
onSelect && isSelected && !isTreeTable ? (
31+
<Th key="row-select" screenReaderText='Data selection table header cell' />
32+
) : null,
33+
...columns.map((column, index) => (
34+
<Th
35+
key={index}
36+
{...(isDataViewThObject(column) && (column?.props ?? {}))}
37+
data-ouia-component-id={`${ouiaId}-th-${index}`}
38+
>
39+
{isDataViewThObject(column) ? column.cell : column}
40+
</Th>
41+
)
42+
) ], [ columns, ouiaId, onSelect, isSelected, isTreeTable ]);
43+
2944
return (
3045
<Thead data-ouia-component-id={`${ouiaId}-thead`} {...props}>
3146
<Tr ouiaId={`${ouiaId}-tr-head`}>
32-
{onSelect && isSelected && !isTreeTable && <Th key="row-select" screenReaderText='Data selection table header cell' />}
33-
{columns.map((column, index) => (
34-
<Th
35-
key={index}
36-
{...(isDataViewThObject(column) && (column?.props ?? {}))}
37-
data-ouia-component-id={`${ouiaId}-th-${index}`}
38-
>
39-
{isDataViewThObject(column) ? column.cell : column}
40-
</Th>
41-
))}
47+
{cells}
4248
</Tr>
4349
</Thead>
4450
);

packages/module/src/DataViewTableTree/DataViewTableTree.tsx

Lines changed: 92 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22
import {
33
Table,
44
TableProps,
@@ -11,16 +11,34 @@ import { useInternalContext } from '../InternalContext';
1111
import { DataViewTableHeader } from '../DataViewTableHeader';
1212
import { DataViewTh, DataViewTrTree, isDataViewTdObject } from '../DataViewTable';
1313

14+
const getDescendants = (node: DataViewTrTree): DataViewTrTree[] => (!node.children || !node.children.length) ? [ node ] : node.children.flatMap(getDescendants);
15+
16+
const isNodeChecked = (node: DataViewTrTree, isSelected: (node: DataViewTrTree) => boolean) => {
17+
let allSelected = true;
18+
let someSelected = false;
19+
20+
for (const descendant of getDescendants(node)) {
21+
const selected = !!isSelected?.(descendant);
22+
23+
someSelected ||= selected;
24+
allSelected &&= selected;
25+
26+
if (!allSelected && someSelected) { return null }
27+
}
28+
29+
return allSelected;
30+
};
31+
1432
export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'rows'> {
1533
/** Columns definition */
1634
columns: DataViewTh[];
1735
/** Current page rows */
1836
rows: DataViewTrTree[];
19-
/** Optinal icon for the leaf rows */
37+
/** Optional icon for the leaf rows */
2038
leafIcon?: React.ReactNode;
21-
/** Optinal icon for the expanded parent rows */
39+
/** Optional icon for the expanded parent rows */
2240
expandedIcon?: React.ReactNode;
23-
/** Optinal icon for the collapsed parent rows */
41+
/** Optional icon for the collapsed parent rows */
2442
collapsedIcon?: React.ReactNode;
2543
/** Custom OUIA ID */
2644
ouiaId?: string;
@@ -40,117 +58,85 @@ export const DataViewTableTree: React.FC<DataViewTableTreeProps> = ({
4058
const [ expandedNodeIds, setExpandedNodeIds ] = React.useState<string[]>([]);
4159
const [ expandedDetailsNodeNames, setExpandedDetailsNodeIds ] = React.useState<string[]>([]);
4260

43-
const getDescendants = (node: DataViewTrTree): DataViewTrTree[] => {
44-
if (!node.children || !node.children.length) {
45-
return [ node ];
46-
} else {
47-
let children: DataViewTrTree[] = [];
48-
node.children.forEach((child) => {
49-
children = [ ...children, ...getDescendants(child) ];
50-
});
51-
return children;
52-
}
53-
};
54-
55-
const areAllDescendantsSelected = (node: DataViewTrTree) => getDescendants(node).every((n) => isSelected?.(n));
56-
const areSomeDescendantsSelected = (node: DataViewTrTree) => getDescendants(node).some((n) => isSelected?.(n));
57-
58-
const isNodeChecked = (node: DataViewTrTree) => {
59-
if (areAllDescendantsSelected(node)) {
60-
return true;
61-
}
62-
if (areSomeDescendantsSelected(node)) {
63-
return null;
64-
}
65-
return false;
66-
};
67-
68-
/**
69-
Recursive function which flattens the data into an array of flattened TreeRowWrapper components
70-
params:
71-
- nodes - array of a single level of tree nodes
72-
- level - number representing how deeply nested the current row is
73-
- posinset - position of the row relative to this row's siblings
74-
- currentRowIndex - position of the row relative to the entire table
75-
- isHidden - defaults to false, true if this row's parent is expanded
76-
*/
77-
const renderRows = (
78-
[ node, ...remainingNodes ]: DataViewTrTree[],
79-
level = 1,
80-
posinset = 1,
81-
rowIndex = 0,
82-
isHidden = false
83-
): React.ReactNode[] => {
84-
if (!node) {
85-
return [];
86-
}
87-
const isExpanded = expandedNodeIds.includes(node.id);
88-
const isDetailsExpanded = expandedDetailsNodeNames.includes(node.id);
89-
const isChecked = isNodeChecked(node);
90-
let icon = leafIcon;
91-
if (node.children) {
92-
icon = isExpanded ? expandedIcon : collapsedIcon;
93-
}
94-
95-
const treeRow: TdProps['treeRow'] = {
96-
onCollapse: () =>
97-
setExpandedNodeIds((prevExpanded) => {
98-
const otherExpandedNodeIds = prevExpanded.filter((id) => id !== node.id);
99-
return isExpanded ? otherExpandedNodeIds : [ ...otherExpandedNodeIds, node.id ];
100-
}),
101-
onToggleRowDetails: () =>
102-
setExpandedDetailsNodeIds((prevDetailsExpanded) => {
103-
const otherDetailsExpandedNodeIds = prevDetailsExpanded.filter((id) => id !== node.id);
104-
return isDetailsExpanded ? otherDetailsExpandedNodeIds : [ ...otherDetailsExpandedNodeIds, node.id ];
105-
}),
106-
onCheckChange: (isSelectDisabled?.(node) || !onSelect) ? undefined : (_event, isChecking) => onSelect?.(isChecking, getDescendants(node)),
107-
rowIndex,
108-
props: {
109-
isExpanded,
110-
isDetailsExpanded,
111-
isHidden,
112-
'aria-level': level,
113-
'aria-posinset': posinset,
114-
'aria-setsize': node.children?.length ?? 0,
115-
isChecked,
116-
ouiaId: `${ouiaId}-tree-toggle-${node.id}`,
117-
checkboxId: `checkbox_id_${node.id?.toLowerCase().replace(/\s+/g, '_')}`,
118-
icon,
61+
const nodes = useMemo(() => {
62+
63+
const renderRows = (
64+
[ node, ...remainingNodes ]: DataViewTrTree[],
65+
level = 1,
66+
posinset = 1,
67+
rowIndex = 0,
68+
isHidden = false
69+
): React.ReactNode[] => {
70+
if (!node) {
71+
return [];
11972
}
120-
};
73+
const isExpanded = expandedNodeIds.includes(node.id);
74+
const isDetailsExpanded = expandedDetailsNodeNames.includes(node.id);
75+
const isChecked = isSelected && isNodeChecked(node, isSelected);
76+
let icon = leafIcon;
77+
if (node.children) {
78+
icon = isExpanded ? expandedIcon : collapsedIcon;
79+
}
80+
81+
const treeRow: TdProps['treeRow'] = {
82+
onCollapse: () =>
83+
setExpandedNodeIds(prevExpanded => {
84+
const otherExpandedNodeIds = prevExpanded.filter(id => id !== node.id);
85+
return isExpanded ? otherExpandedNodeIds : [ ...otherExpandedNodeIds, node.id ];
86+
}),
87+
onToggleRowDetails: () =>
88+
setExpandedDetailsNodeIds(prevDetailsExpanded => {
89+
const otherDetailsExpandedNodeIds = prevDetailsExpanded.filter(id => id !== node.id);
90+
return isDetailsExpanded ? otherDetailsExpandedNodeIds : [ ...otherDetailsExpandedNodeIds, node.id ];
91+
}),
92+
onCheckChange: (isSelectDisabled?.(node) || !onSelect) ? undefined : (_event, isChecking) => onSelect?.(isChecking, getDescendants(node)),
93+
rowIndex,
94+
props: {
95+
isExpanded,
96+
isDetailsExpanded,
97+
isHidden,
98+
'aria-level': level,
99+
'aria-posinset': posinset,
100+
'aria-setsize': node.children?.length ?? 0,
101+
isChecked,
102+
ouiaId: `${ouiaId}-tree-toggle-${node.id}`,
103+
checkboxId: `checkbox_id_${node.id?.toLowerCase().replace(/\s+/g, '_')}`,
104+
icon,
105+
},
106+
};
121107

122-
const childRows =
123-
node.children && node.children.length
108+
const childRows = node.children?.length
124109
? renderRows(node.children, level + 1, 1, rowIndex + 1, !isExpanded || isHidden)
125110
: [];
126111

127-
return [
128-
<TreeRowWrapper key={node.id} row={{ props: treeRow.props }}>
129-
{node.row.map((cell, colIndex) => {
130-
const cellIsObject = isDataViewTdObject(cell);
131-
return (
132-
<Td
133-
key={colIndex}
134-
treeRow={colIndex === 0 ? treeRow : undefined}
135-
{...(cellIsObject && (cell?.props ?? {}))}
136-
data-ouia-component-id={`${ouiaId}-td-${rowIndex}-${colIndex}`}
137-
>
138-
{cellIsObject ? cell.cell : cell}
139-
</Td>
140-
)
141-
})}
142-
</TreeRowWrapper>,
143-
...childRows,
144-
...renderRows(remainingNodes, level, posinset + 1, rowIndex + 1 + childRows.length, isHidden)
145-
];
146-
};
112+
return [
113+
<TreeRowWrapper key={node.id} row={{ props: treeRow.props }}>
114+
{node.row.map((cell, colIndex) => {
115+
const cellIsObject = isDataViewTdObject(cell);
116+
return (
117+
<Td
118+
key={colIndex}
119+
treeRow={colIndex === 0 ? treeRow : undefined}
120+
{...(cellIsObject && (cell?.props ?? {}))}
121+
data-ouia-component-id={`${ouiaId}-td-${rowIndex}-${colIndex}`}
122+
>
123+
{cellIsObject ? cell.cell : cell}
124+
</Td>
125+
);
126+
})}
127+
</TreeRowWrapper>,
128+
...childRows,
129+
...renderRows(remainingNodes, level, posinset + 1, rowIndex + 1 + childRows.length, isHidden),
130+
];
131+
};
132+
133+
return renderRows(rows);
134+
}, [ rows, expandedNodeIds, expandedDetailsNodeNames, leafIcon, expandedIcon, collapsedIcon, isSelected, onSelect, isSelectDisabled, ouiaId ]);
147135

148136
return (
149137
<Table isTreeTable aria-label="Data table" ouiaId={ouiaId} {...props}>
150138
<DataViewTableHeader isTreeTable columns={columns} ouiaId={ouiaId} />
151-
<Tbody>
152-
{renderRows(rows)}
153-
</Tbody>
139+
<Tbody>{nodes}</Tbody>
154140
</Table>
155141
);
156142
};

0 commit comments

Comments
 (0)