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 b900a71578c7be..d123f95fe24d19 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 @@ -1134,6 +1134,46 @@ The Divider's `light` prop was deprecated, Use `sx={{ opacity : "0.6" }}` (or an /> ``` +## Dialog + +Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#dialog-classes) below to migrate the code as described in the following sections: + +```bash +npx @mui/codemod@latest deprecations/dialog-classes +``` + +### Composed CSS classes + +The CSS classes composing the `scroll` prop values have been removed. + +Here's how to migrate: + +```diff +-.MuiDialog-root .MuiDialog-paperScrollBody ++.MuiDialog-root .MuiDialog-scrollBody > .MuiDialog-paper +-.MuiDialog-root .MuiDialog-paperScrollPaper ++.MuiDialog-root .MuiDialog-scrollPaper > .MuiDialog-paper +``` + +```diff + import { dialogClasses } from '@mui/material/Dialog'; + + MuiDialog: { + styleOverrides: { + root: { +- [`& .${dialogClasses.paperScrollBody}`]: { ++ [`& .${dialogClasses.scrollBody} > .${dialogClasses.paper}`]: { + color: 'red', + }, +- [`& .${dialogClasses.paperScrollPaper}`]: { ++ [`& .${dialogClasses.scrollPaper} > .${dialogClasses.paper}`]: { + color: 'red', + }, + }, + }, + }, +``` + ## Drawer Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#drawer-props) below to migrate the code as described in the following sections: diff --git a/docs/pages/material-ui/api/dialog.json b/docs/pages/material-ui/api/dialog.json index a8862cc58f046e..6960358af6171f 100644 --- a/docs/pages/material-ui/api/dialog.json +++ b/docs/pages/material-ui/api/dialog.json @@ -137,13 +137,15 @@ "key": "paperScrollBody", "className": "MuiDialog-paperScrollBody", "description": "Styles applied to the Paper component if `scroll=\"body\"`.", - "isGlobal": false + "isGlobal": false, + "isDeprecated": true }, { "key": "paperScrollPaper", "className": "MuiDialog-paperScrollPaper", "description": "Styles applied to the Paper component if `scroll=\"paper\"`.", - "isGlobal": false + "isGlobal": false, + "isDeprecated": true }, { "key": "paperWidthFalse", diff --git a/docs/translations/api-docs/dialog/dialog.json b/docs/translations/api-docs/dialog/dialog.json index 08870c24e1eb60..ad85e5bee1bd34 100644 --- a/docs/translations/api-docs/dialog/dialog.json +++ b/docs/translations/api-docs/dialog/dialog.json @@ -63,12 +63,14 @@ "paperScrollBody": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the Paper component", - "conditions": "scroll=\"body\"" + "conditions": "scroll=\"body\"", + "deprecationInfo": "Combine the .MuiDialog-paper and .MuiDialog-scrollBody classes instead. See Migrating from deprecated APIs for more details." }, "paperScrollPaper": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the Paper component", - "conditions": "scroll=\"paper\"" + "conditions": "scroll=\"paper\"", + "deprecationInfo": "Combine the .MuiDialog-paper and .MuiDialog-scrollPaper classes instead. See Migrating from deprecated APIs for more details." }, "paperWidthFalse": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", diff --git a/packages/mui-codemod/README.md b/packages/mui-codemod/README.md index da33b465f61abd..cb68ee4844194d 100644 --- a/packages/mui-codemod/README.md +++ b/packages/mui-codemod/README.md @@ -1033,6 +1033,42 @@ npx @mui/codemod@latest deprecations/circular-progress-classes npx @mui/codemod@latest deprecations/divider-props ``` +#### `dialog-classes` + +JS transforms: + +```diff + import { dialogClasses } from '@mui/material/Dialog'; + + MuiDialog: { + styleOverrides: { + root: { +- [`& .${dialogClasses.paperScrollBody}`]: { ++ [`& .${dialogClasses.scrollBody} > .${dialogClasses.paper}`]: { + color: 'red', + }, +- [`& .${dialogClasses.paperScrollPaper}`]: { ++ [`& .${dialogClasses.scrollPaper} > .${dialogClasses.paper}`]: { + color: 'red', + }, + }, + }, + }, +``` + +CSS transforms: + +```diff +-.MuiDialog-root .MuiDialog-paperScrollBody ++.MuiDialog-root .MuiDialog-scrollBody > .MuiDialog-paper +-.MuiDialog-root .MuiDialog-paperScrollPaper ++.MuiDialog-root .MuiDialog-scrollPaper > .MuiDialog-paper +``` + +```bash +npx @mui/codemod@latest deprecations/dialog-classes +``` + #### `drawer-classes` JS transforms: diff --git a/packages/mui-codemod/src/deprecations/all/deprecations-all.js b/packages/mui-codemod/src/deprecations/all/deprecations-all.js index 0b6da34492c577..9e91180dd6e941 100644 --- a/packages/mui-codemod/src/deprecations/all/deprecations-all.js +++ b/packages/mui-codemod/src/deprecations/all/deprecations-all.js @@ -11,6 +11,7 @@ import transformChipClasses from '../chip-classes'; import transformCircularProgressClasses from '../circular-progress-classes'; import transformDividerProps from '../divider-props'; import transformDrawerClasses from '../drawer-classes'; +import transformDialogClasses from '../dialog-classes'; import transformFilledInputProps from '../filled-input-props'; import transformFormControlLabelProps from '../form-control-label-props'; import transformImageListItemBarClasses from '../image-list-item-bar-classes'; @@ -62,6 +63,7 @@ export default function deprecationsAll(file, api, options) { file.source = transformCircularProgressClasses(file, api, options); file.source = transformDividerProps(file, api, options); file.source = transformDrawerClasses(file, api, options); + file.source = transformDialogClasses(file, api, options); file.source = transformFilledInputProps(file, api, options); file.source = transformFormControlLabelProps(file, api, options); file.source = transformImageListItemBarClasses(file, api, options); diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/dialog-classes.js b/packages/mui-codemod/src/deprecations/dialog-classes/dialog-classes.js new file mode 100644 index 00000000000000..88e126eeb53b66 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/dialog-classes.js @@ -0,0 +1,117 @@ +import { classes } from './postcss-plugin'; + +/** + * @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; + classes.forEach(({ deprecatedClass, replacementSelector }) => { + const replacementSelectorPrefix = '&'; + root + .find(j.ImportDeclaration) + .filter((path) => path.node.source.value.match(/^@mui\/material\/Dialog$/)) + .forEach((path) => { + path.node.specifiers.forEach((specifier) => { + if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'dialogClasses') { + const deprecatedAtomicClass = deprecatedClass.replace( + `${deprecatedClass.split('-')[0]}-`, + '', + ); + root + .find(j.MemberExpression, { + object: { name: specifier.local.name }, + property: { name: deprecatedAtomicClass }, + }) + .forEach((memberExpression) => { + const parent = memberExpression.parentPath.parentPath.value; + if (parent.type === j.TemplateLiteral.name) { + const memberExpressionIndex = parent.expressions.findIndex( + (expression) => expression === memberExpression.value, + ); + const precedingTemplateElement = parent.quasis[memberExpressionIndex]; + const atomicClasses = replacementSelector + .replaceAll('MuiDialog-', '') + .replaceAll(replacementSelectorPrefix, '') + .replaceAll(' > ', '') + .replaceAll(' ', '') + .split('.') + .filter(Boolean); + + if ( + precedingTemplateElement.value.raw.endsWith( + deprecatedClass.startsWith(' ') + ? `${replacementSelectorPrefix} .` + : `${replacementSelectorPrefix}.`, + ) + ) { + parent.expressions.splice( + memberExpressionIndex, + 1, + j.memberExpression( + memberExpression.value.object, + j.identifier(atomicClasses[0]), + ), + + j.memberExpression( + memberExpression.value.object, + j.identifier(atomicClasses[1]), + ), + ); + + if (replacementSelector.includes(' > ')) { + parent.quasis.splice( + memberExpressionIndex, + 1, + j.templateElement( + { + raw: precedingTemplateElement.value.raw, + cooked: precedingTemplateElement.value.cooked, + }, + false, + ), + j.templateElement({ raw: ' > .', cooked: ' > .' }, false), + ); + } else { + parent.quasis.splice( + memberExpressionIndex, + 1, + j.templateElement( + { + raw: precedingTemplateElement.value.raw, + cooked: precedingTemplateElement.value.cooked, + }, + false, + ), + + j.templateElement({ raw: '.', cooked: '.' }, false), + ); + } + } + } + }); + } + }); + }); + + const selectorRegex = new RegExp(`^${replacementSelectorPrefix}${deprecatedClass}`); + root + .find( + j.Literal, + (literal) => typeof literal.value === 'string' && literal.value.match(selectorRegex), + ) + .forEach((path) => { + path.replace( + j.literal( + path.value.value.replace( + selectorRegex, + `${replacementSelectorPrefix}${deprecatedClass.startsWith(' ') ? ' ' : ''}${replacementSelector.trim()}`, + ), + ), + ); + }); + }); + return root.toSource(printOptions); +} diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/dialog-classes.test.js b/packages/mui-codemod/src/deprecations/dialog-classes/dialog-classes.test.js new file mode 100644 index 00000000000000..023e4f741bc19e --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/dialog-classes.test.js @@ -0,0 +1,78 @@ +import path from 'path'; +import { expect } from 'chai'; +import postcss from 'postcss'; +import { jscodeshift } from '../../../testUtils'; +import jsTransform from './dialog-classes'; +import { plugin as postcssPlugin } from './postcss-plugin'; +import readFile from '../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +const postcssProcessor = postcss([postcssPlugin]); + +describe('@mui/codemod', () => { + describe('deprecations', () => { + describe('dialog-classes', () => { + describe('js-transform', () => { + it('transforms props as needed', () => { + const actual = jsTransform( + { source: read('./test-cases/actual.js') }, + { jscodeshift }, + { printOptions: { quote: 'double', trailingComma: true } }, + ); + + const expected = read('./test-cases/expected.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = jsTransform( + { 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('css-transform', () => { + it('transforms classes as needed', async () => { + const actual = await postcssProcessor.process(read('./test-cases/actual.css'), { + from: undefined, + }); + + const expected = read('./test-cases/expected.css'); + expect(actual.css).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', async () => { + const actual = await postcssProcessor.process(read('./test-cases/expected.css'), { + from: undefined, + }); + + const expected = read('./test-cases/expected.css'); + expect(actual.css).to.equal(expected, 'The transformed version should be correct'); + }); + }); + + describe('test-cases', () => { + it('should not be the same', () => { + const actualJS = read('./test-cases/actual.js'); + const expectedJS = read('./test-cases/expected.js'); + expect(actualJS).not.to.equal(expectedJS, 'The actual and expected should be different'); + + const actualCSS = read('./test-cases/actual.css'); + const expectedCSS = read('./test-cases/expected.css'); + expect(actualCSS).not.to.equal( + expectedCSS, + 'The actual and expected should be different', + ); + }); + }); + }); + }); +}); diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/index.js b/packages/mui-codemod/src/deprecations/dialog-classes/index.js new file mode 100644 index 00000000000000..52dff3cd5409d3 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/index.js @@ -0,0 +1 @@ +export { default } from './dialog-classes'; diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/postcss-plugin.js b/packages/mui-codemod/src/deprecations/dialog-classes/postcss-plugin.js new file mode 100644 index 00000000000000..c6bc4e293a8dd7 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/postcss-plugin.js @@ -0,0 +1,33 @@ +const classes = [ + { + deprecatedClass: ' .MuiDialog-paperScrollBody', + replacementSelector: ' .MuiDialog-scrollBody > .MuiDialog-paper', + }, + { + deprecatedClass: ' .MuiDialog-paperScrollPaper', + replacementSelector: ' .MuiDialog-scrollPaper > .MuiDialog-paper', + }, +]; + +const plugin = () => { + return { + postcssPlugin: `Replace deprecated Dialog classes with new classes`, + Rule(rule) { + const { selector } = rule; + + classes.forEach(({ deprecatedClass, replacementSelector }) => { + const selectorRegex = new RegExp(`${deprecatedClass}`); + + if (selector.match(selectorRegex)) { + rule.selector = selector.replace(selectorRegex, replacementSelector); + } + }); + }, + }; +}; +plugin.postcss = true; + +module.exports = { + plugin, + classes, +}; diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/postcss.config.js b/packages/mui-codemod/src/deprecations/dialog-classes/postcss.config.js new file mode 100644 index 00000000000000..23bebc1125be6e --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/postcss.config.js @@ -0,0 +1,5 @@ +const { plugin } = require('./postcss-plugin'); + +module.exports = { + plugins: [plugin], +}; diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/actual.css b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/actual.css new file mode 100644 index 00000000000000..582232dbf4afb2 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/actual.css @@ -0,0 +1,7 @@ +.MuiDialog-root .MuiDialog-paperScrollBody { + color: red; +} + +.MuiDialog-root .MuiDialog-paperScrollPaper { + color: red; +} diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/actual.js b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/actual.js new file mode 100644 index 00000000000000..2f33aa35e4e0c6 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/actual.js @@ -0,0 +1,7 @@ +import { dialogClasses } from '@mui/material/Dialog'; + +('& .MuiDialog-paperScrollBody'); +('& .MuiDialog-paperScrollPaper'); + +`& .${dialogClasses.paperScrollBody}`; +`& .${dialogClasses.paperScrollPaper}`; diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/expected.css b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/expected.css new file mode 100644 index 00000000000000..71684ed2968e8d --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/expected.css @@ -0,0 +1,7 @@ +.MuiDialog-root .MuiDialog-scrollBody > .MuiDialog-paper { + color: red; +} + +.MuiDialog-root .MuiDialog-scrollPaper > .MuiDialog-paper { + color: red; +} diff --git a/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/expected.js b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/expected.js new file mode 100644 index 00000000000000..f8bfe6af1f5830 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/dialog-classes/test-cases/expected.js @@ -0,0 +1,7 @@ +import { dialogClasses } from '@mui/material/Dialog'; + +("& .MuiDialog-scrollBody > .MuiDialog-paper"); +("& .MuiDialog-scrollPaper > .MuiDialog-paper"); + +`& .${dialogClasses.scrollBody} > .${dialogClasses.paper}`; +`& .${dialogClasses.scrollPaper} > .${dialogClasses.paper}`; diff --git a/packages/mui-material/src/Dialog/Dialog.test.js b/packages/mui-material/src/Dialog/Dialog.test.js index 95a2a0f3631d0d..f04aee38ff943c 100644 --- a/packages/mui-material/src/Dialog/Dialog.test.js +++ b/packages/mui-material/src/Dialog/Dialog.test.js @@ -81,6 +81,30 @@ describe('', () => { }), ); + it('should work correctly when using css selectors for scroll="body"', () => { + render( + + foo + , + ); + + expect(document.querySelector(`.${classes.scrollBody} > .${classes.paper}`)).to.not.equal(null); + expect(classes.paperScrollBody).to.not.equal(null); + }); + + it('should work correctly when using css selectors for scroll="paper"', () => { + render( + + foo + , + ); + + expect(document.querySelector(`.${classes.scrollPaper} > .${classes.paper}`)).to.not.equal( + null, + ); + expect(classes.paperScrollPaper).to.not.equal(null); + }); + it('should render with a TransitionComponent', () => { const Transition = React.forwardRef(() =>
); const { getAllByTestId } = render( diff --git a/packages/mui-material/src/Dialog/dialogClasses.ts b/packages/mui-material/src/Dialog/dialogClasses.ts index 888795d7ddefe9..b8016c093a3c82 100644 --- a/packages/mui-material/src/Dialog/dialogClasses.ts +++ b/packages/mui-material/src/Dialog/dialogClasses.ts @@ -12,9 +12,13 @@ export interface DialogClasses { container: string; /** Styles applied to the Paper component. */ paper: string; - /** Styles applied to the Paper component if `scroll="paper"`. */ + /** Styles applied to the Paper component if `scroll="paper"`. + * @deprecated Combine the [.MuiDialog-paper](/material-ui/api/dialog/#dialog-classes-paper) and [.MuiDialog-scrollPaper](/material-ui/api/dialog/#dialog-classes-scrollPaper) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. + */ paperScrollPaper: string; - /** Styles applied to the Paper component if `scroll="body"`. */ + /** Styles applied to the Paper component if `scroll="body"`. + * @deprecated Combine the [.MuiDialog-paper](/material-ui/api/dialog/#dialog-classes-paper) and [.MuiDialog-scrollBody](/material-ui/api/dialog/#dialog-classes-scrollBody) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. + */ paperScrollBody: string; /** Styles applied to the Paper component if `maxWidth=false`. */ paperWidthFalse: string;