From 77194ebe32172b576c751979574dd2acd75d962e Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 2 Sep 2024 13:46:59 -0500 Subject: [PATCH] release v3.0.0-beta.0 --- .../components/navigation/routes.ts | 4 ++ .../components/prop-tables/tableOptions.ts | 10 ++++ .../pages/docs/guides/accessibility.mdx | 30 +++++++++++ packages/material-react-table/package.json | 2 +- .../src/components/body/MRT_TableBodyCell.tsx | 4 +- .../src/components/footer/MRT_TableFooter.tsx | 19 ++++++- .../components/footer/MRT_TableFooterCell.tsx | 4 +- .../src/components/head/MRT_TableHeadCell.tsx | 4 +- packages/material-react-table/src/types.ts | 44 ++++++++++++++++ .../src/utils/cell.utils.ts | 50 +++++++++++++++---- 10 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 apps/material-react-table-docs/pages/docs/guides/accessibility.mdx diff --git a/apps/material-react-table-docs/components/navigation/routes.ts b/apps/material-react-table-docs/components/navigation/routes.ts index ff09f049f..eac2923f7 100644 --- a/apps/material-react-table-docs/components/navigation/routes.ts +++ b/apps/material-react-table-docs/components/navigation/routes.ts @@ -353,6 +353,10 @@ export const routes: Array = [ href: '/docs/guides/state-management', label: 'State Management', }, + { + href: '/docs/guides/accessibility', + label: 'Accessibility / Keyboard Navigation', + }, ], }, { diff --git a/apps/material-react-table-docs/components/prop-tables/tableOptions.ts b/apps/material-react-table-docs/components/prop-tables/tableOptions.ts index d6d9c5947..3159ecbe6 100644 --- a/apps/material-react-table-docs/components/prop-tables/tableOptions.ts +++ b/apps/material-react-table-docs/components/prop-tables/tableOptions.ts @@ -492,6 +492,16 @@ export const tableOptions: TableOption[] = [ source: '', type: 'boolean', }, + { + tableOption: 'enableCellNavigation', + defaultValue: 'true', + description: '', + link: '', + linkText: '', + required: false, + source: '', + type: 'boolean', + }, { tableOption: 'enablePagination', defaultValue: 'true', diff --git a/apps/material-react-table-docs/pages/docs/guides/accessibility.mdx b/apps/material-react-table-docs/pages/docs/guides/accessibility.mdx new file mode 100644 index 000000000..86a5f7245 --- /dev/null +++ b/apps/material-react-table-docs/pages/docs/guides/accessibility.mdx @@ -0,0 +1,30 @@ +import Head from 'next/head'; +import TableOptionsTable from '../../../components/prop-tables/TableOptionsTable'; +import ColumnOptionsTable from '../../../components/prop-tables/ColumnOptionsTable'; +import StateOptionsTable from '../../../components/prop-tables/StateOptionsTable'; + + + + {'Accessibility / Keyboard Navigation Guide - Material React Table V3 Docs'} + + + + +## Accessibility / Keyboard Navigation Guide + +Material React Table tries to get the basics of data grid accessibility right out of the box. But since you can easily add event handlers to just about any interaction inside of the table, you can heavily customize the accessibility of your table to your needs. + +### Relevant Table Options + + + +### Keyboard Navigation + +> New in v3 + +Material React Table uses the `tab` key to move focus between cells, rows, and columns. diff --git a/packages/material-react-table/package.json b/packages/material-react-table/package.json index a794213f9..4e06ee758 100644 --- a/packages/material-react-table/package.json +++ b/packages/material-react-table/package.json @@ -1,5 +1,5 @@ { - "version": "3.0.0-alpha.0", + "version": "3.0.0-beta.0", "license": "MIT", "name": "material-react-table", "description": "A fully featured Material UI V6 implementation of TanStack React Table V8, written from the ground up in TypeScript.", diff --git a/packages/material-react-table/src/components/body/MRT_TableBodyCell.tsx b/packages/material-react-table/src/components/body/MRT_TableBodyCell.tsx index 02934f798..f5249b35f 100644 --- a/packages/material-react-table/src/components/body/MRT_TableBodyCell.tsx +++ b/packages/material-react-table/src/components/body/MRT_TableBodyCell.tsx @@ -18,7 +18,7 @@ import { } from '../../types'; import { isCellEditable, - navigateToNextCell, + cellNavigation, openEditingCell, } from '../../utils/cell.utils'; import { getCommonMRTCellStyles } from '../../utils/style.utils'; @@ -234,7 +234,7 @@ export const MRT_TableBodyCell = ({ const handleKeyDown = (e: React.KeyboardEvent) => { if (enableCellNavigation) { - navigateToNextCell(e); + cellNavigation(e); } tableCellProps?.onKeyDown?.(e); }; diff --git a/packages/material-react-table/src/components/footer/MRT_TableFooter.tsx b/packages/material-react-table/src/components/footer/MRT_TableFooter.tsx index eb8b06cca..80ec10209 100644 --- a/packages/material-react-table/src/components/footer/MRT_TableFooter.tsx +++ b/packages/material-react-table/src/components/footer/MRT_TableFooter.tsx @@ -19,7 +19,6 @@ export const MRT_TableFooter = ({ ...rest }: MRT_TableFooterProps) => { const { - getFooterGroups, getState, options: { enableStickyFooter, layoutMode, muiTableFooterProps }, refs: { tableFooterRef }, @@ -36,6 +35,22 @@ export const MRT_TableFooter = ({ const stickFooter = (isFullScreen || enableStickyFooter) && enableStickyFooter !== false; + const footerGroups = table.getFooterGroups(); + + //if no footer cells at all, skip footer + if ( + !footerGroups.some((footerGroup) => + footerGroup.headers?.some( + (header) => + (typeof header.column.columnDef.footer === 'string' && + !!header.column.columnDef.footer) || + header.column.columnDef.Footer, + ), + ) + ) { + return null; + } + return ( ({ ...(parseFromValuesOrFunc(tableFooterProps?.sx, theme) as any), })} > - {getFooterGroups().map((footerGroup) => ( + {footerGroups.map((footerGroup) => ( extends TableCellProps { @@ -50,7 +50,7 @@ export const MRT_TableFooterCell = ({ const handleKeyDown = (e: React.KeyboardEvent) => { if (enableCellNavigation) { - navigateToNextCell(e); + cellNavigation(e); } tableCellProps?.onKeyDown?.(e); }; diff --git a/packages/material-react-table/src/components/head/MRT_TableHeadCell.tsx b/packages/material-react-table/src/components/head/MRT_TableHeadCell.tsx index e462b6375..54a4c18f3 100644 --- a/packages/material-react-table/src/components/head/MRT_TableHeadCell.tsx +++ b/packages/material-react-table/src/components/head/MRT_TableHeadCell.tsx @@ -17,7 +17,7 @@ import { } from '../../types'; import { getCommonMRTCellStyles } from '../../utils/style.utils'; import { parseFromValuesOrFunc } from '../../utils/utils'; -import { navigateToNextCell } from '../../utils/cell.utils'; +import { cellNavigation } from '../../utils/cell.utils'; export interface MRT_TableHeadCellProps extends TableCellProps { @@ -151,7 +151,7 @@ export const MRT_TableHeadCell = ({ const handleKeyDown = (e: React.KeyboardEvent) => { if (enableCellNavigation) { - navigateToNextCell(e); + cellNavigation(e); } tableCellProps?.onKeyDown?.(e); }; diff --git a/packages/material-react-table/src/types.ts b/packages/material-react-table/src/types.ts index c75980cbc..4b21adc09 100644 --- a/packages/material-react-table/src/types.ts +++ b/packages/material-react-table/src/types.ts @@ -271,6 +271,7 @@ export type MRT_TableInstance = Omit< | 'getColumn' | 'getExpandedRowModel' | 'getFlatHeaders' + | 'getFooterGroups' | 'getHeaderGroups' | 'getLeafHeaders' | 'getLeftLeafColumns' @@ -293,6 +294,7 @@ export type MRT_TableInstance = Omit< getColumn: (columnId: string) => MRT_Column; getExpandedRowModel: () => MRT_RowModel; getFlatHeaders: () => MRT_Header[]; + getFooterGroups: () => MRT_HeaderGroup[]; getHeaderGroups: () => MRT_HeaderGroup[]; getLeafHeaders: () => MRT_Header[]; getLeftLeafColumns: () => MRT_Column[]; @@ -920,18 +922,27 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => CircularProgressProps & { Component?: ReactNode }) | (CircularProgressProps & { Component?: ReactNode }); + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiColumnActionsButtonProps?: | ((props: { column: MRT_Column; table: MRT_TableInstance; }) => IconButtonProps) | IconButtonProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiColumnDragHandleProps?: | ((props: { column: MRT_Column; table: MRT_TableInstance; }) => IconButtonProps) | IconButtonProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiCopyButtonProps?: | ((props: { cell: MRT_Cell; @@ -958,6 +969,9 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => DialogProps) | DialogProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiEditTextFieldProps?: | ((props: { cell: MRT_Cell; @@ -976,18 +990,27 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => IconButtonProps) | IconButtonProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiFilterAutocompleteProps?: | ((props: { column: MRT_Column; table: MRT_TableInstance; }) => AutocompleteProps) | AutocompleteProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiFilterCheckboxProps?: | ((props: { column: MRT_Column; table: MRT_TableInstance; }) => CheckboxProps) | CheckboxProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiFilterDatePickerProps?: | ((props: { column: MRT_Column; @@ -995,6 +1018,9 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => DatePickerProps) | DatePickerProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiFilterDateTimePickerProps?: | ((props: { column: MRT_Column; @@ -1002,12 +1028,18 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => DateTimePickerProps) | DateTimePickerProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiFilterSliderProps?: | ((props: { column: MRT_Column; table: MRT_TableInstance; }) => SliderProps) | SliderProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiFilterTextFieldProps?: | ((props: { column: MRT_Column; @@ -1015,6 +1047,9 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => TextFieldProps) | TextFieldProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiFilterTimePickerProps?: | ((props: { column: MRT_Column; @@ -1064,6 +1099,9 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => CheckboxProps | RadioProps) | (CheckboxProps | RadioProps); + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiSkeletonProps?: | ((props: { cell: MRT_Cell; @@ -1072,6 +1110,9 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => SkeletonProps) | SkeletonProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiTableBodyCellProps?: | ((props: { cell: MRT_Cell; @@ -1109,6 +1150,9 @@ export interface MRT_TableOptions table: MRT_TableInstance; }) => TableRowProps) | TableRowProps; + /** + * @deprecated Specify this in the `defaultColumn` table option instead if you want to apply to all columns. + */ muiTableHeadCellProps?: | ((props: { column: MRT_Column; diff --git a/packages/material-react-table/src/utils/cell.utils.ts b/packages/material-react-table/src/utils/cell.utils.ts index f731ee5da..af483f4d3 100644 --- a/packages/material-react-table/src/utils/cell.utils.ts +++ b/packages/material-react-table/src/utils/cell.utils.ts @@ -49,44 +49,72 @@ export const openEditingCell = ({ } }; -export const navigateToNextCell = ( +export const cellNavigation = ( e: React.KeyboardEvent, ) => { - if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(e.key)) { + if ( + ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes( + e.key, + ) + ) { e.preventDefault(); const currentCell = e.currentTarget; + const currentRow = currentCell.closest('tr'); + const tableElement = currentCell.closest('table'); - if (!tableElement) return; + const allCells = Array.from(tableElement?.querySelectorAll('th, td') || []); + const currentCellIndex = allCells.indexOf(currentCell); const currentIndex = parseInt( currentCell.getAttribute('data-index') || '0', ); let nextCell: HTMLElement | undefined = undefined; - const findNextCell = (index: number, searchDirection: 'f' | 'b') => { - const allCells = Array.from(tableElement.querySelectorAll('th, td')); - const currentCellIndex = allCells.indexOf(currentCell); + //home/end first or last cell in row + const findEdgeCell = (rowIndex: 'c' | 'f' | 'l', edge: 'f' | 'l') => { + const row = + rowIndex === 'c' + ? currentRow + : rowIndex === 'f' + ? currentCell.closest('table')?.querySelector('tr') + : currentCell.closest('table')?.lastElementChild?.lastElementChild; + const rowCells = Array.from(row?.children || []); + const targetCell = + edge === 'f' ? rowCells[0] : rowCells[rowCells.length - 1]; + return targetCell as HTMLElement; + }; + + const findAdjacentCell = ( + columnIndex: number, + searchDirection: 'f' | 'b', + ) => { const searchArray = searchDirection === 'f' ? allCells.slice(currentCellIndex + 1) : allCells.slice(0, currentCellIndex).reverse(); return searchArray.find((cell) => - cell.matches(`[data-index="${index}"]`), + cell.matches(`[data-index="${columnIndex}"]`), ) as HTMLElement | undefined; }; switch (e.key) { case 'ArrowRight': - nextCell = findNextCell(currentIndex + 1, 'f'); + nextCell = findAdjacentCell(currentIndex + 1, 'f'); break; case 'ArrowLeft': - nextCell = findNextCell(currentIndex - 1, 'b'); + nextCell = findAdjacentCell(currentIndex - 1, 'b'); break; case 'ArrowUp': - nextCell = findNextCell(currentIndex, 'b'); + nextCell = findAdjacentCell(currentIndex, 'b'); break; case 'ArrowDown': - nextCell = findNextCell(currentIndex, 'f'); + nextCell = findAdjacentCell(currentIndex, 'f'); + break; + case 'Home': + nextCell = findEdgeCell(e.ctrlKey ? 'f' : 'c', 'f'); + break; + case 'End': + nextCell = findEdgeCell(e.ctrlKey ? 'l' : 'c', 'l'); break; }