Skip to content

Commit feca786

Browse files
authored
fix: associate DataTable TableCells with column headers (#22169)
1 parent 1e2d9cd commit feca786

3 files changed

Lines changed: 215 additions & 64 deletions

File tree

packages/react/src/components/DataTable/DataTable.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,15 @@ export interface DataTableRenderProps<RowType, ColTypes extends any[]> {
135135

136136
getHeaderProps: (options: {
137137
header: DataTableHeader;
138+
id?: string;
138139
isSortable?: boolean;
139140
onClick?: (
140141
event: MouseEvent<HTMLButtonElement>,
141142
sortState: { sortHeaderKey: string; sortDirection: DataTableSortState }
142143
) => void;
143144
[key: string]: unknown;
144145
}) => {
146+
id: string;
145147
isSortable: boolean | undefined;
146148
isSortHeader: boolean;
147149
key: string;
@@ -235,9 +237,14 @@ export interface DataTableRenderProps<RowType, ColTypes extends any[]> {
235237
useStaticWidth?: boolean;
236238
};
237239

238-
getCellProps: (options: { cell: DataTableCell<ColTypes> }) => {
240+
getCellProps: (options: {
241+
cell: DataTableCell<ColTypes>;
242+
headers?: string;
243+
[key: string]: unknown;
244+
}) => {
239245
[key: string]: unknown;
240246
hasAILabelHeader?: boolean;
247+
headers: string;
241248
key: string;
242249
};
243250

@@ -402,9 +409,14 @@ export const DataTable = <RowType, ColTypes extends any[]>(
402409
}) => {
403410
const { sortDirection, sortHeaderKey } = state;
404411
const { key, slug, decorator } = header;
412+
const id =
413+
typeof rest.id === 'string' && rest.id.length
414+
? rest.id
415+
: getHeaderId(key);
405416

406417
return {
407418
...rest,
419+
id,
408420
key,
409421
sortDirection,
410422
isSortable: headerIsSortable ?? header.isSortable ?? isSortable,
@@ -609,12 +621,23 @@ export const DataTable = <RowType, ColTypes extends any[]>(
609621
};
610622

611623
const getCellProps: RenderProps['getCellProps'] = ({
612-
cell: { hasAILabelHeader, id },
624+
cell: {
625+
hasAILabelHeader,
626+
id,
627+
info: { header },
628+
},
629+
headers: customHeaders,
613630
...rest
614631
}) => {
632+
const headers =
633+
typeof customHeaders === 'string' && customHeaders.length
634+
? customHeaders
635+
: getHeaderId(header);
636+
615637
return {
616638
...rest,
617639
hasAILabelHeader,
640+
headers,
618641
key: id,
619642
};
620643
};
@@ -644,6 +667,9 @@ export const DataTable = <RowType, ColTypes extends any[]>(
644667
*/
645668
const getTablePrefix = () => `data-table-${instanceId}`;
646669

670+
const getHeaderId = (headerKey: string) =>
671+
`${getTablePrefix()}__header-${headerKey}`;
672+
647673
/**
648674
* Generates a new `rowsById` object with updated selection state.
649675
*/

packages/react/src/components/DataTable/__tests__/DataTable-test.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,75 @@ describe('DataTable', () => {
155155
const { container } = render(<DataTable {...mockProps} />);
156156
expect(container).toMatchSnapshot();
157157
});
158+
159+
it('should associate data cells with their column headers', () => {
160+
render(<DataTable {...mockProps} />);
161+
162+
const [firstHeader, secondHeader] = screen.getAllByRole('columnheader');
163+
const cells = screen.getAllByRole('cell');
164+
165+
expect(firstHeader).toHaveAttribute(
166+
'id',
167+
expect.stringMatching(/^data-table-\d+__header-fieldA$/)
168+
);
169+
expect(secondHeader).toHaveAttribute(
170+
'id',
171+
expect.stringMatching(/^data-table-\d+__header-fieldB$/)
172+
);
173+
expect(cells[0]).toHaveAttribute('headers', firstHeader.id);
174+
expect(cells[1]).toHaveAttribute('headers', secondHeader.id);
175+
expect(cells[2]).toHaveAttribute('headers', firstHeader.id);
176+
expect(cells[3]).toHaveAttribute('headers', secondHeader.id);
177+
});
178+
179+
it('should preserve custom header and cell associations', () => {
180+
const { render: _render, ...props } = mockProps;
181+
182+
render(
183+
<DataTable {...props}>
184+
{({ getCellProps, getHeaderProps, getTableProps, headers, rows }) => (
185+
<Table {...getTableProps()}>
186+
<TableHead>
187+
<TableRow>
188+
{headers.map((header) => (
189+
<TableHeader
190+
{...getHeaderProps({
191+
header,
192+
id: `custom-header-${header.key}`,
193+
})}>
194+
{header.header}
195+
</TableHeader>
196+
))}
197+
</TableRow>
198+
</TableHead>
199+
<TableBody>
200+
{rows.map((row) => (
201+
<TableRow key={row.id}>
202+
{row.cells.map((cell) => (
203+
<TableCell
204+
{...getCellProps({
205+
cell,
206+
headers: `custom-headers-${cell.info.header}`,
207+
})}>
208+
{cell.value}
209+
</TableCell>
210+
))}
211+
</TableRow>
212+
))}
213+
</TableBody>
214+
</Table>
215+
)}
216+
</DataTable>
217+
);
218+
219+
const [firstHeader, secondHeader] = screen.getAllByRole('columnheader');
220+
const cells = screen.getAllByRole('cell');
221+
222+
expect(firstHeader).toHaveAttribute('id', 'custom-header-fieldA');
223+
expect(secondHeader).toHaveAttribute('id', 'custom-header-fieldB');
224+
expect(cells[0]).toHaveAttribute('headers', 'custom-headers-fieldA');
225+
expect(cells[1]).toHaveAttribute('headers', 'custom-headers-fieldB');
226+
});
158227
});
159228

160229
describe('behaves as expected', () => {

0 commit comments

Comments
 (0)