import { useState } from 'react'; import { treeRow, IRow, OnTreeRowCollapse, OnCheckChange, OnToggleRowDetails } from '@patternfly/react-table'; import { Table, TableHeader, TableBody } from '@patternfly/react-table/deprecated'; import LeafIcon from '@patternfly/react-icons/dist/esm/icons/leaf-icon'; import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon'; import FolderOpenIcon from '@patternfly/react-icons/dist/esm/icons/folder-open-icon'; interface RepositoriesTreeNode { name: string; branches: string; pullRequests: string; workspaces: string; children?: RepositoriesTreeNode[]; } export const LegacyTableTree: React.FunctionComponent = () => { // In real usage, this data would come from some external source like an API via props. const data: RepositoriesTreeNode[] = [ { name: 'Repositories one', branches: 'Branch one', pullRequests: 'Pull request one', workspaces: 'Workplace one', children: [ { name: 'Repositories two', branches: 'Branch two', pullRequests: 'Pull request two', workspaces: 'Workplace two', children: [ { name: 'Repositories three', branches: 'Branch three', pullRequests: 'Pull request three', workspaces: 'Workplace three' }, { name: 'Repositories four', branches: 'Branch four', pullRequests: 'Pull request four', workspaces: 'Workplace four' } ] }, { name: 'Repositories five', branches: 'Branch five', pullRequests: 'Pull request five', workspaces: 'Workplace five' }, { name: 'Repositories six', branches: 'Branch six', pullRequests: 'Pull request six', workspaces: 'Workplace six' } ] }, { name: 'Repositories seven', branches: 'Branch seven', pullRequests: 'Pull request seven', workspaces: 'Workplace seven', children: [ { name: 'Repositories eight', branches: 'Branch eight', pullRequests: 'Pull request eight', workspaces: 'Workplace eight' } ] }, { name: 'Repositories nine', branches: 'Branch nine', pullRequests: 'Pull request nine', workspaces: 'Workplace nine' } ]; const [expandedNodeNames, setExpandedNodeNames] = useState(['Repositories one']); const [expandedDetailsNodeNames, setExpandedDetailsNodeNames] = useState([]); const [selectedNodeNames, setSelectedNodeNames] = useState([]); const getDescendants = (node: RepositoriesTreeNode): RepositoriesTreeNode[] => [node].concat(...(node.children ? node.children.map(getDescendants) : [])); const areAllDescendantsSelected = (node: RepositoriesTreeNode) => getDescendants(node).every((n) => selectedNodeNames.includes(n.name)); const areSomeDescendantsSelected = (node: RepositoriesTreeNode) => getDescendants(node).some((n) => selectedNodeNames.includes(n.name)); const isNodeChecked = (node: RepositoriesTreeNode) => { if (areAllDescendantsSelected(node)) { return true; } if (areSomeDescendantsSelected(node)) { return null; } return false; }; // We index the tree nodes in the order of the table rows, for looking up by rowIndex const flattenedNodes: RepositoriesTreeNode[] = []; /** Recursive function which flattens the data into an array of flattened IRow objects 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 - isHidden - defaults to false, true if this row's parent is expanded - currentRowIndex - position of the row relative to the entire table */ const buildRows = ( [node, ...remainingNodes]: RepositoriesTreeNode[], level = 1, posinset = 1, rowIndex = 0, isHidden = false ): IRow[] => { if (!node) { return []; } const isExpanded = expandedNodeNames.includes(node.name); const isDetailsExpanded = expandedDetailsNodeNames.includes(node.name); const isChecked = isNodeChecked(node); let icon = ; if (node.children) { icon = isExpanded ? : ; } flattenedNodes.push(node); const childRows = node.children && node.children.length ? buildRows(node.children, level + 1, 1, rowIndex + 1, !isExpanded || isHidden) : []; return [ { cells: [node.name, node.branches, node.pullRequests, node.workspaces], props: { isExpanded, isDetailsExpanded, isHidden, 'aria-level': level, 'aria-posinset': posinset, 'aria-setsize': node.children ? node.children.length : 0, isChecked, icon } }, ...childRows, ...buildRows(remainingNodes, level, posinset + 1, rowIndex + 1 + childRows.length, isHidden) ]; }; const onCollapse: OnTreeRowCollapse = (_event, rowIndex) => { const node = flattenedNodes[rowIndex]; const isExpanded = expandedNodeNames.includes(node.name); setExpandedNodeNames((prevExpanded) => { const otherExpandedNodeNames = prevExpanded.filter((name) => name !== node.name); return isExpanded ? otherExpandedNodeNames : [...otherExpandedNodeNames, node.name]; }); }; const onCheck: OnCheckChange = (_event, isChecking, rowIndex) => { const node = flattenedNodes[rowIndex]; const nodeNamesToCheck = getDescendants(node).map((n) => n.name); setSelectedNodeNames((prevSelected) => { const otherSelectedNodeNames = prevSelected.filter((name) => !nodeNamesToCheck.includes(name)); return !isChecking ? otherSelectedNodeNames : [...otherSelectedNodeNames, ...nodeNamesToCheck]; }); }; const onToggleRowDetails: OnToggleRowDetails = (_event, rowIndex) => { const node = flattenedNodes[rowIndex]; const isDetailsExpanded = expandedDetailsNodeNames.includes(node.name); setExpandedDetailsNodeNames((prevDetailsExpanded) => { const otherDetailsExpandedNodeNames = prevDetailsExpanded.filter((name) => name !== node.name); return isDetailsExpanded ? otherDetailsExpandedNodeNames : [...otherDetailsExpandedNodeNames, node.name]; }); }; return (
); };