Skip to content

Commit 9c917c3

Browse files
sebaldsnowystinger
andauthored
feat(RAC): Expose cell's column index in its render props (#9459)
* expose cell index to render props * add tests * update, use colIndex instead of cellIndex * add data attributes * consolidate test and add one for non-colspan case --------- Co-authored-by: Robert Snow <[email protected]>
1 parent 1a30398 commit 9c917c3

File tree

2 files changed

+117
-11
lines changed

2 files changed

+117
-11
lines changed

packages/react-aria-components/src/Table.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,11 @@ export interface CellRenderProps {
13211321
/**
13221322
* The unique id of the cell.
13231323
**/
1324-
id?: Key
1324+
id?: Key,
1325+
/**
1326+
* The index of the column that this cell belongs to. Respects col spanning.
1327+
*/
1328+
colIndex?: number | null
13251329
}
13261330

13271331
export interface CellProps extends RenderProps<CellRenderProps>, GlobalDOMAttributes<HTMLTableCellElement> {
@@ -1369,6 +1373,8 @@ export const Cell = /*#__PURE__*/ createLeafComponent(TableCellNode, (props: Cel
13691373
let {isFocused, isFocusVisible, focusProps} = useFocusRing();
13701374
let {hoverProps, isHovered} = useHover({});
13711375
let isSelected = cell.parentKey != null ? state.selectionManager.isSelected(cell.parentKey) : false;
1376+
// colIndex is null, when there is so span, falling back to using the index
1377+
let colIndex = cell.colIndex || cell.index;
13721378

13731379
let renderProps = useRenderProps({
13741380
...props,
@@ -1380,7 +1386,8 @@ export const Cell = /*#__PURE__*/ createLeafComponent(TableCellNode, (props: Cel
13801386
isPressed,
13811387
isHovered,
13821388
isSelected,
1383-
id: cell.key
1389+
id: cell.key,
1390+
colIndex
13841391
}
13851392
});
13861393

@@ -1394,7 +1401,8 @@ export const Cell = /*#__PURE__*/ createLeafComponent(TableCellNode, (props: Cel
13941401
data-focused={isFocused || undefined}
13951402
data-focus-visible={isFocusVisible || undefined}
13961403
data-pressed={isPressed || undefined}
1397-
data-selected={isSelected || undefined}>
1404+
data-selected={isSelected || undefined}
1405+
data-col-index={colIndex}>
13981406
<CollectionRendererContext.Provider value={DefaultCollectionRenderer}>
13991407
{renderProps.children}
14001408
</CollectionRendererContext.Provider>

packages/react-aria-components/test/Table.test.js

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,104 @@ describe('Table', () => {
858858
expect(cells[0]).toHaveTextContent('Foo (focused)');
859859
});
860860

861+
it('should support column index in render props', () => {
862+
let {getAllByRole} = render(
863+
<Table aria-label="Search results">
864+
<TableHeader>
865+
<Column isRowHeader>Name</Column>
866+
<Column isRowHeader>Type</Column>
867+
<Column isRowHeader>Price</Column>
868+
<Column isRowHeader>Total</Column>
869+
</TableHeader>
870+
<TableBody>
871+
<Row>
872+
<Cell>
873+
{({colIndex}) => `cell index: ${colIndex}`}
874+
</Cell>
875+
<Cell>
876+
{({colIndex}) => `cell index: ${colIndex}`}
877+
</Cell>
878+
<Cell>
879+
{({colIndex}) => `cell index: ${colIndex}`}
880+
</Cell>
881+
<Cell>
882+
{({colIndex}) => `cell index: ${colIndex}`}
883+
</Cell>
884+
</Row>
885+
</TableBody>
886+
</Table>
887+
);
888+
889+
let cells = getAllByRole('rowheader');
890+
expect(cells[0]).toHaveTextContent('cell index: 0');
891+
expect(cells[1]).toHaveTextContent('cell index: 1');
892+
expect(cells[2]).toHaveTextContent('cell index: 2');
893+
expect(cells[3]).toHaveTextContent('cell index: 3');
894+
expect(cells[0]).toHaveAttribute('data-col-index', '0');
895+
expect(cells[1]).toHaveAttribute('data-col-index', '1');
896+
expect(cells[2]).toHaveAttribute('data-col-index', '2');
897+
expect(cells[3]).toHaveAttribute('data-col-index', '3');
898+
});
899+
900+
it('should support colspan with cell index', () => {
901+
let {getAllByRole} = render(
902+
<Table aria-label="Search results">
903+
<TableHeader>
904+
<Column isRowHeader>Name</Column>
905+
<Column isRowHeader>Type</Column>
906+
<Column isRowHeader>Price</Column>
907+
<Column isRowHeader>Total</Column>
908+
</TableHeader>
909+
<TableBody>
910+
<Row>
911+
<Cell colSpan={2}>
912+
{({colIndex}) => `cell index: ${colIndex}`}
913+
</Cell>
914+
<Cell>
915+
{({colIndex}) => `cell index: ${colIndex}`}
916+
</Cell>
917+
<Cell>
918+
{({colIndex}) => `cell index: ${colIndex}`}
919+
</Cell>
920+
</Row>
921+
<Row>
922+
<Cell>
923+
{({colIndex}) => `cell index: ${colIndex}`}
924+
</Cell>
925+
<Cell>
926+
{({colIndex}) => `cell index: ${colIndex}`}
927+
</Cell>
928+
<Cell>
929+
{({colIndex}) => `cell index: ${colIndex}`}
930+
</Cell>
931+
<Cell>
932+
{({colIndex}) => `cell index: ${colIndex}`}
933+
</Cell>
934+
</Row>
935+
</TableBody>
936+
</Table>
937+
);
938+
939+
let cells = getAllByRole('rowheader');
940+
// first row
941+
expect(cells[0]).toHaveTextContent('cell index: 0');
942+
expect(cells[1]).toHaveTextContent('cell index: 2');
943+
expect(cells[2]).toHaveTextContent('cell index: 3');
944+
expect(cells[0]).toHaveAttribute('data-col-index', '0');
945+
expect(cells[1]).toHaveAttribute('data-col-index', '2');
946+
expect(cells[2]).toHaveAttribute('data-col-index', '3');
947+
948+
// second row
949+
expect(cells[3]).toHaveTextContent('cell index: 0');
950+
expect(cells[4]).toHaveTextContent('cell index: 1');
951+
expect(cells[5]).toHaveTextContent('cell index: 2');
952+
expect(cells[6]).toHaveTextContent('cell index: 3');
953+
expect(cells[3]).toHaveAttribute('data-col-index', '0');
954+
expect(cells[4]).toHaveAttribute('data-col-index', '1');
955+
expect(cells[5]).toHaveAttribute('data-col-index', '2');
956+
expect(cells[6]).toHaveAttribute('data-col-index', '3');
957+
});
958+
861959
it('should support columnHeader typeahead', async () => {
862960
let {getAllByRole} = render(
863961
<Table aria-label="Files">
@@ -1321,13 +1419,13 @@ describe('Table', () => {
13211419
expect(document.activeElement).toHaveAttribute('aria-label', 'Insert between Adobe Photoshop and Adobe XD');
13221420
await user.tab();
13231421
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on');
1324-
1422+
13251423
const labels = ['Pictures', 'Adobe Fresco', 'Apps', 'Adobe Illustrator', 'Adobe Lightroom', 'Adobe Dreamweaver'];
1326-
1424+
13271425
for (let i = 0; i <= labels.length; i++) {
13281426
fireEvent.keyDown(document.activeElement, {key: 'ArrowDown'});
13291427
fireEvent.keyUp(document.activeElement, {key: 'ArrowDown'});
1330-
1428+
13311429
if (i === 0) {
13321430
expect(document.activeElement).toHaveAttribute('aria-label', `Insert before ${labels[i]}`);
13331431
} else if (i === labels.length) {
@@ -1336,14 +1434,14 @@ describe('Table', () => {
13361434
expect(document.activeElement).toHaveAttribute('aria-label', `Insert between ${labels[i - 1]} and ${labels[i]}`);
13371435
}
13381436
}
1339-
1437+
13401438
await user.keyboard('{Home}');
13411439
expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on');
1342-
1440+
13431441
for (let i = labels.length; i >= 0; i--) {
13441442
fireEvent.keyDown(document.activeElement, {key: 'ArrowUp'});
13451443
fireEvent.keyUp(document.activeElement, {key: 'ArrowUp'});
1346-
1444+
13471445
if (i === 0) {
13481446
expect(document.activeElement).toHaveAttribute('aria-label', `Insert before ${labels[i]}`);
13491447
} else if (i === labels.length) {
@@ -1352,7 +1450,7 @@ describe('Table', () => {
13521450
expect(document.activeElement).toHaveAttribute('aria-label', `Insert between ${labels[i - 1]} and ${labels[i]}`);
13531451
}
13541452
}
1355-
1453+
13561454
await user.keyboard('{End}');
13571455
expect(document.activeElement).toHaveAttribute('aria-label', 'Insert after Adobe Dreamweaver');
13581456
await user.keyboard('{ArrowDown}');
@@ -2696,7 +2794,7 @@ describe('Table', () => {
26962794
let {getByRole} = renderTable({rowProps: {onAction, onPressStart, onPressEnd, onPress, onClick}});
26972795
let tableTester = testUtilUser.createTester('Table', {root: getByRole('grid')});
26982796
await tableTester.triggerRowAction({row: 1, interactionType});
2699-
2797+
27002798
expect(onAction).toHaveBeenCalledTimes(1);
27012799
expect(onPressStart).toHaveBeenCalledTimes(1);
27022800
expect(onPressEnd).toHaveBeenCalledTimes(1);

0 commit comments

Comments
 (0)