Skip to content

Commit 3cfdad1

Browse files
committed
add cell keyboard navigation feature
1 parent 3abeeb4 commit 3cfdad1

File tree

7 files changed

+96
-2
lines changed

7 files changed

+96
-2
lines changed

packages/material-react-table/src/components/body/MRT_TableBodyCell.tsx

+15-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import {
1616
type MRT_RowData,
1717
type MRT_TableInstance,
1818
} from '../../types';
19-
import { isCellEditable, openEditingCell } from '../../utils/cell.utils';
19+
import {
20+
isCellEditable,
21+
navigateToNextCell,
22+
openEditingCell,
23+
} from '../../utils/cell.utils';
2024
import { getCommonMRTCellStyles } from '../../utils/style.utils';
2125
import { parseFromValuesOrFunc } from '../../utils/utils';
2226
import { MRT_CopyButton } from '../buttons/MRT_CopyButton';
@@ -54,6 +58,7 @@ export const MRT_TableBodyCell = <TData extends MRT_RowData>({
5458
enableColumnOrdering,
5559
enableColumnPinning,
5660
enableGrouping,
61+
enableCellNavigation,
5762
layoutMode,
5863
mrtTheme: { draggingBorderColor },
5964
muiSkeletonProps,
@@ -227,12 +232,21 @@ export const MRT_TableBodyCell = <TData extends MRT_RowData>({
227232
}
228233
};
229234

235+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
236+
if (enableCellNavigation) {
237+
navigateToNextCell(e);
238+
}
239+
tableCellProps?.onKeyDown?.(e);
240+
};
241+
230242
return (
231243
<TableCell
232244
align={theme.direction === 'rtl' ? 'right' : 'left'}
233245
data-index={staticColumnIndex}
234246
data-pinned={!!isColumnPinned || undefined}
247+
tabIndex={enableCellNavigation ? 0 : undefined}
235248
{...tableCellProps}
249+
onKeyDown={handleKeyDown}
236250
onContextMenu={handleContextMenu}
237251
onDoubleClick={handleDoubleClick}
238252
onDragEnter={handleDragEnter}

packages/material-react-table/src/components/footer/MRT_TableFooterCell.tsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '../../types';
88
import { getCommonMRTCellStyles } from '../../utils/style.utils';
99
import { parseFromValuesOrFunc } from '../../utils/utils';
10+
import { navigateToNextCell } from '../../utils/cell.utils';
1011

1112
export interface MRT_TableFooterCellProps<TData extends MRT_RowData>
1213
extends TableCellProps {
@@ -24,7 +25,11 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({
2425
const theme = useTheme();
2526
const {
2627
getState,
27-
options: { enableColumnPinning, muiTableFooterCellProps },
28+
options: {
29+
enableColumnPinning,
30+
muiTableFooterCellProps,
31+
enableCellNavigation,
32+
},
2833
} = table;
2934
const { density } = getState();
3035
const { column } = footer;
@@ -43,6 +48,13 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({
4348
...rest,
4449
};
4550

51+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
52+
if (enableCellNavigation) {
53+
navigateToNextCell(e);
54+
}
55+
tableCellProps?.onKeyDown?.(e);
56+
};
57+
4658
return (
4759
<TableCell
4860
align={
@@ -57,6 +69,7 @@ export const MRT_TableFooterCell = <TData extends MRT_RowData>({
5769
data-pinned={!!isColumnPinned || undefined}
5870
variant="footer"
5971
{...tableCellProps}
72+
onKeyDown={handleKeyDown}
6073
sx={(theme) => ({
6174
fontWeight: 'bold',
6275
p:

packages/material-react-table/src/components/head/MRT_TableHeadCell.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from '../../types';
1818
import { getCommonMRTCellStyles } from '../../utils/style.utils';
1919
import { parseFromValuesOrFunc } from '../../utils/utils';
20+
import { navigateToNextCell } from '../../utils/cell.utils';
2021

2122
export interface MRT_TableHeadCellProps<TData extends MRT_RowData>
2223
extends TableCellProps {
@@ -45,6 +46,7 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({
4546
enableColumnOrdering,
4647
enableColumnPinning,
4748
enableGrouping,
49+
enableCellNavigation,
4850
enableMultiSort,
4951
layoutMode,
5052
mrtTheme: { draggingBorderColor },
@@ -147,6 +149,13 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({
147149
}
148150
};
149151

152+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTableCellElement>) => {
153+
if (enableCellNavigation) {
154+
navigateToNextCell(e);
155+
}
156+
tableCellProps?.onKeyDown?.(e);
157+
};
158+
150159
const HeaderElement =
151160
parseFromValuesOrFunc(columnDef.Header, {
152161
column,
@@ -185,7 +194,9 @@ export const MRT_TableHeadCell = <TData extends MRT_RowData>({
185194
}
186195
}
187196
}}
197+
tabIndex={enableCellNavigation ? 0 : undefined}
188198
{...tableCellProps}
199+
onKeyDown={handleKeyDown}
189200
sx={(theme: Theme) => ({
190201
'& :hover': {
191202
'.MuiButtonBase-root': {

packages/material-react-table/src/hooks/useMRT_TableOptions.ts

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const useMRT_TableOptions: <TData extends MRT_RowData>(
7676
enableGlobalFilterRankedResults = true,
7777
enableGrouping = false,
7878
enableHiding = true,
79+
enableCellNavigation = true,
7980
enableMultiRowSelection = true,
8081
enableMultiSort = true,
8182
enablePagination = true,
@@ -203,6 +204,7 @@ export const useMRT_TableOptions: <TData extends MRT_RowData>(
203204
enableGlobalFilterRankedResults,
204205
enableGrouping,
205206
enableHiding,
207+
enableCellNavigation,
206208
enableMultiRowSelection,
207209
enableMultiSort,
208210
enablePagination,

packages/material-react-table/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ export interface MRT_Localization {
246246

247247
export interface MRT_Theme {
248248
baseBackgroundColor: string;
249+
cellNavigationOutlineColor: string;
249250
draggingBorderColor: string;
250251
matchHighlightColor: string;
251252
menuBackgroundColor: string;
@@ -867,6 +868,7 @@ export interface MRT_TableOptions<TData extends MRT_RowData>
867868
enableFullScreenToggle?: boolean;
868869
enableGlobalFilterModes?: boolean;
869870
enableGlobalFilterRankedResults?: boolean;
871+
enableCellNavigation?: boolean;
870872
enablePagination?: boolean;
871873
enableRowActions?: boolean;
872874
enableRowDragging?: boolean;

packages/material-react-table/src/utils/cell.utils.ts

+47
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,50 @@ export const openEditingCell = <TData extends MRT_RowData>({
4848
});
4949
}
5050
};
51+
52+
export const navigateToNextCell = (
53+
e: React.KeyboardEvent<HTMLTableCellElement>,
54+
) => {
55+
if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
56+
e.preventDefault();
57+
const currentCell = e.currentTarget;
58+
const tableElement = currentCell.closest('table');
59+
if (!tableElement) return;
60+
61+
const currentIndex = parseInt(
62+
currentCell.getAttribute('data-index') || '0',
63+
);
64+
let nextCell: HTMLElement | undefined = undefined;
65+
66+
const findNextCell = (index: number, searchDirection: 'f' | 'b') => {
67+
const allCells = Array.from(tableElement.querySelectorAll('th, td'));
68+
const currentCellIndex = allCells.indexOf(currentCell);
69+
const searchArray =
70+
searchDirection === 'f'
71+
? allCells.slice(currentCellIndex + 1)
72+
: allCells.slice(0, currentCellIndex).reverse();
73+
return searchArray.find((cell) =>
74+
cell.matches(`[data-index="${index}"]`),
75+
) as HTMLElement | undefined;
76+
};
77+
78+
switch (e.key) {
79+
case 'ArrowRight':
80+
nextCell = findNextCell(currentIndex + 1, 'f');
81+
break;
82+
case 'ArrowLeft':
83+
nextCell = findNextCell(currentIndex - 1, 'b');
84+
break;
85+
case 'ArrowUp':
86+
nextCell = findNextCell(currentIndex, 'b');
87+
break;
88+
case 'ArrowDown':
89+
nextCell = findNextCell(currentIndex, 'f');
90+
break;
91+
}
92+
93+
if (nextCell) {
94+
nextCell.focus();
95+
}
96+
}
97+
};

packages/material-react-table/src/utils/style.utils.ts

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const getMRTTheme = <TData extends MRT_RowData>(
2727
: muiTheme.palette.background.default);
2828
return {
2929
baseBackgroundColor,
30+
cellNavigationOutlineColor: muiTheme.palette.primary.main,
3031
draggingBorderColor: muiTheme.palette.primary.main,
3132
matchHighlightColor:
3233
muiTheme.palette.mode === 'dark'
@@ -170,6 +171,10 @@ export const getCommonMRTCellStyles = <TData extends MRT_RowData>({
170171
: columnDefType !== 'group' && isColumnPinned
171172
? 1
172173
: 0,
174+
'&:focus-visible': {
175+
outline: `2px solid ${table.options.mrtTheme.cellNavigationOutlineColor}`,
176+
outlineOffset: '-2px',
177+
},
173178
...pinnedStyles,
174179
...widthStyles,
175180
...(parseFromValuesOrFunc(tableCellProps?.sx, theme) as any),

0 commit comments

Comments
 (0)