diff --git a/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md b/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md index 8baab6a5dec4e0..f62b3a53a63dd9 100644 --- a/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md +++ b/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md @@ -253,6 +253,24 @@ The Avatar's `imgProps` was deprecated in favor of `slotProps.img`: />; ``` +## Backdrop + +Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#backdrop-props) below to migrate the code as described in the following sections: + +```bash +npx @mui/codemod@next deprecations/backdrop-props +``` + +### TransitionComponent + +The Backdrop's `TransitionComponent` prop was deprecated in favor of `slots.transition`: + +```diff + | object, transition?: func
| object }" + }, "default": "{}" }, "slots": { - "type": { "name": "shape", "description": "{ root?: elementType }" }, + "type": { + "name": "shape", + "description": "{ root?: elementType, transition?: elementType }" + }, "default": "{}" }, "sx": { @@ -28,7 +34,12 @@ }, "additionalInfo": { "sx": true } }, - "TransitionComponent": { "type": { "name": "elementType" }, "default": "Fade" }, + "TransitionComponent": { + "type": { "name": "elementType" }, + "default": "Fade", + "deprecated": true, + "deprecationInfo": "Use slots.transition instead. This prop will be removed in v7. How to migrate." + }, "transitionDuration": { "type": { "name": "union", @@ -41,18 +52,26 @@ "import Backdrop from '@mui/material/Backdrop';", "import { Backdrop } from '@mui/material';" ], + "slots": [ + { + "name": "root", + "description": "The component that renders the root.", + "default": "'div'", + "class": "MuiBackdrop-root" + }, + { + "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": "Fade", + "class": null + } + ], "classes": [ { "key": "invisible", "className": "MuiBackdrop-invisible", "description": "Styles applied to the root element if `invisible={true}`.", "isGlobal": false - }, - { - "key": "root", - "className": "MuiBackdrop-root", - "description": "Styles applied to the root element.", - "isGlobal": false } ], "spread": true, diff --git a/docs/translations/api-docs/backdrop/backdrop.json b/docs/translations/api-docs/backdrop/backdrop.json index 3b9559dfff07b0..b439692f008532 100644 --- a/docs/translations/api-docs/backdrop/backdrop.json +++ b/docs/translations/api-docs/backdrop/backdrop.json @@ -16,12 +16,8 @@ "description": "If true, the backdrop is invisible. It can be used when rendering a popover or a custom select component." }, "open": { "description": "If true, the component is shown." }, - "slotProps": { - "description": "The extra props for the slot components. You can override the existing props or add new ones.
This prop is an alias for the componentsProps prop, which will be deprecated in the future." - }, - "slots": { - "description": "The components used for each slot inside.
This prop is an alias for the components prop, which will be deprecated in the future." - }, + "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." }, @@ -37,7 +33,10 @@ "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the root element", "conditions": "invisible={true}" - }, - "root": { "description": "Styles applied to the root element." } + } + }, + "slotDescriptions": { + "root": "The component that renders the root.", + "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 465fba0845dacf..62b01c7883c995 100644 --- a/packages/mui-codemod/README.md +++ b/packages/mui-codemod/README.md @@ -274,6 +274,19 @@ npx @mui/codemod@next deprecations/alert-props />; ``` +#### `backdrop-props` + +```diff + +``` + +```bash +npx @mui/codemod@next deprecations/backdrop-props +``` + #### `button-classes` JS transforms: @@ -876,7 +889,6 @@ CSS transforms: +.MuiPaginationItem-outlined.MuiPaginationItem-primary -.MuiPaginationItem-outlinedSecondary +.MuiPaginationItem-outlined.MuiPaginationItem-secondary - /> ``` ```bash @@ -929,6 +941,7 @@ npx @mui/codemod@next deprecations/slider-props ```bash npx @mui/codemod@latest deprecations/step-label-props + ``` ### v5.0.0 diff --git a/packages/mui-codemod/src/deprecations/all/deprecations-all.js b/packages/mui-codemod/src/deprecations/all/deprecations-all.js index 30af0e6d4a7aa8..c11ad4e965c2c2 100644 --- a/packages/mui-codemod/src/deprecations/all/deprecations-all.js +++ b/packages/mui-codemod/src/deprecations/all/deprecations-all.js @@ -8,6 +8,7 @@ import transformChipClasses from '../chip-classes'; import transformPaginationItemClasses from '../pagination-item-classes'; import transformAlertClasses from '../alert-classes'; import transformStepLabelProps from '../step-label-props'; +import transformBackdropProps from '../backdrop-props'; /** * @param {import('jscodeshift').FileInfo} file @@ -24,6 +25,8 @@ export default function deprecationsAll(file, api, options) { file.source = transformPaginationItemClasses(file, api, options); file.source = transformAlertClasses(file, api, options); file.source = transformStepLabelProps(file, api, options); + file.source = transformBackdropProps(file, api, options); + return file.source; } diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.js b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.js new file mode 100644 index 00000000000000..c444dfc97b1004 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.js @@ -0,0 +1,84 @@ +import findComponentJSX from '../../util/findComponentJSX'; +import assignObject from '../../util/assignObject'; +import appendAttribute from '../../util/appendAttribute'; + +/** + * @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; + + findComponentJSX(j, { root, componentName: 'Backdrop' }, (elementPath) => { + const index = elementPath.node.openingElement.attributes.findIndex( + (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'TransitionComponent', + ); + + if (index !== -1) { + const removed = elementPath.node.openingElement.attributes.splice(index, 1); + let hasNode = false; + elementPath.node.openingElement.attributes.forEach((attr) => { + if (attr.name?.name === 'slots') { + hasNode = true; + assignObject(j, { + target: attr, + key: 'transition', + expression: removed[0].value.expression, + }); + } + }); + + if (!hasNode) { + appendAttribute(j, { + target: elementPath.node, + attributeName: 'slots', + expression: j.objectExpression([ + j.objectProperty(j.identifier('transition'), removed[0].value.expression), + ]), + }); + } + } + }); + + root.find(j.ObjectProperty, { key: { name: 'TransitionComponent' } }).forEach((path) => { + if (path.parent?.parent?.parent?.parent?.node.key?.name === 'MuiBackdrop') { + const { properties: defaultPropsProperties } = path.parent.value; + + const existingSlots = defaultPropsProperties.find((prop) => prop.key.name === 'slots'); + const slots = existingSlots + ? existingSlots.value.properties.reduce((acc, prop) => { + return { ...acc, [prop.key.name]: prop.value }; + }, {}) + : {}; + + const transitionComponent = + defaultPropsProperties.find((prop) => prop.key.name === 'TransitionComponent') ?? {}; + + const updatedSlots = j.objectExpression( + Object.entries({ + transition: transitionComponent?.value, + ...slots, + }).map(([slot, value]) => { + return j.objectProperty(j.identifier(slot), value); + }), + ); + + if (existingSlots) { + existingSlots.value = updatedSlots; + path.prune(); + } else { + path.replace( + j.property( + 'init', + j.identifier('slots'), + j.objectExpression([j.objectProperty(j.identifier('transition'), path.node.value)]), + ), + ); + } + } + }); + + return root.toSource(printOptions); +} diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.test.js b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.test.js new file mode 100644 index 00000000000000..4d825b0076014a --- /dev/null +++ b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.test.js @@ -0,0 +1,53 @@ +import path from 'path'; +import { expect } from 'chai'; +import { jscodeshift } from '../../../testUtils'; +import transform from './backdrop-props'; +import readFile from '../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('@mui/codemod', () => { + describe('deprecations', () => { + describe('backdrop-props', () => { + it('transforms props as needed', () => { + const actual = transform({ source: read('./test-cases/actual.js') }, { jscodeshift }, {}); + + const expected = read('./test-cases/expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform({ source: read('./test-cases/expected.js') }, { jscodeshift }, {}); + + const expected = read('./test-cases/expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); + + describe('[theme] backdrop-props', () => { + it('transforms props as needed', () => { + const actual = transform( + { source: read('./test-cases/theme.actual.js') }, + { jscodeshift }, + { printOptions: { trailingComma: true } }, + ); + + const expected = read('./test-cases/theme.expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform( + { source: read('./test-cases/theme.expected.js') }, + { jscodeshift }, + {}, + ); + + const expected = read('./test-cases/theme.expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); + }); +}); diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/index.js b/packages/mui-codemod/src/deprecations/backdrop-props/index.js new file mode 100644 index 00000000000000..e73b413d4be88f --- /dev/null +++ b/packages/mui-codemod/src/deprecations/backdrop-props/index.js @@ -0,0 +1 @@ +export { default } from './backdrop-props'; diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/actual.js new file mode 100644 index 00000000000000..8cca730fda99e4 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/actual.js @@ -0,0 +1,25 @@ +import Backdrop from '@mui/material/Backdrop'; +import { Backdrop as MyBackdrop } from '@mui/material'; + +; +; +; +; +// should skip non MUI components +; diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/expected.js new file mode 100644 index 00000000000000..ca22506cedab88 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/expected.js @@ -0,0 +1,27 @@ +import Backdrop from '@mui/material/Backdrop'; +import { Backdrop as MyBackdrop } from '@mui/material'; + +; +; +; +; +// should skip non MUI components +; diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.actual.js new file mode 100644 index 00000000000000..a8e62bd3583647 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.actual.js @@ -0,0 +1,18 @@ +fn({ + MuiBackdrop: { + defaultProps: { + TransitionComponent: CustomTransition, + }, + }, +}); + +fn({ + MuiBackdrop: { + defaultProps: { + TransitionComponent: CustomTransition, + slots: { + root: 'div', + }, + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.expected.js new file mode 100644 index 00000000000000..ccc52bc558129d --- /dev/null +++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.expected.js @@ -0,0 +1,20 @@ +fn({ + MuiBackdrop: { + defaultProps: { + slots: { + transition: CustomTransition, + }, + }, + }, +}); + +fn({ + MuiBackdrop: { + defaultProps: { + slots: { + transition: CustomTransition, + root: 'div', + }, + }, + }, +}); diff --git a/packages/mui-material/src/Backdrop/Backdrop.d.ts b/packages/mui-material/src/Backdrop/Backdrop.d.ts index f383995b8a2cbc..41380fe5e6532b 100644 --- a/packages/mui-material/src/Backdrop/Backdrop.d.ts +++ b/packages/mui-material/src/Backdrop/Backdrop.d.ts @@ -5,10 +5,46 @@ import { TransitionProps } from '../transitions/transition'; import { Theme } from '../styles'; import { BackdropClasses } from './backdropClasses'; import { OverridableComponent, OverrideProps } from '../OverridableComponent'; +import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; +export interface BackdropSlots { + /** + * The component that renders the root. + * @default 'div' + */ + root?: 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 Fade + */ + transition?: React.JSXElementConstructor< + TransitionProps & { children: React.ReactElement } + >; +} export interface BackdropComponentsPropsOverrides {} -export interface BackdropOwnProps extends Partial> { +export interface BackdropTransitionSlotPropsOverrides {} + +export type BackdropSlotsAndSlotProps = CreateSlotsAndSlotProps< + BackdropSlots, + { + root: SlotProps< + React.ElementType, + BackdropComponentsPropsOverrides, + BackdropOwnerState + >; + transition: SlotProps< + React.JSXElementConstructor, + BackdropTransitionSlotPropsOverrides, + BackdropOwnerState + >; + } +>; + +export interface BackdropOwnProps + extends Partial>, + BackdropSlotsAndSlotProps { /** * The content of the component. */ @@ -50,27 +86,6 @@ export interface BackdropOwnProps extends Partial> { * If `true`, the component is shown. */ open: boolean; - /** - * The extra props for the slot components. - * You can override the existing props or add new ones. - * - * This prop is an alias for the `componentsProps` prop, which will be deprecated in the future. - * - * @default {} - */ - slotProps?: { - root?: React.HTMLAttributes & BackdropComponentsPropsOverrides; - }; - /** - * The components used for each slot inside. - * - * This prop is an alias for the `components` prop, which will be deprecated in the future. - * - * @default {} - */ - slots?: { - root?: React.ElementType; - }; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -84,6 +99,7 @@ export interface BackdropOwnProps extends Partial> { * The component used for the transition. * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. * @default Fade + * @deprecated Use `slots.transition` instead. This prop will be removed in v7. [How to migrate](/material-ui/migration/migrating-from-deprecated-apis/). */ TransitionComponent?: React.JSXElementConstructor< TransitionProps & { @@ -124,4 +140,6 @@ export type BackdropProps< component?: React.ElementType; }; +export interface BackdropOwnerState extends BackdropProps {} + export default Backdrop; diff --git a/packages/mui-material/src/Backdrop/Backdrop.js b/packages/mui-material/src/Backdrop/Backdrop.js index 1221de8bc3db64..323710cb48baba 100644 --- a/packages/mui-material/src/Backdrop/Backdrop.js +++ b/packages/mui-material/src/Backdrop/Backdrop.js @@ -5,6 +5,7 @@ import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import Fade from '../Fade'; import { getBackdropUtilityClass } from './backdropClasses'; @@ -48,13 +49,13 @@ const Backdrop = React.forwardRef(function Backdrop(inProps, ref) { children, className, component = 'div', - components = {}, - componentsProps = {}, invisible = false, open, + components = {}, + componentsProps = {}, slotProps = {}, slots = {}, - TransitionComponent = Fade, + TransitionComponent: TransitionComponentProp, transitionDuration, ...other } = props; @@ -67,22 +68,38 @@ const Backdrop = React.forwardRef(function Backdrop(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootSlotProps = slotProps.root ?? componentsProps.root; + const backwardCompatibleSlots = { + transition: TransitionComponentProp, + root: components.Root, + ...slots, + }; + const backwardCompatibleSlotProps = { ...componentsProps, ...slotProps }; + const externalForwardedProps = { + slots: backwardCompatibleSlots, + slotProps: backwardCompatibleSlotProps, + }; + + const [RootSlot, rootProps] = useSlot('root', { + elementType: BackdropRoot, + externalForwardedProps, + className: clsx(classes.root, className), + ownerState, + }); + + const [TransitionSlot, transitionProps] = useSlot('transition', { + elementType: Fade, + externalForwardedProps, + ownerState, + }); + + delete transitionProps.ownerState; return ( - - + + {children} - - + + ); }); @@ -142,25 +159,20 @@ Backdrop.propTypes /* remove-proptypes */ = { */ open: PropTypes.bool.isRequired, /** - * The extra props for the slot components. - * You can override the existing props or add new ones. - * - * This prop is an alias for the `componentsProps` prop, which will be deprecated in the future. - * + * The props used for each slot inside. * @default {} */ slotProps: PropTypes.shape({ - root: PropTypes.object, + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + transition: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), /** * The components used for each slot inside. - * - * This prop is an alias for the `components` prop, which will be deprecated in the future. - * * @default {} */ slots: PropTypes.shape({ root: PropTypes.elementType, + transition: PropTypes.elementType, }), /** * The system prop that allows defining system overrides as well as additional CSS styles. @@ -174,6 +186,7 @@ Backdrop.propTypes /* remove-proptypes */ = { * The component used for the transition. * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. * @default Fade + * @deprecated Use `slots.transition` instead. This prop will be removed in v7. [How to migrate](/material-ui/migration/migrating-from-deprecated-apis/). */ TransitionComponent: PropTypes.elementType, /** diff --git a/packages/mui-material/src/Backdrop/Backdrop.test.js b/packages/mui-material/src/Backdrop/Backdrop.test.js index 08e5d122eed6bc..a5bb505cb0f071 100644 --- a/packages/mui-material/src/Backdrop/Backdrop.test.js +++ b/packages/mui-material/src/Backdrop/Backdrop.test.js @@ -16,18 +16,19 @@ describe('', () => { refInstanceof: window.HTMLDivElement, muiName: 'MuiBackdrop', testVariantProps: { invisible: true }, - testLegacyComponentsProp: true, slots: { root: { expectedClassName: classes.root, }, + transition: { + testWithElement: null, + }, }, skip: [ 'componentProp', 'componentsProp', // react-transition-group issue 'reactTestRenderer', - 'slotPropsCallback', // not supported yet ], }));