diff --git a/docs/data/material/components/snackbars/ConsecutiveSnackbars.js b/docs/data/material/components/snackbars/ConsecutiveSnackbars.js index eddd4bfdf00000..3328ba085e5484 100644 --- a/docs/data/material/components/snackbars/ConsecutiveSnackbars.js +++ b/docs/data/material/components/snackbars/ConsecutiveSnackbars.js @@ -45,7 +45,7 @@ export default function ConsecutiveSnackbars() { open={open} autoHideDuration={6000} onClose={handleClose} - TransitionProps={{ onExited: handleExited }} + slotProps={{ transition: { onExited: handleExited } }} message={messageInfo ? messageInfo.message : undefined} action={ diff --git a/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx b/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx index 633379ab04a5c4..22d29214f9ced5 100644 --- a/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx +++ b/docs/data/material/components/snackbars/ConsecutiveSnackbars.tsx @@ -55,7 +55,7 @@ export default function ConsecutiveSnackbars() { open={open} autoHideDuration={6000} onClose={handleClose} - TransitionProps={{ onExited: handleExited }} + slotProps={{ transition: { onExited: handleExited } }} message={messageInfo ? messageInfo.message : undefined} action={ diff --git a/docs/data/material/components/snackbars/DirectionSnackbar.js b/docs/data/material/components/snackbars/DirectionSnackbar.js index 945e647c39338a..0a1330fde2d8d7 100644 --- a/docs/data/material/components/snackbars/DirectionSnackbar.js +++ b/docs/data/material/components/snackbars/DirectionSnackbar.js @@ -53,7 +53,7 @@ export default function DirectionSnackbar() { diff --git a/docs/data/material/components/snackbars/DirectionSnackbar.tsx b/docs/data/material/components/snackbars/DirectionSnackbar.tsx index d4377e7d8e1495..ef02be1e934e5b 100644 --- a/docs/data/material/components/snackbars/DirectionSnackbar.tsx +++ b/docs/data/material/components/snackbars/DirectionSnackbar.tsx @@ -57,7 +57,7 @@ export default function DirectionSnackbar() { diff --git a/docs/data/material/components/snackbars/TransitionsSnackbar.js b/docs/data/material/components/snackbars/TransitionsSnackbar.js index 8815424baeffba..538a37f32ce465 100644 --- a/docs/data/material/components/snackbars/TransitionsSnackbar.js +++ b/docs/data/material/components/snackbars/TransitionsSnackbar.js @@ -41,7 +41,7 @@ export default function TransitionsSnackbar() { slotProps.clickAwayListener instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, + "ContentProps": { + "type": { "name": "object" }, + "deprecated": true, + "deprecationInfo": "Use slotProps.content instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, "disableWindowBlurListener": { "type": { "name": "bool" }, "default": "false" }, "key": { "type": { "name": "custom", "description": "any" } }, "message": { "type": { "name": "node" } }, @@ -25,6 +33,20 @@ }, "open": { "type": { "name": "bool" } }, "resumeHideDuration": { "type": { "name": "number" } }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ clickAwayListener?: func
| object
| { children: element, disableReactTree?: bool, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway?: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }
| { children: element, disableReactTree?: bool, key?: number
| { __@toStringTag@9059: 'BigInt', toLocaleString: func, toString: func, valueOf: func }
| string, mouseEvent?: 'onClick'
| 'onMouseDown'
| 'onMouseUp'
| 'onPointerDown'
| 'onPointerUp'
| false, onClickAway?: func, touchEvent?: 'onTouchEnd'
| 'onTouchStart'
| false }, content?: func
| object, root?: func
| object, transition?: func
| object }" + }, + "default": "{}" + }, + "slots": { + "type": { + "name": "shape", + "description": "{ clickAwayListener?: elementType, content?: elementType, root?: elementType, transition?: elementType }" + }, + "default": "{}" + }, "sx": { "type": { "name": "union", @@ -32,7 +54,12 @@ }, "additionalInfo": { "sx": true } }, - "TransitionComponent": { "type": { "name": "elementType" }, "default": "Grow" }, + "TransitionComponent": { + "type": { "name": "elementType" }, + "default": "Grow", + "deprecated": true, + "deprecationInfo": "Use slots.transition instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, "transitionDuration": { "type": { "name": "union", @@ -40,13 +67,44 @@ }, "default": "{\n enter: theme.transitions.duration.enteringScreen,\n exit: theme.transitions.duration.leavingScreen,\n}" }, - "TransitionProps": { "type": { "name": "object" }, "default": "{}" } + "TransitionProps": { + "type": { "name": "object" }, + "default": "{}", + "deprecated": true, + "deprecationInfo": "Use slotProps.transition instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + } }, "name": "Snackbar", "imports": [ "import Snackbar from '@mui/material/Snackbar';", "import { Snackbar } from '@mui/material';" ], + "slots": [ + { + "name": "root", + "description": "The component that renders the root slot.", + "default": "'div'", + "class": "MuiSnackbar-root" + }, + { + "name": "content", + "description": "The component that renders the content slot.", + "default": "SnackbarContent", + "class": null + }, + { + "name": "clickAwayListener", + "description": "The component that renders the clickAwayListener slot.", + "default": "ClickAwayListener", + "class": null + }, + { + "name": "transition", + "description": "The component that renders the transition.\n[Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.", + "default": "Grow", + "class": null + } + ], "classes": [ { "key": "anchorOriginBottomCenter", @@ -83,12 +141,6 @@ "className": "MuiSnackbar-anchorOriginTopRight", "description": "Styles applied to the root element if `anchorOrigin={{ 'top', 'right' }}`.", "isGlobal": false - }, - { - "key": "root", - "className": "MuiSnackbar-root", - "description": "Styles applied to the root element.", - "isGlobal": false } ], "spread": true, diff --git a/docs/translations/api-docs/snackbar/snackbar.json b/docs/translations/api-docs/snackbar/snackbar.json index ed804f558b8731..3d54114c89d633 100644 --- a/docs/translations/api-docs/snackbar/snackbar.json +++ b/docs/translations/api-docs/snackbar/snackbar.json @@ -36,6 +36,8 @@ "resumeHideDuration": { "description": "The number of milliseconds to wait before dismissing after user interaction. If autoHideDuration prop isn't specified, it does nothing. If autoHideDuration prop is specified but resumeHideDuration isn't, we default to autoHideDuration / 2 ms." }, + "slotProps": { "description": "The props used for each slot inside." }, + "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." }, @@ -79,7 +81,12 @@ "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the root element", "conditions": "anchorOrigin={{ 'top', 'right' }}" - }, - "root": { "description": "Styles applied to the root element." } + } + }, + "slotDescriptions": { + "clickAwayListener": "The component that renders the clickAwayListener slot.", + "content": "The component that renders the content slot.", + "root": "The component that renders the root slot.", + "transition": "The component that renders the transition. Follow this guide to learn more about the requirements for this component." } } diff --git a/packages/mui-codemod/README.md b/packages/mui-codemod/README.md index 6e623adea9be17..071d30a28663d7 100644 --- a/packages/mui-codemod/README.md +++ b/packages/mui-codemod/README.md @@ -1612,6 +1612,27 @@ npx @mui/codemod@latest deprecations/select-classes npx @mui/codemod@latest deprecations/slider-props ``` +#### `snackbar-props` + +```diff + +``` + +```bash +npx @mui/codemod@next deprecations/snackbar-props +``` + #### `tooltip-props` ```diff diff --git a/packages/mui-codemod/src/deprecations/all/deprecations-all.js b/packages/mui-codemod/src/deprecations/all/deprecations-all.js index eb1f0df8a16071..4f14ae485ac3ed 100644 --- a/packages/mui-codemod/src/deprecations/all/deprecations-all.js +++ b/packages/mui-codemod/src/deprecations/all/deprecations-all.js @@ -34,6 +34,7 @@ import transformTooltipProps from '../tooltip-props'; import transformTablePaginationProps from '../table-pagination-props'; import transformCardHeaderProps from '../card-header-props'; import transformPopoverProps from '../popover-props'; +import transformSnackbarProps from '../snackbar-props'; /** * @param {import('jscodeshift').FileInfo} file @@ -76,6 +77,7 @@ export default function deprecationsAll(file, api, options) { file.source = transformTablePaginationProps(file, api, options); file.source = transformCardHeaderProps(file, api, options); file.source = transformPopoverProps(file, api, options); + file.source = transformSnackbarProps(file, api, options); return file.source; } diff --git a/packages/mui-codemod/src/deprecations/snackbar-props/index.js b/packages/mui-codemod/src/deprecations/snackbar-props/index.js new file mode 100644 index 00000000000000..d4ce71d259eee8 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/snackbar-props/index.js @@ -0,0 +1 @@ +export { default } from './snackbar-props'; diff --git a/packages/mui-codemod/src/deprecations/snackbar-props/snackbar-props.js b/packages/mui-codemod/src/deprecations/snackbar-props/snackbar-props.js new file mode 100644 index 00000000000000..5644c1bcd7691f --- /dev/null +++ b/packages/mui-codemod/src/deprecations/snackbar-props/snackbar-props.js @@ -0,0 +1,42 @@ +import movePropIntoSlots from '../utils/movePropIntoSlots'; +import movePropIntoSlotProps from '../utils/movePropIntoSlotProps'; + +/** + * @param {import('jscodeshift').FileInfo} file + * @param {import('jscodeshift').API} api + */ +export default function transformer(file, api, options) { + const j = api.jscodeshift; + const root = j(file.source); + const printOptions = options.printOptions; + + movePropIntoSlots(j, { + root, + componentName: 'Snackbar', + propName: 'TransitionComponent', + slotName: 'transition', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'Snackbar', + propName: 'TransitionProps', + slotName: 'transition', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'Snackbar', + propName: 'ContentProps', + slotName: 'content', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'Snackbar', + propName: 'ClickAwayListenerProps', + slotName: 'clickAwayListener', + }); + + return root.toSource(printOptions); +} diff --git a/packages/mui-codemod/src/deprecations/snackbar-props/snackbar-props.test.js b/packages/mui-codemod/src/deprecations/snackbar-props/snackbar-props.test.js new file mode 100644 index 00000000000000..5b9887a9024411 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/snackbar-props/snackbar-props.test.js @@ -0,0 +1,16 @@ +import { describeJscodeshiftTransform } from '../../../testUtils'; +import transform from './snackbar-props'; + +describe('@mui/codemod', () => { + describe('deprecations', () => { + describeJscodeshiftTransform({ + transform, + transformName: 'snackbar-props', + dirname: __dirname, + testCases: [ + { actual: '/test-cases/actual.js', expected: '/test-cases/expected.js' }, + { actual: '/test-cases/theme.actual.js', expected: '/test-cases/theme.expected.js' }, + ], + }); + }); +}); diff --git a/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/actual.js new file mode 100644 index 00000000000000..27dcf7ef959986 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/actual.js @@ -0,0 +1,51 @@ +import Snackbar from '@mui/material/Snackbar'; +import { Snackbar as MySnackbar } from '@mui/material'; + +; +; +; +; +; + +// should skip non MUI components +; diff --git a/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/expected.js new file mode 100644 index 00000000000000..6a0fecdf2cc197 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/expected.js @@ -0,0 +1,59 @@ +import Snackbar from '@mui/material/Snackbar'; +import { Snackbar as MySnackbar } from '@mui/material'; + +; +; +; +; +; + +// should skip non MUI components +; diff --git a/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/theme.actual.js new file mode 100644 index 00000000000000..8c616146434013 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/theme.actual.js @@ -0,0 +1,39 @@ +fn({ + MuiSnackbar: { + defaultProps: { + ClickAwayListenerProps: CustomListenerProps, + ContentProps: CustomContentProps, + TransitionComponent: CustomTransition, + TransitionProps: CustomTransitionProps, + }, + }, +}); + +fn({ + MuiSnackbar: { + defaultProps: { + ClickAwayListenerProps: CustomListenerProps, + ContentProps: CustomContentProps, + TransitionComponent: CustomTransition, + TransitionProps: CustomTransitionProps, + slots: { + root: 'div', + }, + }, + }, +}); + +fn({ + MuiSnackbar: { + defaultProps: { + ClickAwayListenerProps: CustomListenerProps, + ContentProps: CustomContentProps, + TransitionComponent: ComponentTransition, + TransitionProps: CustomTransitionProps, + slots: { + root: 'div', + transition: SlotTransition, + }, + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/theme.expected.js new file mode 100644 index 00000000000000..33be6b79aab87c --- /dev/null +++ b/packages/mui-codemod/src/deprecations/snackbar-props/test-cases/theme.expected.js @@ -0,0 +1,49 @@ +fn({ + MuiSnackbar: { + defaultProps: { + slots: { + transition: CustomTransition + }, + + slotProps: { + transition: CustomTransitionProps, + content: CustomContentProps, + clickAwayListener: CustomListenerProps + } + }, + }, +}); + +fn({ + MuiSnackbar: { + defaultProps: { + slots: { + root: 'div', + transition: CustomTransition + }, + + slotProps: { + transition: CustomTransitionProps, + content: CustomContentProps, + clickAwayListener: CustomListenerProps + } + }, + }, +}); + +fn({ + MuiSnackbar: { + defaultProps: { + slots: { + root: 'div', + transition: SlotTransition, + }, + + slotProps: { + transition: CustomTransitionProps, + content: CustomContentProps, + clickAwayListener: CustomListenerProps + } + }, + }, +}); diff --git a/packages/mui-material/src/Snackbar/Snackbar.d.ts b/packages/mui-material/src/Snackbar/Snackbar.d.ts index 4d0ac7fe4f1383..f65d6e8116539a 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.d.ts +++ b/packages/mui-material/src/Snackbar/Snackbar.d.ts @@ -6,6 +6,75 @@ import { InternalStandardProps as StandardProps } from '..'; import { SnackbarContentProps } from '../SnackbarContent'; import { TransitionProps } from '../transitions/transition'; import { SnackbarClasses } from './snackbarClasses'; +import { CreateSlotsAndSlotProps, SlotComponentProps, SlotProps } from '../utils/types'; + +export interface SnackbarSlots { + /** + * The component that renders the root slot. + * @default 'div' + */ + root: React.ElementType; + /** + * The component that renders the content slot. + * @default SnackbarContent + */ + content: React.ElementType; + /** + * The component that renders the clickAwayListener slot. + * @default ClickAwayListener + */ + clickAwayListener: React.ElementType; + /** + * The component that renders the transition. + * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. + * @default Grow + */ + transition: React.ElementType; +} + +export interface SnackbarRootSlotPropsOverrides {} +export interface SnackbarContentSlotPropsOverrides {} +export interface SnackbarClickAwayListenerSlotPropsOverrides {} + +export interface SnackbarTransitionSlotPropsOverrides {} + +export type SnackbarSlotsAndSlotProps = CreateSlotsAndSlotProps< + SnackbarSlots, + { + /** + * Props forwarded to the root slot. + * By default, the avaible props are based on the div element. + */ + root: SlotProps<'div', SnackbarRootSlotPropsOverrides, SnackbarOwnerState>; + /** + * Props forwarded to the content slot. + * By default, the avaible props are based on the [SnackbarContent](https://mui.com/material-ui/api/snackbar-content/#props) component. + */ + content: SlotProps< + React.ElementType>, + SnackbarContentSlotPropsOverrides, + SnackbarOwnerState + >; + /** + * Props forwarded to the clickAwayListener slot. + * By default, the avaible props are based on the [ClickAwayListener](https://mui.com/material-ui/api/click-away-listener/#props) component. + */ + clickAwayListener: SlotComponentProps< + React.ElementType>, + SnackbarClickAwayListenerSlotPropsOverrides, + SnackbarOwnerState + >; + /** + * Props applied to the transition element. + * By default, the element is based on this [`Transition`](https://reactcommunity.org/react-transition-group/transition/) component. + */ + transition: SlotComponentProps< + React.ElementType, + SnackbarTransitionSlotPropsOverrides, + SnackbarOwnerState + >; + } +>; export interface SnackbarOrigin { vertical: 'top' | 'bottom'; @@ -14,7 +83,9 @@ export interface SnackbarOrigin { export type SnackbarCloseReason = 'timeout' | 'clickaway' | 'escapeKeyDown'; -export interface SnackbarProps extends StandardProps> { +export interface SnackbarProps + extends Omit>, 'slots' | 'slotProps'>, + SnackbarSlotsAndSlotProps { /** * The action to display. It renders after the message, at the end of the snackbar. */ @@ -44,10 +115,12 @@ export interface SnackbarProps extends StandardProps; /** * Props applied to the `ClickAwayListener` element. + * @deprecated Use `slotProps.clickAwayListener` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ClickAwayListenerProps?: Partial; /** * Props applied to the [`SnackbarContent`](https://mui.com/material-ui/api/snackbar-content/) element. + * @deprecated Use `slotProps.content` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ContentProps?: Partial; /** @@ -95,6 +168,7 @@ export interface SnackbarProps extends StandardProps {} diff --git a/packages/mui-material/src/Snackbar/Snackbar.js b/packages/mui-material/src/Snackbar/Snackbar.js index 9756fe042078a6..0c1ce63da1b321 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.js +++ b/packages/mui-material/src/Snackbar/Snackbar.js @@ -2,7 +2,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; -import useSlotProps from '@mui/utils/useSlotProps'; import useSnackbar from './useSnackbar'; import ClickAwayListener from '../ClickAwayListener'; import { styled, useTheme } from '../zero-styled'; @@ -12,6 +11,7 @@ import capitalize from '../utils/capitalize'; import Grow from '../Grow'; import SnackbarContent from '../SnackbarContent'; import { getSnackbarUtilityClass } from './snackbarClasses'; +import useSlot from '../utils/useSlot'; const useUtilityClasses = (ownerState) => { const { classes, anchorOrigin } = ownerState; @@ -107,8 +107,8 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { autoHideDuration = null, children, className, - ClickAwayListenerProps, - ContentProps, + ClickAwayListenerProps: ClickAwayListenerPropsProp, + ContentProps: ContentPropsProp, disableWindowBlurListener = false, message, onBlur, @@ -118,9 +118,11 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { onMouseLeave, open, resumeHideDuration, - TransitionComponent = Grow, + slots = {}, + slotProps = {}, + TransitionComponent: TransitionComponentProp, transitionDuration = defaultTransitionDuration, - TransitionProps: { onEnter, onExited, ...TransitionProps } = {}, + TransitionProps: { onEnter, onExited, ...TransitionPropsProp } = {}, ...other } = props; @@ -129,7 +131,7 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { anchorOrigin: { vertical, horizontal }, autoHideDuration, disableWindowBlurListener, - TransitionComponent, + TransitionComponent: TransitionComponentProp, transitionDuration, }; @@ -139,17 +141,6 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const [exited, setExited] = React.useState(true); - const rootProps = useSlotProps({ - elementType: SnackbarRoot, - getSlotProps: getRootProps, - externalForwardedProps: other, - ownerState, - additionalProps: { - ref, - }, - className: [classes.root, className], - }); - const handleExited = (node) => { setExited(true); @@ -166,27 +157,89 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { } }; + const externalForwardedProps = { + slots: { + transition: TransitionComponentProp, + ...slots, + }, + slotProps: { + content: ContentPropsProp, + clickAwayListener: ClickAwayListenerPropsProp, + transition: TransitionPropsProp, + ...slotProps, + }, + }; + + const [Root, rootProps] = useSlot('root', { + ref, + className: [classes.root, className], + elementType: SnackbarRoot, + getSlotProps: getRootProps, + externalForwardedProps: { + ...externalForwardedProps, + ...other, + }, + ownerState, + }); + + const [ClickAwaySlot, clickAwayListenerProps] = useSlot('clickAwayListener', { + elementType: ClickAwayListener, + externalForwardedProps, + getSlotProps: (handlers) => ({ + onClickAway: (...params) => { + handlers.onClickAway?.(...params); + onClickAway(...params); + }, + }), + ownerState, + }); + + const [ContentSlot, contentSlotProps] = useSlot('content', { + elementType: SnackbarContent, + shouldForwardComponentProp: true, + externalForwardedProps, + additionalProps: { + message, + action, + }, + ownerState, + }); + + const [TransitionSlot, transitionProps] = useSlot('transition', { + elementType: Grow, + externalForwardedProps, + getSlotProps: (handlers) => ({ + onEnter: (...params) => { + handlers.onEnter?.(...params); + handleEnter(...params); + }, + onExited: (...params) => { + handlers.onExited?.(...params); + handleExited(...params); + }, + }), + additionalProps: { + appear: true, + in: open, + timeout: transitionDuration, + direction: vertical === 'top' ? 'down' : 'up', + }, + ownerState, + }); + // So we only render active snackbars. if (!open && exited) { return null; } return ( - - - - {children || } - - - + + + + {children || } + + + ); }); @@ -231,10 +284,12 @@ Snackbar.propTypes /* remove-proptypes */ = { className: PropTypes.string, /** * Props applied to the `ClickAwayListener` element. + * @deprecated Use `slotProps.clickAwayListener` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ClickAwayListenerProps: PropTypes.object, /** * Props applied to the [`SnackbarContent`](https://mui.com/material-ui/api/snackbar-content/) element. + * @deprecated Use `slotProps.content` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ ContentProps: PropTypes.object, /** @@ -291,6 +346,67 @@ Snackbar.propTypes /* remove-proptypes */ = { * we default to `autoHideDuration / 2` ms. */ resumeHideDuration: PropTypes.number, + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + clickAwayListener: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + PropTypes.shape({ + children: PropTypes.element.isRequired, + disableReactTree: PropTypes.bool, + mouseEvent: PropTypes.oneOf([ + 'onClick', + 'onMouseDown', + 'onMouseUp', + 'onPointerDown', + 'onPointerUp', + false, + ]), + onClickAway: PropTypes.func, + touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), + }), + PropTypes.shape({ + children: PropTypes.element.isRequired, + disableReactTree: PropTypes.bool, + key: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + '__@toStringTag@9059': PropTypes.oneOf(['BigInt']).isRequired, + toLocaleString: PropTypes.func.isRequired, + toString: PropTypes.func.isRequired, + valueOf: PropTypes.func.isRequired, + }), + PropTypes.string, + ]), + mouseEvent: PropTypes.oneOf([ + 'onClick', + 'onMouseDown', + 'onMouseUp', + 'onPointerDown', + 'onPointerUp', + false, + ]), + onClickAway: PropTypes.func, + touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false]), + }), + ]), + content: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + transition: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + clickAwayListener: PropTypes.elementType, + content: PropTypes.elementType, + root: PropTypes.elementType, + transition: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -302,6 +418,7 @@ Snackbar.propTypes /* remove-proptypes */ = { /** * The component used for the transition. * [Follow this guide](https://mui.com/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. + * @deprecated Use `slots.transition` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. * @default Grow */ TransitionComponent: PropTypes.elementType, @@ -324,6 +441,7 @@ Snackbar.propTypes /* remove-proptypes */ = { /** * Props applied to the transition element. * By default, the element is based on this [`Transition`](https://reactcommunity.org/react-transition-group/transition/) component. + * @deprecated Use `slotProps.transition` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. * @default {} */ TransitionProps: PropTypes.object, diff --git a/packages/mui-material/src/Snackbar/Snackbar.spec.tsx b/packages/mui-material/src/Snackbar/Snackbar.spec.tsx new file mode 100644 index 00000000000000..77b6c4006b23c4 --- /dev/null +++ b/packages/mui-material/src/Snackbar/Snackbar.spec.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import Snackbar from '@mui/material/Snackbar'; +import { expectType } from '@mui/types'; + +; + +(event); + }, + }, + transition: { + timeout: 1000, + onEnter(node, isAppearing) { + expectType(node); + expectType(isAppearing); + }, + }, + }} +/>; diff --git a/packages/mui-material/src/Snackbar/Snackbar.test.js b/packages/mui-material/src/Snackbar/Snackbar.test.js index 9f0e1e25b97da2..1a25aeb556e214 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.test.js +++ b/packages/mui-material/src/Snackbar/Snackbar.test.js @@ -1,8 +1,10 @@ import * as React from 'react'; +import clsx from 'clsx'; import { expect } from 'chai'; import { spy } from 'sinon'; import { act, createRenderer, fireEvent } from '@mui/internal-test-utils'; import Snackbar, { snackbarClasses as classes } from '@mui/material/Snackbar'; +import { snackbarContentClasses } from '@mui/material/SnackbarContent'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import describeConformance from '../../test/describeConformance'; @@ -24,6 +26,16 @@ describe('', () => { return result; } + function CustomContent({ ownerState, className, ...props }) { + return ( +
+ ); + } + describeConformance(, () => ({ classes, inheritComponent: 'div', @@ -31,6 +43,20 @@ describe('', () => { refInstanceof: window.HTMLDivElement, muiName: 'MuiSnackbar', skip: ['componentProp', 'componentsProp', 'themeVariants'], + slots: { + root: { + expectedClassName: classes.root, + }, + content: { + expectedClassName: snackbarContentClasses.root, + testWithComponent: CustomContent, + testWithElement: CustomContent, + }, + transition: { + testWithElement: null, + }, + // skip `clickAwayListener` because it does not have any element. + }, })); describe('prop: onClose', () => {