From fd5acc69055eb6afd92ac2f0936f9d9a935cd167 Mon Sep 17 00:00:00 2001 From: Kate Zaprazna Date: Wed, 8 Jul 2026 23:48:36 +0200 Subject: [PATCH] feat(table): add support for traditional expandable rows --- ...DataViewTableCompoundExpandableExample.tsx | 108 ++++++++++++++++++ .../Table/DataViewTableExpandableExample.tsx | 89 +++------------ .../Table/DataViewTableInteractiveExample.tsx | 6 +- .../data-view/examples/Table/Table.md | 38 +++--- .../module/patternfly-docs/generated/index.js | 4 +- .../DataViewTableBasic.test.tsx | 105 ++++++++++++++++- .../DataViewTableBasic/DataViewTableBasic.tsx | 36 ++++-- 7 files changed, 278 insertions(+), 108 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableCompoundExpandableExample.tsx diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableCompoundExpandableExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableCompoundExpandableExample.tsx new file mode 100644 index 00000000..f899a93d --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableCompoundExpandableExample.tsx @@ -0,0 +1,108 @@ +import { FunctionComponent } from 'react'; +import { DataViewTable, DataViewTr, DataViewTh, ExpandableContent } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import { Button } from '@patternfly/react-core'; +import { ActionsColumn } from '@patternfly/react-table'; + +interface Repository { + id: number; + name: string; + branches: string | null; + prs: string | null; + workspaces: string; + lastCommit: string; +} + +const expandableContents: ExpandableContent[] = [ + // Row 1 - Repository one + { rowId: 1, columnId: 3, content:
PR Details: 3 open PRs, 45 merged this month, avg review time: 2 days
}, + { rowId: 1, columnId: 5, content:
Commit Info: Author: John Doe, Message: "Fix critical authentication bug", SHA: a1b2c3d
}, + + // Row 2 - Repository two + { rowId: 2, columnId: 2, content:
Branch Details: 8 active branches, main, staging, feature/api-v2, feature/dashboard
}, + { rowId: 2, columnId: 3, content:
PR Details: 5 open PRs, 120 merged this month, avg review time: 1.5 days
}, + { rowId: 2, columnId: 4, content:
Workspace Info: Development env, 3 active deployments, last updated 30 mins ago
}, + { rowId: 2, columnId: 5, content:
Commit Info: Author: Jane Smith, Message: "Add new API endpoints", SHA: x9y8z7w
}, + + // Row 3 - Repository three + { rowId: 3, columnId: 2, content:
Branch Details: 12 active branches including main, develop, multiple feature branches
}, + { rowId: 3, columnId: 3, content:
PR Details: 8 open PRs, 200 merged this month, avg review time: 3 days
}, + { rowId: 3, columnId: 4, content:
Workspace Info: Staging env, 10 active deployments, last updated 1 day ago
}, + { rowId: 3, columnId: 5, content:
Commit Info: Author: Bob Johnson, Message: "Refactor core modules", SHA: p0o9i8u
}, + + // Row 4 - Repository four + { rowId: 4, columnId: 2, content:
Branch Details: 6 active branches, focusing on microservices architecture
}, + { rowId: 4, columnId: 3, content:
PR Details: 2 open PRs, 90 merged this month, avg review time: 2.5 days
}, + { rowId: 4, columnId: 4, content:
Workspace Info: QA env, 7 active deployments, automated testing enabled
}, + { rowId: 4, columnId: 5, content:
Commit Info: Author: Alice Williams, Message: "Update dependencies", SHA: m5n4b3v
}, + + // Row 5 - Repository five + { rowId: 5, columnId: 2, content:
Branch Details: 4 active branches, clean branch strategy
}, + { rowId: 5, columnId: 3, content:
PR Details: 6 open PRs, 75 merged this month, avg review time: 1 day
}, + { rowId: 5, columnId: 4, content:
Workspace Info: Pre-production env, CI/CD pipeline configured
}, + { rowId: 5, columnId: 5, content:
Commit Info: Author: Charlie Brown, Message: "Implement dark mode", SHA: q2w3e4r
}, + + // Row 6 - Repository six + { rowId: 6, columnId: 2, content:
Branch Details: 15 active branches, complex branching model
}, + { rowId: 6, columnId: 3, content:
PR Details: 10 open PRs, 250 merged this month, avg review time: 4 days
}, + { rowId: 6, columnId: 4, content:
Workspace Info: Multi-region deployment, high availability setup
}, + { rowId: 6, columnId: 5, content:
Commit Info: Author: David Lee, Message: "Security patches applied", SHA: t6y7u8i
}, +]; + +const repositories: Repository[] = [ + { id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' }, + { id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' }, + { id: 3, name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' }, + { id: 4, name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' }, + { id: 5, name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' }, + { id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' } +]; + +const rowActions = [ + { + title: 'Some action', + onClick: () => console.log('clicked on Some action') // eslint-disable-line no-console + }, + { + title:
Another action
, + onClick: () => console.log('clicked on Another action') // eslint-disable-line no-console + }, + { + isSeparator: true + }, + { + title: 'Third action', + onClick: () => console.log('clicked on Third action') // eslint-disable-line no-console + } +]; + +const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [ + { + id, + cell: workspaces, + props: { + favorites: { isFavorited: true } + } + }, + { cell: }, + branches, + prs, + workspaces, + lastCommit, + { cell: , props: { isActionCell: true } }, +]); + +const columns: DataViewTh[] = [ + null, + 'Repositories', + { cell: <>Branches }, + 'Pull requests', + { cell: 'Workspaces', props: { info: { tooltip: 'More information' }, isStickyColumn: true } }, + { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } }, +]; + +const ouiaId = 'CompoundExpandableTableExample'; + +export const CompoundExpandableExample: FunctionComponent = () => ( + +); diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx index dc4c061b..ca426e00 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx @@ -1,54 +1,15 @@ import { FunctionComponent } from 'react'; import { DataViewTable, DataViewTr, DataViewTh, ExpandableContent } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; -import { ExclamationCircleIcon } from '@patternfly/react-icons'; -import { Button } from '@patternfly/react-core'; -import { ActionsColumn } from '@patternfly/react-table'; interface Repository { id: number; name: string; - branches: string | null; - prs: string | null; + branches: string; + prs: string; workspaces: string; lastCommit: string; } -const expandableContents: ExpandableContent[] = [ - // Row 1 - Repository one - { rowId: 1, columnId: 3, content:
PR Details: 3 open PRs, 45 merged this month, avg review time: 2 days
}, - { rowId: 1, columnId: 5, content:
Commit Info: Author: John Doe, Message: "Fix critical authentication bug", SHA: a1b2c3d
}, - - // Row 2 - Repository two - { rowId: 2, columnId: 2, content:
Branch Details: 8 active branches, main, staging, feature/api-v2, feature/dashboard
}, - { rowId: 2, columnId: 3, content:
PR Details: 5 open PRs, 120 merged this month, avg review time: 1.5 days
}, - { rowId: 2, columnId: 4, content:
Workspace Info: Development env, 3 active deployments, last updated 30 mins ago
}, - { rowId: 2, columnId: 5, content:
Commit Info: Author: Jane Smith, Message: "Add new API endpoints", SHA: x9y8z7w
}, - - // Row 3 - Repository three - { rowId: 3, columnId: 2, content:
Branch Details: 12 active branches including main, develop, multiple feature branches
}, - { rowId: 3, columnId: 3, content:
PR Details: 8 open PRs, 200 merged this month, avg review time: 3 days
}, - { rowId: 3, columnId: 4, content:
Workspace Info: Staging env, 10 active deployments, last updated 1 day ago
}, - { rowId: 3, columnId: 5, content:
Commit Info: Author: Bob Johnson, Message: "Refactor core modules", SHA: p0o9i8u
}, - - // Row 4 - Repository four - { rowId: 4, columnId: 2, content:
Branch Details: 6 active branches, focusing on microservices architecture
}, - { rowId: 4, columnId: 3, content:
PR Details: 2 open PRs, 90 merged this month, avg review time: 2.5 days
}, - { rowId: 4, columnId: 4, content:
Workspace Info: QA env, 7 active deployments, automated testing enabled
}, - { rowId: 4, columnId: 5, content:
Commit Info: Author: Alice Williams, Message: "Update dependencies", SHA: m5n4b3v
}, - - // Row 5 - Repository five - { rowId: 5, columnId: 2, content:
Branch Details: 4 active branches, clean branch strategy
}, - { rowId: 5, columnId: 3, content:
PR Details: 6 open PRs, 75 merged this month, avg review time: 1 day
}, - { rowId: 5, columnId: 4, content:
Workspace Info: Pre-production env, CI/CD pipeline configured
}, - { rowId: 5, columnId: 5, content:
Commit Info: Author: Charlie Brown, Message: "Implement dark mode", SHA: q2w3e4r
}, - - // Row 6 - Repository six - { rowId: 6, columnId: 2, content:
Branch Details: 15 active branches, complex branching model
}, - { rowId: 6, columnId: 3, content:
PR Details: 10 open PRs, 250 merged this month, avg review time: 4 days
}, - { rowId: 6, columnId: 4, content:
Workspace Info: Multi-region deployment, high availability setup
}, - { rowId: 6, columnId: 5, content:
Commit Info: Author: David Lee, Message: "Security patches applied", SHA: t6y7u8i
}, -]; - const repositories: Repository[] = [ { id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' }, { id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' }, @@ -58,50 +19,26 @@ const repositories: Repository[] = [ { id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' } ]; -const rowActions = [ - { - title: 'Some action', - onClick: () => console.log('clicked on Some action') // eslint-disable-line no-console - }, - { - title:
Another action
, - onClick: () => console.log('clicked on Another action') // eslint-disable-line no-console - }, - { - isSeparator: true - }, - { - title: 'Third action', - onClick: () => console.log('clicked on Third action') // eslint-disable-line no-console - } +const expandableContents: ExpandableContent[] = [ + { rowId: 1, content:
Detailed information for Repository one
}, + { rowId: 2, content:
Detailed information for Repository two
}, + { rowId: 3, content:
Detailed information for Repository three
}, + { rowId: 4, content:
Detailed information for Repository four
}, + { rowId: 5, content:
Detailed information for Repository five
}, + { rowId: 6, content:
Detailed information for Repository six
}, ]; const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [ - { - id, - cell: workspaces, - props: { - favorites: { isFavorited: true } - } - }, - { cell: }, + { id, cell: name }, branches, prs, workspaces, - lastCommit, - { cell: , props: { isActionCell: true } }, + lastCommit ]); -const columns: DataViewTh[] = [ - null, - 'Repositories', - { cell: <>Branches }, - 'Pull requests', - { cell: 'Workspaces', props: { info: { tooltip: 'More information' }, isStickyColumn: true } }, - { cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } }, -]; +const columns: DataViewTh[] = [ null, 'Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit' ]; -const ouiaId = 'TableExample'; +const ouiaId = 'ExpandableTableExample'; export const ExpandableExample: FunctionComponent = () => ( diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx index 4972770d..58ad7fed 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx @@ -114,11 +114,11 @@ export const InteractiveExample: FunctionComponent = () => { setIsExpandable(checked)} - aria-label="Toggle expandable rows" + aria-label="Toggle compound expandable rows" /> diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md index 2aa382ad..60b60034 100644 --- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md @@ -58,28 +58,26 @@ If you want to have all expandable nodes open on initial load pass the `expandAl ``` -## Expandable rows +## Adding expandable content to rows -To add expandable content to table cells, pass an array of `ExpandableContent` objects to the `expandedRows` prop of the `` component. Each expandable content object defines which cell can be expanded and what content to display when expanded. +To add expandable content to table rows, pass an array of `ExpandableContent` objects to the `expandedRows` prop of the `` component and set `isExpandable` to `true`. The `ExpandableContent` interface is defined as: ```typescript interface ExpandableContent { - /** The ID of the row containing the expandable cell (must match the id property in the row data) */ - rowId: number; - /** The column index (0-based) that should be expandable */ - columnId: number; - /** The content to display when the cell is expanded */ + rowId: string | number; + columnId?: number; content: ReactNode; } ``` +The `columnId` property is optional. Omit it for [expandable rows](#expandable-rows-example), which add an expand toggle as the first column. Provide it on all entries for [compound expandable rows](#compound-expandable-rows), where individual cells act as expand toggles. + +## Expandable rows -When a cell has expandable content: -- A compound expand toggle button appears in the cell -- Clicking the toggle expands the row to show the additional content below -- Only one expanded cell is shown per row at a time -- Clicking another expandable cell in the same row switches the expanded content +To make a parent/child row pair expandable, omit `columnId` from the expandable content entries: +- A toggle is added as the first column (no column header) +- Rows without matching expandable content display an empty toggle cell ### Expandable rows example @@ -87,6 +85,18 @@ When a cell has expandable content: ``` +## Compound expandable rows + +To make a parent/child row pair compound expandable, provide `columnId` on all expandable content entries: +- Individual cells with expandable content act as expand toggles +- Only one cell can be expanded per row at a time + +### Compound expandable rows example + +```js file="./DataViewTableCompoundExpandableExample.tsx" + +``` + ## Sticky header and columns To enable sticky headers and columns, set the `isSticky` prop to `true` on the `` component. This keeps the table header and designated columns visible when scrolling. @@ -112,8 +122,8 @@ When sticky headers and columns are enabled: ``` ### Interactive example -- Interactive example show how the different composable options work together. -- By toggling the toggles you can switch between them and observe the behaviour +- This interactive example shows how the different composable options work together +- By toggling the toggles, you can switch between them and observe the behaviour ```js file="./DataViewTableInteractiveExample.tsx" diff --git a/packages/module/patternfly-docs/generated/index.js b/packages/module/patternfly-docs/generated/index.js index 610e983e..be229904 100644 --- a/packages/module/patternfly-docs/generated/index.js +++ b/packages/module/patternfly-docs/generated/index.js @@ -14,8 +14,8 @@ module.exports = { '/extensions/data-view/table/react': { id: "Table", title: "Data view table", - toc: [{"text":"Configuring rows and columns"},[{"text":"Table example"}],{"text":"Expandable rows"},[{"text":"Expandable rows example"}],{"text":"Sticky header and columns"},[{"text":"Sticky header and columns example"},{"text":"Interactive example"},{"text":"Resizable columns"}],{"text":"Tree table"},[{"text":"Tree table example"}],{"text":"Sorting"},[{"text":"Sorting example"},{"text":"Sorting state"}],{"text":"States"},[{"text":"Empty"},{"text":"Error"},{"text":"Loading"}]], - examples: ["Table example","Expandable rows example","Sticky header and columns example","Interactive example","Resizable columns","Tree table example","Sorting example","Empty","Error","Loading"], + toc: [{"text":"Configuring rows and columns"},[{"text":"Table example"}],{"text":"Adding expandable content to rows"},{"text":"Expandable rows"},[{"text":"Expandable rows example"}],{"text":"Compound expandable rows"},[{"text":"Compound expandable rows example"}],{"text":"Sticky header and columns"},[{"text":"Sticky header and columns example"},{"text":"Interactive example"},{"text":"Resizable columns"}],{"text":"Tree table"},[{"text":"Tree table example"}],{"text":"Sorting"},[{"text":"Sorting example"},{"text":"Sorting state"}],{"text":"States"},[{"text":"Empty"},{"text":"Error"},{"text":"Loading"}]], + examples: ["Table example","Expandable rows example","Compound expandable rows example","Sticky header and columns example","Interactive example","Resizable columns","Tree table example","Sorting example","Empty","Error","Loading"], section: "extensions", subsection: "Data view", source: "react", diff --git a/packages/module/src/DataViewTableBasic/DataViewTableBasic.test.tsx b/packages/module/src/DataViewTableBasic/DataViewTableBasic.test.tsx index c52e1959..d6cf63c3 100644 --- a/packages/module/src/DataViewTableBasic/DataViewTableBasic.test.tsx +++ b/packages/module/src/DataViewTableBasic/DataViewTableBasic.test.tsx @@ -45,6 +45,19 @@ const objectExpandableContents: ExpandableContent[] = [ { rowId: '1', columnId: 1, content:
Branch details for Repository one
}, ]; +const standardExpandableContents: ExpandableContent[] = [ + { rowId: 1, content:
Details for Repository one
}, +]; + +const objectStandardExpandableContents: ExpandableContent[] = [ + { rowId: '1', content:
Details for Repository one
}, +]; + +// Only row 3 has content, other rows get an empty toggle cell +const partialStandardExpandableContents: ExpandableContent[] = [ + { rowId: 3, content:
Details for Repository three
}, +]; + const ouiaId = 'TableExample'; describe('DataViewTable component', () => { @@ -82,7 +95,7 @@ describe('DataViewTable component', () => { expect(container).toMatchSnapshot(); }); - test('when isExpandable cell should be clickable and expandable', async () => { + test('compound expandable rows: cell should be clickable and expandable', async () => { const user = userEvent.setup(); render( @@ -121,7 +134,7 @@ describe('DataViewTable component', () => { expect(container.querySelectorAll('tr').length).toBeGreaterThan(0); }); - test('when isExpandable with indexBy, expand button uses row id as key', async () => { + test('compound expandable rows with indexBy: expand button uses row id as key', async () => { const user = userEvent.setup(); render( @@ -171,4 +184,92 @@ describe('DataViewTable component', () => { const branchContent = screen.getByText('Branch details for Repository one'); expect(branchContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeTruthy(); }); + + test('traditional expandable rows: dedicated toggle column expands row', async () => { + const user = userEvent.setup(); + + render( + + ); + + // The expand toggle is in a dedicated column before the data cells + const expandButton = document.querySelector('button[aria-label="Details"]'); + expect(expandButton).toBeTruthy(); + + // The toggle cell should be before the first data cell + const toggleCell = expandButton!.closest('td')!; + const firstDataCell = document.querySelector('[data-ouia-component-id="TableExample-td-0-0"]')!; + expect(toggleCell.compareDocumentPosition(firstDataCell) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + + // Initially collapsed + const expandableContent = screen.getByText('Details for Repository one'); + expect(expandableContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeFalsy(); + + // Click the expand toggle + await user.click(expandButton!); + + // After clicking, the expandable content should be visible + expect(expandableContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeTruthy(); + + // Click again to collapse + await user.click(expandButton!); + expect(expandableContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeFalsy(); + }); + + test('traditional expandable rows with indexBy: expand uses row id as key', async () => { + const user = userEvent.setup(); + + render( + + ); + + // The expandId should use the row's id value ("1") instead of the array index (0) + const expandButton = document.querySelector('[id^="expandable-1"]'); + expect(expandButton).toBeTruthy(); + + // Click the expand button + await user.click(expandButton!); + + // After clicking, the expandable content should be visible + const expandableContent = screen.getByText('Details for Repository one'); + expect(expandableContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeTruthy(); + }); + + test('traditional expandable rows: rows without expandable content render empty toggle cell', () => { + render( + + ); + + // Row 1 (rowId=1) has no expandable content — toggle cell should be empty (no button) + const firstRowToggleCell = document.querySelector('[data-ouia-component-id="TableExample-td-0-0"]')!.previousElementSibling as HTMLElement; + expect(firstRowToggleCell.tagName).toBe('TD'); + expect(firstRowToggleCell.querySelector('button')).toBeNull(); + + // Row 3 (rowId=3) has expandable content — toggle cell should have a button + const thirdRowToggleCell = document.querySelector('[data-ouia-component-id="TableExample-td-2-0"]')!.previousElementSibling as HTMLElement; + expect(thirdRowToggleCell.tagName).toBe('TD'); + expect(thirdRowToggleCell.querySelector('button')).toBeTruthy(); + }); }); diff --git a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx index 6e049e9d..58a77e49 100644 --- a/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx +++ b/packages/module/src/DataViewTableBasic/DataViewTableBasic.tsx @@ -16,7 +16,7 @@ import { DataViewState } from '../DataView/DataView'; export interface ExpandableContent { rowId: string | number; - columnId: number; + columnId?: number; content: React.ReactNode; } @@ -69,6 +69,7 @@ export const DataViewTableBasic: FC = ({ const tableRef = useRef(null); const needsSeparateTbody = isExpandable; + const isCompoundExpandable = isExpandable && expandedRows?.every((row) => row.columnId !== undefined); const renderedRows = useMemo(() => rows.map((row, rowIndex) => { const rowIsObject = isDataViewTrObject(row); @@ -86,10 +87,11 @@ export const DataViewTableBasic: FC = ({ const firstCell = rowData[0]; const rowId = isDataViewTdObject(firstCell) ? (firstCell as { id?: string | number }).id : undefined; + const matchesRow = (content: ExpandableContent) => + indexBy ? String(content.rowId) === String(rowKey) : content.rowId === rowId; + // Find all expandable contents for this row - const rowExpandableContents = isExpandable ? expandedRows?.filter( - (content) => indexBy ? String(content.rowId) === String(rowKey) : content.rowId === rowId - ) : []; + const rowExpandableContents = isExpandable ? expandedRows?.filter(matchesRow) : []; const rowContent = ( @@ -106,11 +108,23 @@ export const DataViewTableBasic: FC = ({ }} /> )} + {isExpandable && !isCompoundExpandable && ( + rowExpandableContents?.length + ? setExpandedRowsState(prev => ({ ...prev, [rowKey]: !prev[rowKey] })), + }} + /> + : + )} {(rowIsObject ? row.row : row).map((cell, colIndex) => { const cellIsObject = isDataViewTdObject(cell); - const cellExpandableContent = isExpandable ? expandedRows?.find( - (content) => (indexBy ? String(content.rowId) === String(rowKey) : content.rowId === rowId) && content.columnId === colIndex - ) : undefined; + const cellExpandableContent = isCompoundExpandable + ? expandedRows?.find((row) => matchesRow(row) && row.columnId === colIndex) + : undefined; return ( = ({ {rowContent} {rowExpandableContents?.map((expandableContent) => ( - - - + + + {expandableContent.content} @@ -158,7 +172,7 @@ export const DataViewTableBasic: FC = ({ } else { return rowContent; } - }), [ rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId, expandedRowsState, expandedColumnIndex, expandedRows, isExpandable, needsSeparateTbody, indexBy ]); + }), [ rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId, expandedRowsState, expandedColumnIndex, expandedRows, isExpandable, isCompoundExpandable, needsSeparateTbody, indexBy ]); const bodyContent = activeBodyState || (needsSeparateTbody ? renderedRows : {renderedRows});