Skip to content

Commit a5d6da7

Browse files
[material-ui][TablePagination] Add ability to change icons in TablePaginationActions using slots and slotProps (#33797)
Co-authored-by: ZeeshanTamboli <[email protected]>
1 parent 30484b8 commit a5d6da7

File tree

8 files changed

+370
-22
lines changed

8 files changed

+370
-22
lines changed

docs/pages/material-ui/api/table-pagination.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,14 @@
6060
"slotProps": {
6161
"type": {
6262
"name": "shape",
63-
"description": "{ actions?: { firstButton?: object, lastButton?: object, nextButton?: object, previousButton?: object }, select?: object }"
63+
"description": "{ actions?: { firstButton?: object, firstButtonIcon?: object, lastButton?: object, lastButtonIcon?: object, nextButton?: object, nextButtonIcon?: object, previousButton?: object, previousButtonIcon?: object }, select?: object }"
64+
},
65+
"default": "{}"
66+
},
67+
"slots": {
68+
"type": {
69+
"name": "shape",
70+
"description": "{ actions?: { firstButton?: elementType, firstButtonIcon?: elementType, lastButton?: elementType, lastButtonIcon?: elementType, nextButton?: elementType, nextButtonIcon?: elementType, previousButton?: elementType, previousButtonIcon?: elementType } }"
6471
},
6572
"default": "{}"
6673
},

docs/translations/api-docs/table-pagination/table-pagination.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
"showFirstButton": { "description": "If <code>true</code>, show the first-page button." },
5555
"showLastButton": { "description": "If <code>true</code>, show the last-page button." },
5656
"slotProps": { "description": "The props used for each slot inside the TablePagination." },
57+
"slots": {
58+
"description": "The components used for each slot inside the TablePagination. Either a string to use a HTML element or a component."
59+
},
5760
"sx": {
5861
"description": "The system prop that allows defining system overrides as well as additional CSS styles."
5962
}

packages/mui-material/src/TablePagination/TablePagination.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ export interface TablePaginationOwnProps extends TablePaginationBaseProps {
142142
actions?: TablePaginationActionsProps['slotProps'];
143143
select?: Partial<SelectProps>;
144144
};
145+
/**
146+
* The components used for each slot inside the TablePagination.
147+
* Either a string to use a HTML element or a component.
148+
* @default {}
149+
*/
150+
slots?: {
151+
actions?: TablePaginationActionsProps['slots'];
152+
};
145153
/**
146154
* The system prop that allows defining system overrides as well as additional CSS styles.
147155
*/

packages/mui-material/src/TablePagination/TablePagination.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ const TablePagination = React.forwardRef(function TablePagination(inProps, ref)
158158
SelectProps = {},
159159
showFirstButton = false,
160160
showLastButton = false,
161-
slotProps,
161+
slotProps = {},
162+
slots = {},
162163
...other
163164
} = props;
164165

@@ -253,7 +254,8 @@ const TablePagination = React.forwardRef(function TablePagination(inProps, ref)
253254
rowsPerPage={rowsPerPage}
254255
showFirstButton={showFirstButton}
255256
showLastButton={showLastButton}
256-
slotProps={slotProps?.actions}
257+
slotProps={slotProps.actions}
258+
slots={slots.actions}
257259
getItemAriaLabel={getItemAriaLabel}
258260
disabled={disabled}
259261
/>
@@ -423,12 +425,33 @@ TablePagination.propTypes /* remove-proptypes */ = {
423425
slotProps: PropTypes.shape({
424426
actions: PropTypes.shape({
425427
firstButton: PropTypes.object,
428+
firstButtonIcon: PropTypes.object,
426429
lastButton: PropTypes.object,
430+
lastButtonIcon: PropTypes.object,
427431
nextButton: PropTypes.object,
432+
nextButtonIcon: PropTypes.object,
428433
previousButton: PropTypes.object,
434+
previousButtonIcon: PropTypes.object,
429435
}),
430436
select: PropTypes.object,
431437
}),
438+
/**
439+
* The components used for each slot inside the TablePagination.
440+
* Either a string to use a HTML element or a component.
441+
* @default {}
442+
*/
443+
slots: PropTypes.shape({
444+
actions: PropTypes.shape({
445+
firstButton: PropTypes.elementType,
446+
firstButtonIcon: PropTypes.elementType,
447+
lastButton: PropTypes.elementType,
448+
lastButtonIcon: PropTypes.elementType,
449+
nextButton: PropTypes.elementType,
450+
nextButtonIcon: PropTypes.elementType,
451+
previousButton: PropTypes.elementType,
452+
previousButtonIcon: PropTypes.elementType,
453+
}),
454+
}),
432455
/**
433456
* The system prop that allows defining system overrides as well as additional CSS styles.
434457
*/

packages/mui-material/src/TablePagination/TablePagination.spec.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
import * as React from 'react';
22
import TablePagination from '@mui/material/TablePagination';
3+
import SvgIcon from '@mui/material/SvgIcon';
4+
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
5+
6+
function SampleIcon() {
7+
return (
8+
<SvgIcon>
9+
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
10+
</SvgIcon>
11+
);
12+
}
13+
14+
function CustomIconButton(props: IconButtonProps) {
15+
const { children, ...other } = props;
16+
return (
17+
<IconButton {...other} color="secondary" size="large">
18+
{children}
19+
</IconButton>
20+
);
21+
}
322

423
function classesTest() {
524
const defaultProps = {
@@ -13,3 +32,57 @@ function classesTest() {
1332
// @ts-expect-error desired
1433
<TablePagination classes={{ alignCenter: 'center' }} {...defaultProps} />;
1534
}
35+
36+
// slots and slotProps type test
37+
<TablePagination
38+
rowsPerPageOptions={[10, 25, 100]}
39+
component="div"
40+
count={1}
41+
rowsPerPage={1}
42+
page={1}
43+
onPageChange={() => {}}
44+
showFirstButton
45+
showLastButton
46+
slots={{
47+
actions: {
48+
firstButton: CustomIconButton,
49+
lastButton: CustomIconButton,
50+
nextButton: CustomIconButton,
51+
previousButton: CustomIconButton,
52+
53+
firstButtonIcon: SampleIcon,
54+
lastButtonIcon: SampleIcon,
55+
nextButtonIcon: SampleIcon,
56+
previousButtonIcon: SampleIcon,
57+
},
58+
}}
59+
slotProps={{
60+
actions: {
61+
firstButton: {
62+
disableFocusRipple: true,
63+
},
64+
lastButton: {
65+
disableTouchRipple: true,
66+
},
67+
nextButton: {
68+
disableRipple: true,
69+
},
70+
previousButton: {
71+
centerRipple: true,
72+
},
73+
74+
firstButtonIcon: {
75+
fontSize: 'small',
76+
},
77+
lastButtonIcon: {
78+
color: 'success',
79+
},
80+
nextButtonIcon: {
81+
inheritViewBox: true,
82+
},
83+
previousButtonIcon: {
84+
fill: 'currentColor',
85+
},
86+
},
87+
}}
88+
/>;

packages/mui-material/src/TablePagination/TablePagination.test.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import TablePagination, { tablePaginationClasses as classes } from '@mui/materia
1010
import { inputClasses } from '@mui/material/Input';
1111
import { outlinedInputClasses } from '@mui/material/OutlinedInput';
1212
import { filledInputClasses } from '@mui/material/FilledInput';
13+
import IconButton, { iconButtonClasses } from '@mui/material/IconButton';
14+
import { svgIconClasses } from '@mui/material/SvgIcon';
15+
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
16+
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
17+
import KeyboardDoubleArrowLeftRoundedIcon from '@mui/icons-material/KeyboardDoubleArrowLeftRounded';
18+
import KeyboardDoubleArrowRightRoundedIcon from '@mui/icons-material/KeyboardDoubleArrowRightRounded';
1319

1420
describe('<TablePagination />', () => {
1521
const noop = () => {};
@@ -629,6 +635,44 @@ describe('<TablePagination />', () => {
629635
expect(nextButton).to.have.property('disabled', slotPropsDisabled);
630636
});
631637
});
638+
639+
it('should pass props to button icons', () => {
640+
const { getByTestId } = render(
641+
<table>
642+
<TableFooter>
643+
<TableRow>
644+
<TablePagination
645+
count={1}
646+
page={0}
647+
onPageChange={noop}
648+
onRowsPerPageChange={noop}
649+
rowsPerPage={10}
650+
showFirstButton
651+
showLastButton
652+
slotProps={{
653+
actions: {
654+
firstButtonIcon: {
655+
fontSize: 'small',
656+
},
657+
lastButtonIcon: {
658+
fontSize: 'large',
659+
},
660+
previousButtonIcon: {
661+
fontSize: 'inherit',
662+
},
663+
},
664+
}}
665+
/>
666+
</TableRow>
667+
</TableFooter>
668+
</table>,
669+
);
670+
671+
expect(getByTestId('FirstPageIcon')).to.have.class(svgIconClasses.fontSizeSmall);
672+
expect(getByTestId('LastPageIcon')).to.have.class(svgIconClasses.fontSizeLarge);
673+
expect(getByTestId('KeyboardArrowLeftIcon')).to.have.class(svgIconClasses.fontSizeInherit);
674+
expect(getByTestId('KeyboardArrowRightIcon')).to.have.class(svgIconClasses.fontSizeMedium);
675+
});
632676
});
633677

634678
describe('select', () => {
@@ -661,6 +705,88 @@ describe('<TablePagination />', () => {
661705
});
662706
});
663707

708+
describe('prop: slots', () => {
709+
it('should render custom action buttons', () => {
710+
function CustomIconButton(props) {
711+
const { children, ...other } = props;
712+
return (
713+
<IconButton {...other} color="secondary">
714+
{children}
715+
</IconButton>
716+
);
717+
}
718+
719+
const { getByRole } = render(
720+
<table>
721+
<TableFooter>
722+
<TableRow>
723+
<TablePagination
724+
count={1}
725+
page={0}
726+
onPageChange={noop}
727+
onRowsPerPageChange={noop}
728+
rowsPerPage={10}
729+
showFirstButton
730+
showLastButton
731+
slots={{
732+
actions: {
733+
firstButton: CustomIconButton,
734+
lastButton: CustomIconButton,
735+
nextButton: CustomIconButton,
736+
previousButton: CustomIconButton,
737+
},
738+
}}
739+
/>
740+
</TableRow>
741+
</TableFooter>
742+
</table>,
743+
);
744+
745+
const firstButton = getByRole('button', { name: 'Go to first page' });
746+
const lastButton = getByRole('button', { name: 'Go to last page' });
747+
const nextButton = getByRole('button', { name: 'Go to next page' });
748+
const previousButton = getByRole('button', { name: 'Go to previous page' });
749+
750+
expect(firstButton).to.have.class(iconButtonClasses.colorSecondary);
751+
expect(lastButton).to.have.class(iconButtonClasses.colorSecondary);
752+
expect(nextButton).to.have.class(iconButtonClasses.colorSecondary);
753+
expect(previousButton).to.have.class(iconButtonClasses.colorSecondary);
754+
});
755+
756+
it('should render custom action button icons', () => {
757+
const { getByTestId } = render(
758+
<table>
759+
<TableFooter>
760+
<TableRow>
761+
<TablePagination
762+
count={1}
763+
page={0}
764+
onPageChange={noop}
765+
onRowsPerPageChange={noop}
766+
rowsPerPage={10}
767+
showFirstButton
768+
showLastButton
769+
slots={{
770+
actions: {
771+
firstButtonIcon: KeyboardDoubleArrowLeftRoundedIcon,
772+
lastButtonIcon: KeyboardDoubleArrowRightRoundedIcon,
773+
previousButtonIcon: ArrowBackIcon,
774+
nextButtonIcon: ArrowForwardIcon,
775+
},
776+
}}
777+
/>
778+
</TableRow>
779+
</TableFooter>
780+
</table>,
781+
);
782+
783+
expect(getByTestId('KeyboardDoubleArrowLeftRoundedIcon')).not.to.equal(null);
784+
expect(getByTestId('KeyboardDoubleArrowRightRoundedIcon')).not.to.equal(null);
785+
expect(getByTestId('ArrowBackIcon')).not.to.equal(null);
786+
expect(getByTestId('ArrowForwardIcon')).not.to.equal(null);
787+
});
788+
});
789+
664790
describe('duplicated keys', () => {
665791
it('should not raise a warning due to duplicated keys', () => {
666792
render(

packages/mui-material/src/TablePagination/TablePaginationActions.d.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from 'react';
22
import { IconButtonProps } from '../IconButton/IconButton';
3+
import { SvgIconProps } from '../SvgIcon';
34

45
export interface TablePaginationActionsProps extends React.HTMLAttributes<HTMLDivElement> {
56
/**
@@ -41,7 +42,55 @@ export interface TablePaginationActionsProps extends React.HTMLAttributes<HTMLDi
4142
lastButton?: Partial<IconButtonProps>;
4243
nextButton?: Partial<IconButtonProps>;
4344
previousButton?: Partial<IconButtonProps>;
45+
firstButtonIcon?: Partial<SvgIconProps>;
46+
lastButtonIcon?: Partial<SvgIconProps>;
47+
nextButtonIcon?: Partial<SvgIconProps>;
48+
previousButtonIcon?: Partial<SvgIconProps>;
4449
};
50+
slots?: TablePaginationActionsSlots;
51+
}
52+
53+
export interface TablePaginationActionsSlots {
54+
/**
55+
* The component that renders the first button.
56+
* @default IconButton
57+
*/
58+
firstButton?: React.ElementType;
59+
/**
60+
* The component that renders the last button.
61+
* @default IconButton
62+
*/
63+
lastButton?: React.ElementType;
64+
/**
65+
* The component that renders the next button.
66+
* @default IconButton
67+
*/
68+
nextButton?: React.ElementType;
69+
/**
70+
* The component that renders the previous button.
71+
* @default IconButton
72+
*/
73+
previousButton?: React.ElementType;
74+
/**
75+
* The component that renders the first button icon.
76+
* @default FirstPageIcon
77+
*/
78+
firstButtonIcon?: React.ElementType;
79+
/**
80+
* The component that renders the last button icon.
81+
* @default LastPageIcon
82+
*/
83+
lastButtonIcon?: React.ElementType;
84+
/**
85+
* The component that renders the next button icon.
86+
* @default KeyboardArrowRight
87+
*/
88+
nextButtonIcon?: React.ElementType;
89+
/**
90+
* The component that renders the previous button icon.
91+
* @default KeyboardArrowLeft
92+
*/
93+
previousButtonIcon?: React.ElementType;
4594
}
4695

4796
declare const TablePaginationActions: React.JSXElementConstructor<TablePaginationActionsProps>;

0 commit comments

Comments
 (0)