Skip to content

Commit

Permalink
fix(tree): Optimize the code
Browse files Browse the repository at this point in the history
  • Loading branch information
fhlavac committed Sep 9, 2024
1 parent c8282d6 commit a3b5b7f
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 117 deletions.
28 changes: 17 additions & 11 deletions packages/module/src/DataViewTableHeader/DataViewTableHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import {
Th,
Thead,
Expand Down Expand Up @@ -26,19 +26,25 @@ export const DataViewTableHeader: React.FC<DataViewTableHeaderProps> = ({
const { selection } = useInternalContext();
const { onSelect, isSelected } = selection ?? {};

const cells = useMemo(() => [
onSelect && isSelected && !isTreeTable ? (
<Th key="row-select" screenReaderText='Data selection table header cell' />
) : null,
...columns.map((column, index) => (
<Th
key={index}
{...(isDataViewThObject(column) && (column?.props ?? {}))}
data-ouia-component-id={`${ouiaId}-th-${index}`}
>
{isDataViewThObject(column) ? column.cell : column}
</Th>
)
) ], [ columns, ouiaId, onSelect, isSelected, isTreeTable ]);

return (
<Thead data-ouia-component-id={`${ouiaId}-thead`} {...props}>
<Tr ouiaId={`${ouiaId}-tr-head`}>
{onSelect && isSelected && !isTreeTable && <Th key="row-select" screenReaderText='Data selection table header cell' />}
{columns.map((column, index) => (
<Th
key={index}
{...(isDataViewThObject(column) && (column?.props ?? {}))}
data-ouia-component-id={`${ouiaId}-th-${index}`}
>
{isDataViewThObject(column) ? column.cell : column}
</Th>
))}
{cells}
</Tr>
</Thead>
);
Expand Down
198 changes: 92 additions & 106 deletions packages/module/src/DataViewTableTree/DataViewTableTree.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import {
Table,
TableProps,
Expand All @@ -11,16 +11,34 @@ import { useInternalContext } from '../InternalContext';
import { DataViewTableHeader } from '../DataViewTableHeader';
import { DataViewTh, DataViewTrTree, isDataViewTdObject } from '../DataViewTable';

const getDescendants = (node: DataViewTrTree): DataViewTrTree[] => (!node.children || !node.children.length) ? [ node ] : node.children.flatMap(getDescendants);

const isNodeChecked = (node: DataViewTrTree, isSelected: (node: DataViewTrTree) => boolean) => {
let allSelected = true;
let someSelected = false;

for (const descendant of getDescendants(node)) {
const selected = !!isSelected?.(descendant);

someSelected ||= selected;
allSelected &&= selected;

if (!allSelected && someSelected) { return null }
}

return allSelected;
};

export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'rows'> {
/** Columns definition */
columns: DataViewTh[];
/** Current page rows */
rows: DataViewTrTree[];
/** Optinal icon for the leaf rows */
/** Optional icon for the leaf rows */
leafIcon?: React.ReactNode;
/** Optinal icon for the expanded parent rows */
/** Optional icon for the expanded parent rows */
expandedIcon?: React.ReactNode;
/** Optinal icon for the collapsed parent rows */
/** Optional icon for the collapsed parent rows */
collapsedIcon?: React.ReactNode;
/** Custom OUIA ID */
ouiaId?: string;
Expand All @@ -40,117 +58,85 @@ export const DataViewTableTree: React.FC<DataViewTableTreeProps> = ({
const [ expandedNodeIds, setExpandedNodeIds ] = React.useState<string[]>([]);
const [ expandedDetailsNodeNames, setExpandedDetailsNodeIds ] = React.useState<string[]>([]);

const getDescendants = (node: DataViewTrTree): DataViewTrTree[] => {
if (!node.children || !node.children.length) {
return [ node ];
} else {
let children: DataViewTrTree[] = [];
node.children.forEach((child) => {
children = [ ...children, ...getDescendants(child) ];
});
return children;
}
};

const areAllDescendantsSelected = (node: DataViewTrTree) => getDescendants(node).every((n) => isSelected?.(n));
const areSomeDescendantsSelected = (node: DataViewTrTree) => getDescendants(node).some((n) => isSelected?.(n));

const isNodeChecked = (node: DataViewTrTree) => {
if (areAllDescendantsSelected(node)) {
return true;
}
if (areSomeDescendantsSelected(node)) {
return null;
}
return false;
};

/**
Recursive function which flattens the data into an array of flattened TreeRowWrapper components
params:
- nodes - array of a single level of tree nodes
- level - number representing how deeply nested the current row is
- posinset - position of the row relative to this row's siblings
- currentRowIndex - position of the row relative to the entire table
- isHidden - defaults to false, true if this row's parent is expanded
*/
const renderRows = (
[ node, ...remainingNodes ]: DataViewTrTree[],
level = 1,
posinset = 1,
rowIndex = 0,
isHidden = false
): React.ReactNode[] => {
if (!node) {
return [];
}
const isExpanded = expandedNodeIds.includes(node.id);
const isDetailsExpanded = expandedDetailsNodeNames.includes(node.id);
const isChecked = isNodeChecked(node);
let icon = leafIcon;
if (node.children) {
icon = isExpanded ? expandedIcon : collapsedIcon;
}

const treeRow: TdProps['treeRow'] = {
onCollapse: () =>
setExpandedNodeIds((prevExpanded) => {
const otherExpandedNodeIds = prevExpanded.filter((id) => id !== node.id);
return isExpanded ? otherExpandedNodeIds : [ ...otherExpandedNodeIds, node.id ];
}),
onToggleRowDetails: () =>
setExpandedDetailsNodeIds((prevDetailsExpanded) => {
const otherDetailsExpandedNodeIds = prevDetailsExpanded.filter((id) => id !== node.id);
return isDetailsExpanded ? otherDetailsExpandedNodeIds : [ ...otherDetailsExpandedNodeIds, node.id ];
}),
onCheckChange: (isSelectDisabled?.(node) || !onSelect) ? undefined : (_event, isChecking) => onSelect?.(isChecking, getDescendants(node)),
rowIndex,
props: {
isExpanded,
isDetailsExpanded,
isHidden,
'aria-level': level,
'aria-posinset': posinset,
'aria-setsize': node.children?.length ?? 0,
isChecked,
ouiaId: `${ouiaId}-tree-toggle-${node.id}`,
checkboxId: `checkbox_id_${node.id?.toLowerCase().replace(/\s+/g, '_')}`,
icon,
const nodes = useMemo(() => {

const renderRows = (
[ node, ...remainingNodes ]: DataViewTrTree[],
level = 1,
posinset = 1,
rowIndex = 0,
isHidden = false
): React.ReactNode[] => {
if (!node) {
return [];
}
};
const isExpanded = expandedNodeIds.includes(node.id);
const isDetailsExpanded = expandedDetailsNodeNames.includes(node.id);
const isChecked = isSelected && isNodeChecked(node, isSelected);
let icon = leafIcon;
if (node.children) {
icon = isExpanded ? expandedIcon : collapsedIcon;
}

const treeRow: TdProps['treeRow'] = {
onCollapse: () =>
setExpandedNodeIds(prevExpanded => {
const otherExpandedNodeIds = prevExpanded.filter(id => id !== node.id);
return isExpanded ? otherExpandedNodeIds : [ ...otherExpandedNodeIds, node.id ];
}),
onToggleRowDetails: () =>
setExpandedDetailsNodeIds(prevDetailsExpanded => {
const otherDetailsExpandedNodeIds = prevDetailsExpanded.filter(id => id !== node.id);
return isDetailsExpanded ? otherDetailsExpandedNodeIds : [ ...otherDetailsExpandedNodeIds, node.id ];
}),
onCheckChange: (isSelectDisabled?.(node) || !onSelect) ? undefined : (_event, isChecking) => onSelect?.(isChecking, getDescendants(node)),
rowIndex,
props: {
isExpanded,
isDetailsExpanded,
isHidden,
'aria-level': level,
'aria-posinset': posinset,
'aria-setsize': node.children?.length ?? 0,
isChecked,
ouiaId: `${ouiaId}-tree-toggle-${node.id}`,
checkboxId: `checkbox_id_${node.id?.toLowerCase().replace(/\s+/g, '_')}`,
icon,
},
};

const childRows =
node.children && node.children.length
const childRows = node.children?.length
? renderRows(node.children, level + 1, 1, rowIndex + 1, !isExpanded || isHidden)
: [];

return [
<TreeRowWrapper key={node.id} row={{ props: treeRow.props }}>
{node.row.map((cell, colIndex) => {
const cellIsObject = isDataViewTdObject(cell);
return (
<Td
key={colIndex}
treeRow={colIndex === 0 ? treeRow : undefined}
{...(cellIsObject && (cell?.props ?? {}))}
data-ouia-component-id={`${ouiaId}-td-${rowIndex}-${colIndex}`}
>
{cellIsObject ? cell.cell : cell}
</Td>
)
})}
</TreeRowWrapper>,
...childRows,
...renderRows(remainingNodes, level, posinset + 1, rowIndex + 1 + childRows.length, isHidden)
];
};
return [
<TreeRowWrapper key={node.id} row={{ props: treeRow.props }}>
{node.row.map((cell, colIndex) => {
const cellIsObject = isDataViewTdObject(cell);
return (
<Td
key={colIndex}
treeRow={colIndex === 0 ? treeRow : undefined}
{...(cellIsObject && (cell?.props ?? {}))}
data-ouia-component-id={`${ouiaId}-td-${rowIndex}-${colIndex}`}
>
{cellIsObject ? cell.cell : cell}
</Td>
);
})}
</TreeRowWrapper>,
...childRows,
...renderRows(remainingNodes, level, posinset + 1, rowIndex + 1 + childRows.length, isHidden),
];
};

return renderRows(rows);
}, [ rows, expandedNodeIds, expandedDetailsNodeNames, leafIcon, expandedIcon, collapsedIcon, isSelected, onSelect, isSelectDisabled, ouiaId ]);

return (
<Table isTreeTable aria-label="Data table" ouiaId={ouiaId} {...props}>
<DataViewTableHeader isTreeTable columns={columns} ouiaId={ouiaId} />
<Tbody>
{renderRows(rows)}
</Tbody>
<Tbody>{nodes}</Tbody>
</Table>
);
};
Expand Down

0 comments on commit a3b5b7f

Please sign in to comment.