Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Menu][material] Fixes slots and slotProps overriding defaults completely #37902

Merged
merged 9 commits into from
Jul 27, 2023
11 changes: 11 additions & 0 deletions docs/pages/material-ui/api/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
}
},
"PopoverClasses": { "type": { "name": "object" } },
"slotProps": {
"type": {
"name": "shape",
"description": "{ paper?: func<br>&#124;&nbsp;object, root?: func<br>&#124;&nbsp;object }"
},
"default": "{}"
},
"slots": {
"type": { "name": "shape", "description": "{ paper?: elementType, root?: elementType }" },
"default": "{}"
},
"sx": {
"type": {
"name": "union",
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/menu/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
"PopoverClasses": {
"description": "<code>classes</code> prop applied to the <a href=\"/material-ui/api/popover/\"><code>Popover</code></a> element."
},
"slotProps": {
"description": "The extra props for the slot components. You can override the existing props or add new ones."
},
"slots": { "description": "The components used for each slot inside." },
"sx": {
"description": "The system prop that allows defining system overrides as well as additional CSS styles."
},
Expand Down
25 changes: 23 additions & 2 deletions packages/mui-material/src/Menu/Menu.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import * as React from 'react';
import { SxProps } from '@mui/system';
import { MenuOwnerState, SlotComponentProps } from '@mui/base';
import { InternalStandardProps as StandardProps } from '..';
import { PaperProps } from '../Paper';
import Paper, { PaperProps } from '../Paper';
import { PopoverProps } from '../Popover';
import { MenuListProps } from '../MenuList';
import { Theme } from '../styles';
import { TransitionProps } from '../transitions/transition';
import { MenuClasses } from './menuClasses';

export interface MenuProps extends StandardProps<PopoverProps> {
export interface MenuProps
extends StandardProps<Omit<PopoverProps, 'slots' | 'slotProps'>, 'children'> {
/**
* An HTML element, or a function that returns one.
* It's used to set the position of the menu.
Expand Down Expand Up @@ -58,6 +60,25 @@ export interface MenuProps extends StandardProps<PopoverProps> {
* `classes` prop applied to the [`Popover`](/material-ui/api/popover/) element.
*/
PopoverClasses?: PopoverProps['classes'];
/**
* The components used for each slot inside.
*
* @default {}
*/
slots?: {
root?: React.ElementType;
paper?: React.ElementType;
};
/**
* The extra props for the slot components.
* You can override the existing props or add new ones.
*
* @default {}
*/
slotProps?: {
root?: SlotComponentProps<typeof Menu, {}, MenuOwnerState>;
paper?: SlotComponentProps<typeof Paper, {}, {}>;
};
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
Expand Down
60 changes: 50 additions & 10 deletions packages/mui-material/src/Menu/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';
import { isFragment } from 'react-is';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { unstable_composeClasses as composeClasses } from '@mui/base';
import { unstable_composeClasses as composeClasses, useSlotProps } from '@mui/base';
import { HTMLElementType } from '@mui/utils';
import MenuList from '../MenuList';
import Popover, { PopoverPaper } from '../Popover';
Expand Down Expand Up @@ -69,6 +69,7 @@ const Menu = React.forwardRef(function Menu(inProps, ref) {
const {
autoFocus = true,
children,
className,
disableAutoFocusItem = false,
MenuListProps = {},
onClose,
Expand All @@ -78,6 +79,8 @@ const Menu = React.forwardRef(function Menu(inProps, ref) {
transitionDuration = 'auto',
TransitionProps: { onEntering, ...TransitionProps } = {},
variant = 'selectedMenu',
slots = {},
slotProps = {},
...other
} = props;

Expand Down Expand Up @@ -156,6 +159,23 @@ const Menu = React.forwardRef(function Menu(inProps, ref) {
}
});

const PaperSlot = slots.paper ?? MenuPaper;
const paperExternalSlotProps = slotProps.paper ?? PaperProps;

const rootSlotProps = useSlotProps({
elementType: slots.root,
externalSlotProps: slotProps.root,
ownerState,
className: [classes.root, className],
});

const paperSlotProps = useSlotProps({
elementType: PaperSlot,
externalSlotProps: paperExternalSlotProps,
ownerState,
className: classes.paper,
});

return (
<MenuRoot
onClose={onClose}
Expand All @@ -164,17 +184,14 @@ const Menu = React.forwardRef(function Menu(inProps, ref) {
horizontal: isRtl ? 'right' : 'left',
}}
transformOrigin={isRtl ? RTL_ORIGIN : LTR_ORIGIN}
slots={{ paper: MenuPaper }}
slots={{
paper: PaperSlot,
root: slots.root,
}}
slotProps={{
paper: {
...PaperProps,
classes: {
...PaperProps.classes,
root: classes.paper,
},
},
root: rootSlotProps,
paper: paperSlotProps,
}}
className={classes.root}
open={open}
ref={ref}
transitionDuration={transitionDuration}
Expand Down Expand Up @@ -227,6 +244,10 @@ Menu.propTypes /* remove-proptypes */ = {
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* @ignore
*/
className: PropTypes.string,
/**
* When opening the menu will not focus the active item but the `[role="menu"]`
* unless `autoFocus` is also set to `false`. Not using the default means not
Expand Down Expand Up @@ -259,6 +280,25 @@ Menu.propTypes /* remove-proptypes */ = {
* `classes` prop applied to the [`Popover`](/material-ui/api/popover/) element.
*/
PopoverClasses: PropTypes.object,
/**
* The extra props for the slot components.
* You can override the existing props or add new ones.
*
* @default {}
*/
slotProps: PropTypes.shape({
paper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}),
/**
* The components used for each slot inside.
*
* @default {}
*/
slots: PropTypes.shape({
paper: PropTypes.elementType,
root: PropTypes.elementType,
}),
DiegoAndai marked this conversation as resolved.
Show resolved Hide resolved
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
Expand Down
20 changes: 20 additions & 0 deletions packages/mui-material/src/Menu/Menu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ describe('<Menu />', () => {
render,
muiName: 'MuiMenu',
refInstanceof: window.HTMLDivElement,
slots: {
root: {
expectedClassName: classes.root,
},
paper: {
expectedClassName: classes.paper,
},
},
testDeepOverrides: { slotName: 'list', slotClassName: classes.list },
testRootOverrides: { slotName: 'root', slotClassName: classes.root },
testVariantProps: { variant: 'menu' },
Expand Down Expand Up @@ -384,4 +392,16 @@ describe('<Menu />', () => {
expect(wrapper.find(MenuPaper)).to.have.length(1);
});
});

describe('slots', () => {
it('should merge slots with existing values', () => {
const wrapper = mount(
<Menu slots={{ root: 'span' }} anchorEl={document.createElement('div')} open>
<div />
</Menu>,
);

expect(wrapper.find(MenuPaper)).to.have.length(1);
});
});
});