From 2215a8399de5fa063da9a9406e634cdaac3a89a3 Mon Sep 17 00:00:00 2001 From: Erik Paulsson Date: Wed, 25 Feb 2026 12:02:58 -0500 Subject: [PATCH 1/5] DataTable update to add state props to manage expanded rows externally --- .../src/components/data-table/data-table.tsx | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/comet-extras/src/components/data-table/data-table.tsx b/packages/comet-extras/src/components/data-table/data-table.tsx index b1ed43ba..70773c06 100644 --- a/packages/comet-extras/src/components/data-table/data-table.tsx +++ b/packages/comet-extras/src/components/data-table/data-table.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { Dispatch, SetStateAction, useEffect } from 'react'; import { SortingState, flexRender, @@ -63,9 +63,20 @@ export interface DataTableProps { */ getChildRows?: (row: T) => T[] | undefined; /** - * Initial expanded state for rows (object with row IDs as keys and boolean values) + * An optional state value from parent representing the expanded rows. + * Value: object with row IDs as keys and boolean values, or true to expand all. */ - initialExpanded?: Record; + parentExpanded?: Record | true; + /** + * An optional state setter function from parent for the expanded rows state. + */ + setParentExpanded?: Dispatch | true>>; + /** + * Initial expanded state for rows (object with row IDs as keys and boolean values + * or true to expand all). + * Only used if parent state value and setter function are not provided. + */ + initialExpanded?: Record | true; /** * Additional class names for the table */ @@ -91,6 +102,8 @@ export const DataTable = ({ pageSize = 10, expandable = false, getChildRows, + parentExpanded, + setParentExpanded, initialExpanded = {}, className, ariaLabel, @@ -99,7 +112,12 @@ export const DataTable = ({ sortable ? [{ id: sortCol ?? columns[0], desc: sortDir === 'desc' }] : [], ); const [paging, setPaging] = React.useState({ pageIndex, pageSize }); - const [expanded, setExpanded] = React.useState(initialExpanded); + const [internalExpanded, setInternalExpanded] = React.useState(initialExpanded); + + // Set which state value and setter function to use for tracking expanded rows. + // Use the parent state if passed in via props, otherwise track with internal state. + const expanded = parentExpanded ?? internalExpanded; + const setExpanded = setParentExpanded ?? setInternalExpanded; // Apply sorting to the full dataset first, then handle pagination const sortedData = React.useMemo(() => { From 0f95b43f86021cd742e2f6e4b47a38c25e4c8411 Mon Sep 17 00:00:00 2001 From: Erik Paulsson Date: Wed, 25 Feb 2026 16:34:34 -0500 Subject: [PATCH 2/5] Added DataTable test for all rows initially expanded --- .../components/data-table/data-table.test.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/packages/comet-extras/src/components/data-table/data-table.test.tsx b/packages/comet-extras/src/components/data-table/data-table.test.tsx index 5c418e5a..8df9f02c 100644 --- a/packages/comet-extras/src/components/data-table/data-table.test.tsx +++ b/packages/comet-extras/src/components/data-table/data-table.test.tsx @@ -355,6 +355,60 @@ describe('DataTable', () => { expect(pageButtons.length).toBeGreaterThan(0); // Should have page buttons }); + test('should display all rows initially expanded', () => { + const dataWithChildren: Person[] = [ + { + firstName: 'John', + lastName: 'Doe', + children: [ + { + firstName: 'Johnny', + lastName: 'Doe Jr', + }, + ], + }, + { + firstName: 'Jane', + lastName: 'Smith', + children: [ + { + firstName: 'Janie', + lastName: 'Smith Jr', + }, + ], + }, + { + firstName: 'Bob', + lastName: 'Wilson', + children: [ + { + firstName: 'Bobby', + lastName: 'Wilson Jr', + }, + ], + }, + ]; + + const { baseElement } = render( + row.children} + >, + ); + + // Should show 3 parent rows, each with 1 child row + const allRows = baseElement.querySelectorAll('tbody tr'); + expect(allRows).toHaveLength(6); // 3 parents rows + 1 child row each + + // Verify we have 3 child rows + const childRows = baseElement.querySelectorAll('.child-row'); + expect(childRows).toHaveLength(3); // 1 child row each + }); + test('should not count child rows against pagination when expanded', async () => { const dataWithChildren: Person[] = [ { From f75b90eb533d15ffc0ec03a96c0156b8cf2a6e73 Mon Sep 17 00:00:00 2001 From: Erik Paulsson Date: Thu, 26 Feb 2026 11:07:08 -0500 Subject: [PATCH 3/5] Updated DataTable tests for newly added external state expanded row props --- .../components/data-table/data-table.test.tsx | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/packages/comet-extras/src/components/data-table/data-table.test.tsx b/packages/comet-extras/src/components/data-table/data-table.test.tsx index 8df9f02c..93fe93a7 100644 --- a/packages/comet-extras/src/components/data-table/data-table.test.tsx +++ b/packages/comet-extras/src/components/data-table/data-table.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { act, render } from '@testing-library/react'; +import React, { useState } from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; import DataTable from './data-table'; import { createColumnHelper } from '@tanstack/react-table'; @@ -409,6 +410,86 @@ describe('DataTable', () => { expect(childRows).toHaveLength(3); // 1 child row each }); + test('external expanded row state', async () => { + const dataWithChildren: Person[] = [ + { + firstName: 'John', + lastName: 'Doe', + children: [ + { + firstName: 'Johnny', + lastName: 'Doe Jr', + }, + ], + }, + { + firstName: 'Jane', + lastName: 'Smith', + children: [ + { + firstName: 'Janie', + lastName: 'Smith Jr', + }, + ], + }, + { + firstName: 'Bob', + lastName: 'Wilson', + children: [ + { + firstName: 'Bobby', + lastName: 'Wilson Jr', + }, + ], + }, + ]; + + const Parent = () => { + const [expandedRows, setExpandedRows] = useState | true>({}); + return ( + <> +

Expanded rows: [{Object.keys(expandedRows).join()}]

+ row.children} + > + + ); + }; + + const { baseElement } = render(); + + // Should show 3 parent rows + const allRows = baseElement.querySelectorAll('tbody tr'); + expect(allRows).toHaveLength(3); // 3 parents rows + + const expandButtons = screen.getAllByRole('button', { name: 'Expand row' }); + expect(expandButtons).toHaveLength(3); // 3 expandable rows + + await userEvent.click(expandButtons[0]); + const parentsPlusOneChild = baseElement.querySelectorAll('tbody tr'); + expect(parentsPlusOneChild).toHaveLength(4); // 3 parents rows + 1 child + // test the external expanded row state is updated correctly + let expandedRows = screen.getByText('Expanded rows: [0]'); + expect(expandedRows).toBeInTheDocument(); + + await userEvent.click(expandButtons[2]); + const parentsPlusTwoChildren = baseElement.querySelectorAll('tbody tr'); + expect(parentsPlusTwoChildren).toHaveLength(5); // 3 parents rows + 2 children + // test the external expanded row state is updated correctly + expandedRows = screen.getByText('Expanded rows: [0,2]'); + expect(expandedRows).toBeInTheDocument(); + + // Verify we have 2 child rows + const childRows = baseElement.querySelectorAll('.child-row'); + expect(childRows).toHaveLength(2); // rows 1 and 3 expanded each with 1 child + }); + test('should not count child rows against pagination when expanded', async () => { const dataWithChildren: Person[] = [ { From f7fd71567431d165b8849aa2b2a3dfd9db470d95 Mon Sep 17 00:00:00 2001 From: Erik Paulsson Date: Fri, 27 Feb 2026 09:40:06 -0500 Subject: [PATCH 4/5] Updated DataTable sotrybook to include example of external expanded row state --- .../data-table/data-table.stories.tsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/comet-extras/src/components/data-table/data-table.stories.tsx b/packages/comet-extras/src/components/data-table/data-table.stories.tsx index 40267edf..4fe0e463 100644 --- a/packages/comet-extras/src/components/data-table/data-table.stories.tsx +++ b/packages/comet-extras/src/components/data-table/data-table.stories.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Meta } from '@storybook/react-vite'; import DataTable, { DataTableProps } from './data-table'; import { createColumnHelper } from '@tanstack/react-table'; @@ -173,3 +173,30 @@ export const WithExpandableRows = { }, render: (args: DataTableProps) => , }; + +const ParentStateDataTable = () => { + const [expandedRows, setExpandedRows] = useState | true>({}); + return ( + <> +

External state - expanded rows: [{Object.keys(expandedRows).join()}]

+ row.children} + /> + + ); +}; + +export const WithExternalExpandedRowState = ParentStateDataTable.bind({}); From 1146741d794d847fb532ad0dce43a71f5efc8942 Mon Sep 17 00:00:00 2001 From: Erik Paulsson Date: Fri, 27 Feb 2026 13:22:26 -0500 Subject: [PATCH 5/5] Updated DataTable sotrybook external state example to CSF 3 --- .../data-table/data-table.stories.tsx | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/comet-extras/src/components/data-table/data-table.stories.tsx b/packages/comet-extras/src/components/data-table/data-table.stories.tsx index 4fe0e463..8ff61de1 100644 --- a/packages/comet-extras/src/components/data-table/data-table.stories.tsx +++ b/packages/comet-extras/src/components/data-table/data-table.stories.tsx @@ -174,29 +174,36 @@ export const WithExpandableRows = { render: (args: DataTableProps) => , }; -const ParentStateDataTable = () => { - const [expandedRows, setExpandedRows] = useState | true>({}); - return ( - <> -

External state - expanded rows: [{Object.keys(expandedRows).join()}]

- row.children} - /> - - ); +export const WithExternalExpandedRowState = { + args: { + id: 'table-parent-state', + columns: cols, + data, + striped: false, + sortable: true, + sortDir: 'asc', + sortCol: 'lastName', + pageable: true, + pageIndex: 0, + pageSize: 3, + expandable: true, + getChildRows: (row: Person) => row.children, + className: 'width-full', + }, + parameters: { + docs: { + source: { + type: 'code', + }, + }, + }, + render: (args: DataTableProps) => { + const [expandedRows, setExpandedRows] = useState | true>({}); + return ( +
+

External state - expanded rows: [{Object.keys(expandedRows).join()}]

+ +
+ ); + }, }; - -export const WithExternalExpandedRowState = ParentStateDataTable.bind({});