From a545acd23464c67864b3fd5e1ab4619a46e5ed57 Mon Sep 17 00:00:00 2001 From: Andy Le Date: Tue, 7 Nov 2023 05:26:32 -0700 Subject: [PATCH] [material-ui][TablePagination] Implement `slotProps` pattern for the actions and the select slots (#39353) --- .../material-ui/api/table-pagination.json | 27 ++- .../table-pagination/table-pagination.json | 8 +- .../src/TablePagination/TablePagination.d.ts | 23 ++ .../src/TablePagination/TablePagination.js | 53 ++++- .../TablePagination/TablePagination.test.js | 203 ++++++++++++++++++ .../TablePaginationActions.d.ts | 19 ++ .../TablePagination/TablePaginationActions.js | 31 ++- 7 files changed, 343 insertions(+), 21 deletions(-) diff --git a/docs/pages/material-ui/api/table-pagination.json b/docs/pages/material-ui/api/table-pagination.json index 0fe0e042df85bb..e4ffefc539a7d9 100644 --- a/docs/pages/material-ui/api/table-pagination.json +++ b/docs/pages/material-ui/api/table-pagination.json @@ -12,9 +12,14 @@ "page": { "type": { "name": "custom", "description": "integer" }, "required": true }, "rowsPerPage": { "type": { "name": "custom", "description": "integer" }, "required": true }, "ActionsComponent": { "type": { "name": "elementType" }, "default": "TablePaginationActions" }, - "backIconButtonProps": { "type": { "name": "object" } }, + "backIconButtonProps": { + "type": { "name": "object" }, + "deprecated": true, + "deprecationInfo": "Use slotProps.actions.previousButton instead." + }, "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, "component": { "type": { "name": "elementType" } }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, "getItemAriaLabel": { "type": { "name": "func" }, "default": "function defaultGetAriaLabel(type) {\n return `Go to ${type} page`;\n}", @@ -25,7 +30,11 @@ "default": "function defaultLabelDisplayedRows({ from, to, count }) {\n return `${from}–${to} of ${count !== -1 ? count : `more than ${to}`}`;\n}" }, "labelRowsPerPage": { "type": { "name": "node" }, "default": "'Rows per page:'" }, - "nextIconButtonProps": { "type": { "name": "object" } }, + "nextIconButtonProps": { + "type": { "name": "object" }, + "deprecated": true, + "deprecationInfo": "Use slotProps.actions.nextButton instead." + }, "onRowsPerPageChange": { "type": { "name": "func" }, "signature": { @@ -40,9 +49,21 @@ }, "default": "[10, 25, 50, 100]" }, - "SelectProps": { "type": { "name": "object" }, "default": "{}" }, + "SelectProps": { + "type": { "name": "object" }, + "default": "{}", + "deprecated": true, + "deprecationInfo": "Use slotProps.select instead." + }, "showFirstButton": { "type": { "name": "bool" }, "default": "false" }, "showLastButton": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ actions?: { firstButton?: object, lastButton?: object, nextButton?: object, previousButton?: object }, select?: object }" + }, + "default": "{}" + }, "sx": { "type": { "name": "union", diff --git a/docs/translations/api-docs/table-pagination/table-pagination.json b/docs/translations/api-docs/table-pagination/table-pagination.json index 25a5bd3cb7dd9e..21a7bb468b5699 100644 --- a/docs/translations/api-docs/table-pagination/table-pagination.json +++ b/docs/translations/api-docs/table-pagination/table-pagination.json @@ -5,7 +5,7 @@ "description": "The component used for displaying the actions. Either a string to use a HTML element or a component." }, "backIconButtonProps": { - "description": "Props applied to the back arrow IconButton component." + "description": "Props applied to the back arrow IconButton component.
This prop is an alias for slotProps.actions.previousButton and will be overriden by it if both are used." }, "classes": { "description": "Override or extend the styles applied to the component." }, "component": { @@ -14,6 +14,7 @@ "count": { "description": "The total number of rows.
To enable server side pagination for an unknown number of items, provide -1." }, + "disabled": { "description": "If true, the component is disabled." }, "getItemAriaLabel": { "description": "Accepts a function which returns a string value that provides a user-friendly name for the current page. This is important for screen reader users.
For localization purposes, you can use the provided translations.", "typeDescriptions": { @@ -27,7 +28,7 @@ "description": "Customize the rows per page label.
For localization purposes, you can use the provided translations." }, "nextIconButtonProps": { - "description": "Props applied to the next arrow IconButton element." + "description": "Props applied to the next arrow IconButton element.
This prop is an alias for slotProps.actions.nextButton and will be overriden by it if both are used." }, "onPageChange": { "description": "Callback fired when the page is changed.", @@ -48,10 +49,11 @@ "description": "Customizes the options of the rows per page select field. If less than two options are available, no select field will be displayed. Use -1 for the value with a custom label to show all the rows." }, "SelectProps": { - "description": "Props applied to the rows per page Select element." + "description": "Props applied to the rows per page Select element.
This prop is an alias for slotProps.select and will be overriden by it if both are used." }, "showFirstButton": { "description": "If true, show the first-page button." }, "showLastButton": { "description": "If true, show the last-page button." }, + "slotProps": { "description": "The props used for each slot inside the TablePagination." }, "sx": { "description": "The system prop that allows defining system overrides as well as additional CSS styles." } diff --git a/packages/mui-material/src/TablePagination/TablePagination.d.ts b/packages/mui-material/src/TablePagination/TablePagination.d.ts index 3c72b57d9186b9..609b420e726941 100644 --- a/packages/mui-material/src/TablePagination/TablePagination.d.ts +++ b/packages/mui-material/src/TablePagination/TablePagination.d.ts @@ -29,6 +29,9 @@ export interface TablePaginationOwnProps extends TablePaginationBaseProps { ActionsComponent?: React.ElementType; /** * Props applied to the back arrow [`IconButton`](/material-ui/api/icon-button/) component. + * + * This prop is an alias for `slotProps.actions.previousButton` and will be overriden by it if both are used. + * @deprecated Use `slotProps.actions.previousButton` instead. */ backIconButtonProps?: Partial; /** @@ -41,6 +44,11 @@ export interface TablePaginationOwnProps extends TablePaginationBaseProps { * To enable server side pagination for an unknown number of items, provide -1. */ count: number; + /** + * If `true`, the component is disabled. + * @default false + */ + disabled?: boolean; /** * Accepts a function which returns a string value that provides a user-friendly name for the current page. * This is important for screen reader users. @@ -72,6 +80,9 @@ export interface TablePaginationOwnProps extends TablePaginationBaseProps { labelRowsPerPage?: React.ReactNode; /** * Props applied to the next arrow [`IconButton`](/material-ui/api/icon-button/) element. + * + * This prop is an alias for `slotProps.actions.nextButton` and will be overriden by it if both are used. + * @deprecated Use `slotProps.actions.nextButton` instead. */ nextIconButtonProps?: Partial; /** @@ -106,6 +117,10 @@ export interface TablePaginationOwnProps extends TablePaginationBaseProps { rowsPerPageOptions?: Array; /** * Props applied to the rows per page [`Select`](/material-ui/api/select/) element. + * + * This prop is an alias for `slotProps.select` and will be overriden by it if both are used. + * @deprecated Use `slotProps.select` instead. + * * @default {} */ SelectProps?: Partial; @@ -119,6 +134,14 @@ export interface TablePaginationOwnProps extends TablePaginationBaseProps { * @default false */ showLastButton?: boolean; + /** + * The props used for each slot inside the TablePagination. + * @default {} + */ + slotProps?: { + actions?: TablePaginationActionsProps['slotProps']; + select?: Partial; + }; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/TablePagination/TablePagination.js b/packages/mui-material/src/TablePagination/TablePagination.js index 4e040523cac251..d3d515d46b1383 100644 --- a/packages/mui-material/src/TablePagination/TablePagination.js +++ b/packages/mui-material/src/TablePagination/TablePagination.js @@ -145,6 +145,7 @@ const TablePagination = React.forwardRef(function TablePagination(inProps, ref) colSpan: colSpanProp, component = TableCell, count, + disabled = false, getItemAriaLabel = defaultGetAriaLabel, labelDisplayedRows = defaultLabelDisplayedRows, labelRowsPerPage = 'Rows per page:', @@ -157,21 +158,24 @@ const TablePagination = React.forwardRef(function TablePagination(inProps, ref) SelectProps = {}, showFirstButton = false, showLastButton = false, + slotProps, ...other } = props; const ownerState = props; const classes = useUtilityClasses(ownerState); - const MenuItemComponent = SelectProps.native ? 'option' : TablePaginationMenuItem; + const selectProps = slotProps?.select ?? SelectProps; + + const MenuItemComponent = selectProps.native ? 'option' : TablePaginationMenuItem; let colSpan; if (component === TableCell || component === 'td') { colSpan = colSpanProp || 1000; // col-span over everything } - const selectId = useId(SelectProps.id); - const labelId = useId(SelectProps.labelId); + const selectId = useId(selectProps.id); + const labelId = useId(selectProps.labelId); const getLabelDisplayedRowsTo = () => { if (count === -1) { @@ -200,20 +204,21 @@ const TablePagination = React.forwardRef(function TablePagination(inProps, ref) {rowsPerPageOptions.length > 1 && ( })} + {...(!selectProps.variant && { input: })} value={rowsPerPage} onChange={onRowsPerPageChange} id={selectId} labelId={labelId} - {...SelectProps} + {...selectProps} classes={{ - ...SelectProps.classes, + ...selectProps.classes, // TODO v5 remove `classes.input` - root: clsx(classes.input, classes.selectRoot, (SelectProps.classes || {}).root), - select: clsx(classes.select, (SelectProps.classes || {}).select), + root: clsx(classes.input, classes.selectRoot, (selectProps.classes || {}).root), + select: clsx(classes.select, (selectProps.classes || {}).select), // TODO v5 remove `selectIcon` - icon: clsx(classes.selectIcon, (SelectProps.classes || {}).icon), + icon: clsx(classes.selectIcon, (selectProps.classes || {}).icon), }} + disabled={disabled} > {rowsPerPageOptions.map((rowsPerPageOption) => ( @@ -268,6 +275,9 @@ TablePagination.propTypes /* remove-proptypes */ = { ActionsComponent: PropTypes.elementType, /** * Props applied to the back arrow [`IconButton`](/material-ui/api/icon-button/) component. + * + * This prop is an alias for `slotProps.actions.previousButton` and will be overriden by it if both are used. + * @deprecated Use `slotProps.actions.previousButton` instead. */ backIconButtonProps: PropTypes.object, /** @@ -293,6 +303,11 @@ TablePagination.propTypes /* remove-proptypes */ = { * To enable server side pagination for an unknown number of items, provide -1. */ count: integerPropType.isRequired, + /** + * If `true`, the component is disabled. + * @default false + */ + disabled: PropTypes.bool, /** * Accepts a function which returns a string value that provides a user-friendly name for the current page. * This is important for screen reader users. @@ -324,6 +339,9 @@ TablePagination.propTypes /* remove-proptypes */ = { labelRowsPerPage: PropTypes.node, /** * Props applied to the next arrow [`IconButton`](/material-ui/api/icon-button/) element. + * + * This prop is an alias for `slotProps.actions.nextButton` and will be overriden by it if both are used. + * @deprecated Use `slotProps.actions.nextButton` instead. */ nextIconButtonProps: PropTypes.object, /** @@ -381,6 +399,10 @@ TablePagination.propTypes /* remove-proptypes */ = { ), /** * Props applied to the rows per page [`Select`](/material-ui/api/select/) element. + * + * This prop is an alias for `slotProps.select` and will be overriden by it if both are used. + * @deprecated Use `slotProps.select` instead. + * * @default {} */ SelectProps: PropTypes.object, @@ -394,6 +416,19 @@ TablePagination.propTypes /* remove-proptypes */ = { * @default false */ showLastButton: PropTypes.bool, + /** + * The props used for each slot inside the TablePagination. + * @default {} + */ + slotProps: PropTypes.shape({ + actions: PropTypes.shape({ + firstButton: PropTypes.object, + lastButton: PropTypes.object, + nextButton: PropTypes.object, + previousButton: PropTypes.object, + }), + select: PropTypes.object, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/TablePagination/TablePagination.test.js b/packages/mui-material/src/TablePagination/TablePagination.test.js index 36958b7a1be69b..5b044a54687d00 100644 --- a/packages/mui-material/src/TablePagination/TablePagination.test.js +++ b/packages/mui-material/src/TablePagination/TablePagination.test.js @@ -353,6 +353,112 @@ describe('', () => { }); }); + describe('prop: backIconButtonProps', () => { + it('should apply props to the back button', () => { + const backIconButtonPropsDisabled = true; + + const { getByRole } = render( + + + + + + +
, + ); + + const backButton = getByRole('button', { name: 'Go to previous page' }); + expect(backButton).to.have.property('disabled', backIconButtonPropsDisabled); + }); + }); + + describe('prop: nextIconButtonProps', () => { + it('should apply props to the next button', () => { + const nextIconButtonPropsDisabled = true; + + const { getByRole } = render( + + + + + + +
, + ); + + const nextButton = getByRole('button', { name: 'Go to next page' }); + expect(nextButton).to.have.property('disabled', nextIconButtonPropsDisabled); + }); + }); + + describe('prop: disabled', () => { + it('should disable the first, last, next, and back buttons', () => { + const { getByRole } = render( + + + + + + +
, + ); + + const firstButton = getByRole('button', { name: 'Go to first page' }); + const lastButton = getByRole('button', { name: 'Go to last page' }); + const nextButton = getByRole('button', { name: 'Go to next page' }); + const backButton = getByRole('button', { name: 'Go to previous page' }); + expect(firstButton).to.have.property('disabled', true); + expect(lastButton).to.have.property('disabled', true); + expect(nextButton).to.have.property('disabled', true); + expect(backButton).to.have.property('disabled', true); + }); + + it('should disable TablePaginationSelect', () => { + const { getByRole } = render( + + + + + + +
, + ); + + const combobox = getByRole('combobox'); + expect(combobox.parentElement).to.have.class(inputClasses.disabled); + }); + }); + describe('warnings', () => { beforeEach(() => { PropTypes.resetWarningCache(); @@ -458,6 +564,103 @@ describe('', () => { }); }); + describe('prop: slotProps', () => { + describe('actions', () => { + describe('previousButton', () => { + it('should override backIconButtonProps', () => { + const slotPropsDisabled = false; + const backIconButtonPropsDisabled = true; + + const { getByRole } = render( + + + + + + +
, + ); + + const backButton = getByRole('button', { name: 'Go to previous page' }); + expect(slotPropsDisabled).not.to.equal(backIconButtonPropsDisabled); + expect(backButton).to.have.property('disabled', slotPropsDisabled); + }); + }); + + describe('nextButton', () => { + it('should override nextIconButtonProps', () => { + const slotPropsDisabled = false; + const nextIconButtonPropsDisabled = true; + + const { getByRole } = render( + + + + + + +
, + ); + + const nextButton = getByRole('button', { name: 'Go to next page' }); + expect(slotPropsDisabled).not.to.equal(nextIconButtonPropsDisabled); + expect(nextButton).to.have.property('disabled', slotPropsDisabled); + }); + }); + }); + + describe('select', () => { + it('should override SelectProps', () => { + const slotPropsDisabled = false; + const SelectPropsDisabled = true; + + const { getByRole } = render( + + + + + + +
, + ); + + const combobox = getByRole('combobox'); + expect(slotPropsDisabled).not.to.equal(SelectPropsDisabled); + expect(combobox.parentElement).not.to.have.class(inputClasses.disabled); + }); + }); + }); + describe('duplicated keys', () => { it('should not raise a warning due to duplicated keys', () => { render( diff --git a/packages/mui-material/src/TablePagination/TablePaginationActions.d.ts b/packages/mui-material/src/TablePagination/TablePaginationActions.d.ts index 843b2fdd5ee61b..d0d555421360e1 100644 --- a/packages/mui-material/src/TablePagination/TablePaginationActions.d.ts +++ b/packages/mui-material/src/TablePagination/TablePaginationActions.d.ts @@ -2,12 +2,21 @@ import * as React from 'react'; import { IconButtonProps } from '../IconButton/IconButton'; export interface TablePaginationActionsProps extends React.HTMLAttributes { + /** + * This prop is an alias for `slotProps.previousButton` and will be overriden by it if both are used. + * @deprecated Use `slotProps.previousButton` instead. + */ backIconButtonProps?: Partial; /** * Override or extend the styles applied to the component. */ classes?: {}; count: number; + /** + * If `true`, the component is disabled. + * @default false + */ + disabled?: boolean; /** * Accepts a function which returns a string value that provides a user-friendly name for the current page. * This is important for screen reader users. @@ -17,12 +26,22 @@ export interface TablePaginationActionsProps extends React.HTMLAttributes string; + /** + * This prop is an alias for `slotProps.nextButton` and will be overriden by it if both are used. + * @deprecated Use `slotProps.nextButton` instead. + */ nextIconButtonProps?: Partial; onPageChange: (event: React.MouseEvent | null, page: number) => void; page: number; rowsPerPage: number; showFirstButton: boolean; showLastButton: boolean; + slotProps?: { + firstButton?: Partial; + lastButton?: Partial; + nextButton?: Partial; + previousButton?: Partial; + }; } declare const TablePaginationActions: React.JSXElementConstructor; diff --git a/packages/mui-material/src/TablePagination/TablePaginationActions.js b/packages/mui-material/src/TablePagination/TablePaginationActions.js index 9e3e1d61d4249c..6d9ab6cedf1e6b 100644 --- a/packages/mui-material/src/TablePagination/TablePaginationActions.js +++ b/packages/mui-material/src/TablePagination/TablePaginationActions.js @@ -15,6 +15,7 @@ const TablePaginationActions = React.forwardRef(function TablePaginationActions( const { backIconButtonProps, count, + disabled = false, getItemAriaLabel, nextIconButtonProps, onPageChange, @@ -22,6 +23,7 @@ const TablePaginationActions = React.forwardRef(function TablePaginationActions( rowsPerPage, showFirstButton, showLastButton, + slotProps, ...other } = props; @@ -48,39 +50,41 @@ const TablePaginationActions = React.forwardRef(function TablePaginationActions( {showFirstButton && ( {theme.direction === 'rtl' ? : } )} {theme.direction === 'rtl' ? : } = Math.ceil(count / rowsPerPage) - 1 : false} + disabled={disabled || (count !== -1 ? page >= Math.ceil(count / rowsPerPage) - 1 : false)} color="inherit" aria-label={getItemAriaLabel('next', page)} title={getItemAriaLabel('next', page)} - {...nextIconButtonProps} + {...(slotProps?.nextButton ?? nextIconButtonProps)} > {theme.direction === 'rtl' ? : } {showLastButton && ( = Math.ceil(count / rowsPerPage) - 1} + disabled={disabled || page >= Math.ceil(count / rowsPerPage) - 1} aria-label={getItemAriaLabel('last', page)} title={getItemAriaLabel('last', page)} + {...(slotProps?.lastButton ?? {})} > {theme.direction === 'rtl' ? : } @@ -98,6 +102,11 @@ TablePaginationActions.propTypes = { * The total number of rows. */ count: PropTypes.number.isRequired, + /** + * If `true`, the component is disabled. + * @default false + */ + disabled: PropTypes.bool, /** * Accepts a function which returns a string value that provides a user-friendly name for the current page. * @@ -135,6 +144,16 @@ TablePaginationActions.propTypes = { * If `true`, show the last-page button. */ showLastButton: PropTypes.bool.isRequired, + /** + * The props used for each slot inside the TablePaginationActions. + * @default {} + */ + slotProps: PropTypes.shape({ + firstButton: PropTypes.object, + lastButton: PropTypes.object, + nextButton: PropTypes.object, + previousButton: PropTypes.object, + }), }; export default TablePaginationActions;