import { useEffect, useState } from 'react'; import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; interface Repository { name: string; branches: string; prs: string; workspaces: string; lastCommit: string; } export const TableSelectableIndeterminate: React.FunctionComponent = () => { // In real usage, this data would come from some external source like an API via props. const repositories: Repository[] = [ { name: 'one', branches: 'two', prs: 'a', workspaces: 'four', lastCommit: 'five' }, { name: 'a', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' }, { name: 'b', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' }, { name: 'c', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' }, { name: 'd', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' }, { name: 'e', branches: 'two', prs: 'b', workspaces: 'four', lastCommit: 'five' } ]; const columnNames = { name: 'Repositories', branches: 'Branches', prs: 'Pull requests', workspaces: 'Workspaces', lastCommit: 'Last commit' }; const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example const selectableRepos = repositories.filter(isRepoSelectable); // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier. // This is to prevent state from being based on row order index in case we later add sorting. const [selectedRepoNames, setSelectedRepoNames] = useState([]); const setRepoSelected = (repo: Repository, isSelecting = true) => setSelectedRepoNames((prevSelected) => { const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name); return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames; }); const selectAllRepos = (isSelecting = true) => setSelectedRepoNames(isSelecting ? selectableRepos.map((r) => r.name) : []); const areAllReposSelected = selectedRepoNames.length === selectableRepos.length; const areSomeReposSelected = selectedRepoNames.length > 0 && selectedRepoNames.length < selectableRepos.length; const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name); // To allow shift+click to select/deselect multiple rows const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState(null); const [shifting, setShifting] = useState(false); const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => { // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected if (shifting && recentSelectedRowIndex !== null) { const numberSelected = rowIndex - recentSelectedRowIndex; const intermediateIndexes = numberSelected > 0 ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex) : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex); intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting)); } else { setRepoSelected(repo, isSelecting); } setRecentSelectedRowIndex(rowIndex); }; useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Shift') { setShifting(true); } }; const onKeyUp = (e: KeyboardEvent) => { if (e.key === 'Shift') { setShifting(false); } }; document.addEventListener('keydown', onKeyDown); document.addEventListener('keyup', onKeyUp); return () => { document.removeEventListener('keydown', onKeyDown); document.removeEventListener('keyup', onKeyUp); }; }, []); return ( {repositories.map((repo, rowIndex) => ( ))}
selectAllRepos(isSelecting), isSelected: areAllReposSelected, isIndeterminate: areSomeReposSelected }} aria-label="Row select" /> {columnNames.name} {columnNames.branches} {columnNames.prs} {columnNames.workspaces} {columnNames.lastCommit}
onSelectRepo(repo, rowIndex, isSelecting), isSelected: isRepoSelected(repo), isDisabled: !isRepoSelectable(repo) }} /> {repo.name} {repo.branches} {repo.prs} {repo.workspaces} {repo.lastCommit}
); };