diff --git a/docs/data/material/getting-started/templates/blog/theme/customizations/menus.js b/docs/data/material/getting-started/templates/blog/theme/customizations/menus.js index 167af530c54596..ffd022a1ae476c 100644 --- a/docs/data/material/getting-started/templates/blog/theme/customizations/menus.js +++ b/docs/data/material/getting-started/templates/blog/theme/customizations/menus.js @@ -4,7 +4,7 @@ import { svgIconClasses } from '@mui/material/SvgIcon'; import { typographyClasses } from '@mui/material/Typography'; import { buttonBaseClasses } from '@mui/material/ButtonBase'; import { dividerClasses } from '@mui/material/Divider'; -import { listItemClasses } from '@mui/material/ListItem'; +import { listItemButtonClasses } from '@mui/material/ListItemButton'; import { menuItemClasses } from '@mui/material/MenuItem'; import { selectClasses } from '@mui/material/Select'; import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded'; @@ -39,20 +39,20 @@ export const menuComponentsCustomizations = { padding: '2px 8px', borderRadius: theme.shape.borderRadius, opacity: 0.7, - [`&.${listItemClasses.selected}`]: { + [`&.${listItemButtonClasses.selected}`]: { opacity: 1, backgroundColor: alpha(theme.palette.action.selected, 0.3), [`& .${svgIconClasses.root}`]: { color: theme.palette.text.primary, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: alpha(theme.palette.action.selected, 0.3), }, '&:hover': { backgroundColor: alpha(theme.palette.action.selected, 0.5), }, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: 'transparent', }, }, diff --git a/docs/data/material/getting-started/templates/blog/theme/customizations/menus.tsx b/docs/data/material/getting-started/templates/blog/theme/customizations/menus.tsx index 7125260edd8583..391c849ff6e644 100644 --- a/docs/data/material/getting-started/templates/blog/theme/customizations/menus.tsx +++ b/docs/data/material/getting-started/templates/blog/theme/customizations/menus.tsx @@ -4,7 +4,7 @@ import { svgIconClasses, SvgIconProps } from '@mui/material/SvgIcon'; import { typographyClasses } from '@mui/material/Typography'; import { buttonBaseClasses } from '@mui/material/ButtonBase'; import { dividerClasses } from '@mui/material/Divider'; -import { listItemClasses } from '@mui/material/ListItem'; +import { listItemButtonClasses } from '@mui/material/ListItemButton'; import { menuItemClasses } from '@mui/material/MenuItem'; import { selectClasses } from '@mui/material/Select'; import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded'; @@ -39,20 +39,20 @@ export const menuComponentsCustomizations: Components = { padding: '2px 8px', borderRadius: theme.shape.borderRadius, opacity: 0.7, - [`&.${listItemClasses.selected}`]: { + [`&.${listItemButtonClasses.selected}`]: { opacity: 1, backgroundColor: alpha(theme.palette.action.selected, 0.3), [`& .${svgIconClasses.root}`]: { color: theme.palette.text.primary, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: alpha(theme.palette.action.selected, 0.3), }, '&:hover': { backgroundColor: alpha(theme.palette.action.selected, 0.5), }, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: 'transparent', }, }, diff --git a/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.js b/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.js index d7c0429319889b..1c8394cfadb7f1 100644 --- a/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.js +++ b/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.js @@ -4,7 +4,7 @@ import { svgIconClasses } from '@mui/material/SvgIcon'; import { typographyClasses } from '@mui/material/Typography'; import { buttonBaseClasses } from '@mui/material/ButtonBase'; import { dividerClasses } from '@mui/material/Divider'; -import { listItemClasses } from '@mui/material/ListItem'; +import { listItemButtonClasses } from '@mui/material/ListItemButton'; import { menuItemClasses } from '@mui/material/MenuItem'; import { selectClasses } from '@mui/material/Select'; import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded'; @@ -39,20 +39,20 @@ export const menuComponentsCustomizations = { padding: '2px 8px', borderRadius: theme.shape.borderRadius, opacity: 0.7, - [`&.${listItemClasses.selected}`]: { + [`&.${listItemButtonClasses.selected}`]: { opacity: 1, backgroundColor: alpha(theme.palette.action.selected, 0.3), [`& .${svgIconClasses.root}`]: { color: theme.palette.text.primary, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: alpha(theme.palette.action.selected, 0.3), }, '&:hover': { backgroundColor: alpha(theme.palette.action.selected, 0.5), }, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: 'transparent', }, }, diff --git a/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.tsx b/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.tsx index 51fd86fbf98247..4d2e1d8a15aad6 100644 --- a/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.tsx +++ b/docs/data/material/getting-started/templates/dashboard/theme/customizations/menus.tsx @@ -4,7 +4,7 @@ import { SvgIconProps, svgIconClasses } from '@mui/material/SvgIcon'; import { typographyClasses } from '@mui/material/Typography'; import { buttonBaseClasses } from '@mui/material/ButtonBase'; import { dividerClasses } from '@mui/material/Divider'; -import { listItemClasses } from '@mui/material/ListItem'; +import { listItemButtonClasses } from '@mui/material/ListItemButton'; import { menuItemClasses } from '@mui/material/MenuItem'; import { selectClasses } from '@mui/material/Select'; import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded'; @@ -39,20 +39,20 @@ export const menuComponentsCustomizations: Components = { padding: '2px 8px', borderRadius: theme.shape.borderRadius, opacity: 0.7, - [`&.${listItemClasses.selected}`]: { + [`&.${listItemButtonClasses.selected}`]: { opacity: 1, backgroundColor: alpha(theme.palette.action.selected, 0.3), [`& .${svgIconClasses.root}`]: { color: theme.palette.text.primary, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: alpha(theme.palette.action.selected, 0.3), }, '&:hover': { backgroundColor: alpha(theme.palette.action.selected, 0.5), }, }, - [`&.${listItemClasses.focusVisible}`]: { + [`&.${listItemButtonClasses.focusVisible}`]: { backgroundColor: 'transparent', }, }, diff --git a/docs/data/material/migration/migrating-to-v6/migrating-to-v6.md b/docs/data/material/migration/migrating-to-v6/migrating-to-v6.md index d66a2bcb5c8867..e297a98a04ed3a 100644 --- a/docs/data/material/migration/migrating-to-v6/migrating-to-v6.md +++ b/docs/data/material/migration/migrating-to-v6/migrating-to-v6.md @@ -199,6 +199,40 @@ The `Grid2` was updated and stabilized: This brings some breaking changes described in the following sections. +### ListItem + +`ListItem`'s props `autoFocus`, `button`, `disabled`, and `selected`, deprecated in v5, have been removed. To replace the `button` prop, use `ListItemButton` instead. The other removed props are available in the `ListItemButton` component as well. + +```diff +- ++ +``` + +Use this codemod to migrate your project to the `ListItemButton` component: + +```bash +npx @mui/codemod@next v6.0.0/list-item-button-prop +``` + +As the `ListItem` no longer supports these props, the class names related to these props were removed. You should use the `listItemButtonClasses` object instead. + +```diff +-import { listItemClasses } from '@mui/material/ListItem'; ++import { listItemButtonClasses } from '@mui/material/ListItemButton'; + +- listItemClasses.button ++ listItemButtonClasses.root + +- listItemClasses.focusVisible ++ listItemButtonClasses.focusVisible + +- listItemClasses.disabled ++ listItemButtonClasses.disabled + +- listItemClasses.selected ++ listItemButtonClasses.selected +``` + #### Stabilized API The `Grid2` component API was stabilized, so its import no longer contains the `Unstable_` prefix: diff --git a/docs/pages/material-ui/api/list-item.json b/docs/pages/material-ui/api/list-item.json index f9f795a1e09b04..bc039787a53e9c 100644 --- a/docs/pages/material-ui/api/list-item.json +++ b/docs/pages/material-ui/api/list-item.json @@ -4,18 +4,6 @@ "type": { "name": "enum", "description": "'center'
| 'flex-start'" }, "default": "'center'" }, - "autoFocus": { - "type": { "name": "bool" }, - "default": "false", - "deprecated": true, - "deprecationInfo": "checkout ListItemButton instead" - }, - "button": { - "type": { "name": "bool" }, - "default": "false", - "deprecated": true, - "deprecationInfo": "checkout ListItemButton instead" - }, "children": { "type": { "name": "custom", "description": "node" } }, "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, "component": { "type": { "name": "elementType" } }, @@ -44,22 +32,10 @@ "deprecationInfo": "Use the slotProps.root prop instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." }, "dense": { "type": { "name": "bool" }, "default": "false" }, - "disabled": { - "type": { "name": "bool" }, - "default": "false", - "deprecated": true, - "deprecationInfo": "checkout ListItemButton instead" - }, "disableGutters": { "type": { "name": "bool" }, "default": "false" }, "disablePadding": { "type": { "name": "bool" }, "default": "false" }, "divider": { "type": { "name": "bool" }, "default": "false" }, "secondaryAction": { "type": { "name": "node" } }, - "selected": { - "type": { "name": "bool" }, - "default": "false", - "deprecated": true, - "deprecationInfo": "checkout ListItemButton instead" - }, "slotProps": { "type": { "name": "shape", "description": "{ root?: object }" }, "default": "{}" @@ -88,12 +64,6 @@ "description": "Styles applied to the component element if `alignItems=\"flex-start\"`.", "isGlobal": false }, - { - "key": "button", - "className": "MuiListItem-button", - "description": "Styles applied to the inner `component` element if `button={true}`.", - "isGlobal": false - }, { "key": "container", "className": "MuiListItem-container", @@ -106,24 +76,12 @@ "description": "Styles applied to the component element if dense.", "isGlobal": false }, - { - "key": "disabled", - "className": "Mui-disabled", - "description": "State class applied to the inner `component` element if `disabled={true}`.", - "isGlobal": true - }, { "key": "divider", "className": "MuiListItem-divider", "description": "Styles applied to the inner `component` element if `divider={true}`.", "isGlobal": false }, - { - "key": "focusVisible", - "className": "Mui-focusVisible", - "description": "State class applied to the `component`'s `focusVisibleClassName` prop if `button={true}`.", - "isGlobal": true - }, { "key": "gutters", "className": "MuiListItem-gutters", @@ -147,12 +105,6 @@ "className": "MuiListItem-secondaryAction", "description": "Styles applied to the component element if `children` includes `ListItemSecondaryAction`.", "isGlobal": false - }, - { - "key": "selected", - "className": "Mui-selected", - "description": "State class applied to the root element if `selected={true}`.", - "isGlobal": true } ], "spread": true, diff --git a/docs/translations/api-docs/list-item/list-item.json b/docs/translations/api-docs/list-item/list-item.json index 802cdbb8fca4b9..d7e58e5251f11d 100644 --- a/docs/translations/api-docs/list-item/list-item.json +++ b/docs/translations/api-docs/list-item/list-item.json @@ -2,12 +2,6 @@ "componentDescription": "Uses an additional container component if `ListItemSecondaryAction` is the last child.", "propDescriptions": { "alignItems": { "description": "Defines the align-items style property." }, - "autoFocus": { - "description": "If true, the list item is focused during the first mount. Focus will also be triggered if the value changes from false to true." - }, - "button": { - "description": "If true, the list item is a button (using ButtonBase). Props intended for ButtonBase can then be applied to ListItem." - }, "children": { "description": "The content of the component if a ListItemSecondaryAction is used it must be the last child." }, @@ -27,7 +21,6 @@ "dense": { "description": "If true, compact vertical padding designed for keyboard and mouse input is used. The prop defaults to the value inherited from the parent List component." }, - "disabled": { "description": "If true, the component is disabled." }, "disableGutters": { "description": "If true, the left and right padding is removed." }, @@ -36,7 +29,6 @@ "description": "If true, a 1px light border is added to the bottom of the list item." }, "secondaryAction": { "description": "The element to display at the end of ListItem." }, - "selected": { "description": "Use to apply selected styling." }, "slotProps": { "description": "The extra props for the slot components. You can override the existing props or add new ones." }, @@ -51,11 +43,6 @@ "nodeName": "the component element", "conditions": "alignItems=\"flex-start\"" }, - "button": { - "description": "Styles applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the inner component element", - "conditions": "button={true}" - }, "container": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the container element", @@ -66,21 +53,11 @@ "nodeName": "the component element", "conditions": "dense" }, - "disabled": { - "description": "State class applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the inner component element", - "conditions": "disabled={true}" - }, "divider": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the inner component element", "conditions": "divider={true}" }, - "focusVisible": { - "description": "State class applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the component's focusVisibleClassName prop", - "conditions": "button={true}" - }, "gutters": { "description": "Styles applied to {{nodeName}} unless {{conditions}}.", "nodeName": "the inner component element", @@ -99,11 +76,6 @@ "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the component element", "conditions": "children includes ListItemSecondaryAction" - }, - "selected": { - "description": "State class applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the root element", - "conditions": "selected={true}" } } } diff --git a/packages/mui-codemod/src/v6.0.0/all/v6-all.js b/packages/mui-codemod/src/v6.0.0/all/v6-all.js index 77c8e343a4b08a..416fc9cbea8793 100644 --- a/packages/mui-codemod/src/v6.0.0/all/v6-all.js +++ b/packages/mui-codemod/src/v6.0.0/all/v6-all.js @@ -1,11 +1,10 @@ +import transformerListItemButtonProps from '../list-item-button-prop/list-item-button-prop'; /** * @param {import('jscodeshift').FileInfo} file * @param {import('jscodeshift').API} api */ -export default function v6All(file) { - // Currently empty, when adding the first codemod: - // - Read mui-codemod/CONTRIBUTING.md - // - Follow mui-codemod/src/deprecations/all/deprecations-all.js as a guide - // - Remove this comment +export default function v6All(file, api, options) { + file.source = transformerListItemButtonProps(file, api, options); + return file.source; } diff --git a/packages/mui-codemod/src/v6.0.0/list-item-button-prop/index.js b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/index.js new file mode 100644 index 00000000000000..add9efbd02c949 --- /dev/null +++ b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/index.js @@ -0,0 +1 @@ +export { default } from './list-item-button-prop'; diff --git a/packages/mui-codemod/src/v6.0.0/list-item-button-prop/list-item-button-prop.js b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/list-item-button-prop.js new file mode 100644 index 00000000000000..0fb4f0cc53fa73 --- /dev/null +++ b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/list-item-button-prop.js @@ -0,0 +1,131 @@ +import findComponentJSX from '../../util/findComponentJSX'; +import findComponentDefaultProps from '../../util/findComponentDefaultProps'; + +/** + * @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; + + const defaultPropsPathCollection = findComponentDefaultProps(j, { + root, + componentName: 'ListItem', + }); + + defaultPropsPathCollection.find(j.ObjectProperty, { key: { name: 'button' } }).forEach((path) => { + const defaultProps = path.parent.value; + + defaultProps.properties.forEach((property) => { + if (property.key?.name === 'button') { + // Remove the button property from the defaultProps object + const newListButtonProps = defaultProps.properties.filter( + (prop) => prop.key.name !== 'button', + ); + + const muiListItemButtonNode = j.objectProperty( + j.identifier('MuiListItemButton'), + j.objectExpression([ + j.property( + 'init', + j.identifier('defaultProps'), + j.objectExpression(newListButtonProps), + ), + ]), + ); + + // Add MuiListItemButton entry to the parent object + const parentObject = path.parent.parent.parent.parent.parent.node; + parentObject.properties.push(muiListItemButtonNode); + } + }); + + defaultProps.properties = defaultProps.properties.filter( + (prop) => + prop.key.name !== 'button' && + prop.key.name !== 'autoFocus' && + prop.key.name !== 'disabled' && + prop.key.name !== 'selected', + ); + path.prune(); + }); + + const openTaggedNotHavingButtonProp = new Set(); + const openTaggedHavingButtonProp = new Set(); + // Rename components that have ListItem button to ListItemButton + findComponentJSX(j, { root, componentName: 'ListItem' }, (elementPath) => { + const index = elementPath.node.openingElement.attributes.findIndex( + (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'button', + ); + // The ListItem has a button prop + if (index !== -1) { + openTaggedHavingButtonProp.add(elementPath.node.openingElement.name.name); + elementPath.node.openingElement.name.name = `ListItemButton`; + elementPath.node.openingElement.attributes.splice(index, 1); + } else { + openTaggedNotHavingButtonProp.add(elementPath.node.openingElement.name.name); + } + }); + + const importsToRemove = [...openTaggedHavingButtonProp].filter( + (item) => !openTaggedNotHavingButtonProp.has(item), + ); + + root + .find(j.ImportDeclaration) + .filter((path) => path.node.source.value === '@mui/material/ListItem') + .filter((path) => { + path.node.specifiers.forEach((specifier) => { + if (specifier.type === 'ImportDefaultSpecifier') { + if (importsToRemove.indexOf(specifier.local.name) >= 0) { + path.node.specifiers = path.node.specifiers.filter((spec) => spec !== specifier); + } + } + }); + if (path.node.specifiers.length === 0) { + return true; + } + return false; + }) + .remove(); + + root + .find(j.ImportDeclaration) + .filter((path) => path.node.source.value === '@mui/material') + .filter((path) => { + path.node.specifiers.forEach((specifier) => { + if ( + specifier.type === 'ImportSpecifier' && + specifier.imported.name === 'ListItem' && + importsToRemove.indexOf(specifier.local.name) >= 0 + ) { + path.node.specifiers = path.node.specifiers.filter((spec) => spec !== specifier); + } + }); + if (path.node.specifiers.length === 0) { + return true; + } + return false; + }) + .remove(); + // If ListItemButton does not already exist, add it at the end + const imports = root + .find(j.ImportDeclaration) + .filter((path) => path.node.source.value === '@mui/material/ListItemButton'); + + if (imports.length === 0) { + const lastImport = root.find(j.ImportDeclaration).at(-1); + + // Insert the import for 'ListItemButton' after the last import declaration + lastImport.insertAfter( + j.importDeclaration( + [j.importDefaultSpecifier(j.identifier('ListItemButton'))], + j.stringLiteral('@mui/material/ListItemButton'), + ), + ); + } + + return root.toSource(printOptions); +} diff --git a/packages/mui-codemod/src/v6.0.0/list-item-button-prop/list-item-button-prop.test.js b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/list-item-button-prop.test.js new file mode 100644 index 00000000000000..723ae584e04f5d --- /dev/null +++ b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/list-item-button-prop.test.js @@ -0,0 +1,65 @@ +import path from 'path'; +import { expect } from 'chai'; +import { jscodeshift } from '../../../testUtils'; +import transform from './list-item-button-prop'; +import readFile from '../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('@mui/codemod', () => { + describe('deprecations', () => { + describe('list-item-button-prop', () => { + 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'); + }); + + it('actual.js should not be equal to expected.js', () => { + const actual = read('./test-cases/actual.js'); + const expected = read('./test-cases/expected.js'); + expect(actual).to.not.equal(expected); + }); + }); + + describe('[theme] button-props', () => { + it('transforms props as needed', () => { + const actual = transform( + { source: read('./test-cases/theme.actual.js') }, + { jscodeshift }, + {}, + ); + + 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'); + }); + + it('theme.actual.js should not be equal to theme.expected.js', () => { + const actual = read('./test-cases/theme.actual.js'); + const expected = read('./test-cases/theme.expected.js'); + expect(actual).to.not.equal(expected); + }); + }); + }); +}); diff --git a/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/actual.js b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/actual.js new file mode 100644 index 00000000000000..b58cbe361031a6 --- /dev/null +++ b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/actual.js @@ -0,0 +1,24 @@ +import ListItem from "@mui/material/ListItem"; +import MuiListItem from "@mui/material/ListItem"; +import { ListItem as MyListItem } from "@mui/material"; +import { ListItem as MyListItem1 } from "@mui/material"; +import { ListItem as MyListItem2, Button } from "@mui/material"; +import { ListItem as MyListItem3, List } from "@mui/material"; +import AnotherComponent from "ui"; + +; +; + +; +; + +; + +; + +; + +; +; + +; \ No newline at end of file diff --git a/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/expected.js b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/expected.js new file mode 100644 index 00000000000000..01e7933e147757 --- /dev/null +++ b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/expected.js @@ -0,0 +1,24 @@ +import ListItem from "@mui/material/ListItem"; +import { ListItem as MyListItem } from "@mui/material"; +import { Button } from "@mui/material"; +import { ListItem as MyListItem3, List } from "@mui/material"; +import AnotherComponent from "ui"; + +import ListItemButton from "@mui/material/ListItemButton"; + +; +; + +; +; + +; + +; + +; + +; +; + +; \ No newline at end of file diff --git a/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/theme.actual.js b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/theme.actual.js new file mode 100644 index 00000000000000..b9013d5a5e45f8 --- /dev/null +++ b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/theme.actual.js @@ -0,0 +1,19 @@ +fn({ + MuiListItem: { + defaultProps: { + anotherProp: 'value', + autoFocus: true, + disabled: false, + selected: true, + button: true, + } + } +}); + +fn({ + MuiListItem: { + defaultProps: { + anotherProp: 'value' + } + } +}); \ No newline at end of file diff --git a/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/theme.expected.js b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/theme.expected.js new file mode 100644 index 00000000000000..fccbe337fea630 --- /dev/null +++ b/packages/mui-codemod/src/v6.0.0/list-item-button-prop/test-cases/theme.expected.js @@ -0,0 +1,24 @@ +fn({ + MuiListItem: { + defaultProps: { + anotherProp: 'value' + } + }, + + MuiListItemButton: { + defaultProps: { + anotherProp: 'value', + autoFocus: true, + disabled: false, + selected: true + } + } +}); + +fn({ + MuiListItem: { + defaultProps: { + anotherProp: 'value' + } + } +}); \ No newline at end of file diff --git a/packages/mui-material/src/ListItem/ListItem.d.ts b/packages/mui-material/src/ListItem/ListItem.d.ts index e0ac9ab0deb244..59da2dee9b6e5b 100644 --- a/packages/mui-material/src/ListItem/ListItem.d.ts +++ b/packages/mui-material/src/ListItem/ListItem.d.ts @@ -1,7 +1,6 @@ import * as React from 'react'; import { SxProps } from '@mui/system'; import { Theme } from '../styles'; -import { ExtendButtonBase } from '../ButtonBase'; import { OverridableComponent, OverrideProps } from '../OverridableComponent'; import { ListItemClasses } from './listItemClasses'; @@ -16,13 +15,6 @@ export interface ListItemBaseProps { * @default 'center' */ alignItems?: 'flex-start' | 'center'; - /** - * If `true`, the list item is focused during the first mount. - * Focus will also be triggered if the value changes from false to true. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - autoFocus?: boolean; /** * The content of the component if a `ListItemSecondaryAction` is used it must * be the last child. @@ -50,12 +42,6 @@ export interface ListItemBaseProps { * @default false */ dense?: boolean; - /** - * If `true`, the component is disabled. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - disabled?: boolean; /** * If `true`, the left and right padding is removed. * @default false @@ -75,12 +61,6 @@ export interface ListItemBaseProps { * The element to display at the end of ListItem. */ secondaryAction?: React.ReactNode; - /** - * Use to apply selected styling. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - selected?: boolean; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -143,35 +123,7 @@ export interface ListItemTypeMap -> & - OverridableComponent< - ListItemTypeMap< - { - /** - * If `true`, the list item is a button (using `ButtonBase`). Props intended - * for `ButtonBase` can then be applied to `ListItem`. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - button?: false; - }, - 'li' - > - >; +declare const ListItem: OverridableComponent>; export type ListItemProps< RootComponent extends React.ElementType = 'li', diff --git a/packages/mui-material/src/ListItem/ListItem.js b/packages/mui-material/src/ListItem/ListItem.js index f9fc7c4c26531c..e435033a1f11c7 100644 --- a/packages/mui-material/src/ListItem/ListItem.js +++ b/packages/mui-material/src/ListItem/ListItem.js @@ -5,16 +5,13 @@ import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef'; import chainPropTypes from '@mui/utils/chainPropTypes'; -import { alpha } from '@mui/system/colorManipulator'; import isHostComponent from '../utils/isHostComponent'; import { styled } from '../zero-styled'; import { useDefaultProps } from '../DefaultPropsProvider'; -import ButtonBase from '../ButtonBase'; import isMuiElement from '../utils/isMuiElement'; -import useEnhancedEffect from '../utils/useEnhancedEffect'; import useForkRef from '../utils/useForkRef'; import ListContext from '../List/ListContext'; -import listItemClasses, { getListItemUtilityClass } from './listItemClasses'; +import { getListItemUtilityClass } from './listItemClasses'; import { listItemButtonClasses } from '../ListItemButton'; import ListItemSecondaryAction from '../ListItemSecondaryAction'; @@ -28,7 +25,6 @@ export const overridesResolver = (props, styles) => { ownerState.divider && styles.divider, !ownerState.disableGutters && styles.gutters, !ownerState.disablePadding && styles.padding, - ownerState.button && styles.button, ownerState.hasSecondaryAction && styles.secondaryAction, ]; }; @@ -36,15 +32,12 @@ export const overridesResolver = (props, styles) => { const useUtilityClasses = (ownerState) => { const { alignItems, - button, classes, dense, - disabled, disableGutters, disablePadding, divider, hasSecondaryAction, - selected, } = ownerState; const slots = { @@ -54,11 +47,8 @@ const useUtilityClasses = (ownerState) => { !disableGutters && 'gutters', !disablePadding && 'padding', divider && 'divider', - disabled && 'disabled', - button && 'button', alignItems === 'flex-start' && 'alignItemsFlexStart', hasSecondaryAction && 'secondaryAction', - selected && 'selected', ], container: ['container'], }; @@ -79,25 +69,6 @@ export const ListItemRoot = styled('div', { width: '100%', boxSizing: 'border-box', textAlign: 'left', - [`&.${listItemClasses.focusVisible}`]: { - backgroundColor: (theme.vars || theme).palette.action.focus, - }, - [`&.${listItemClasses.selected}`]: { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` - : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), - [`&.${listItemClasses.focusVisible}`]: { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` - : alpha( - theme.palette.primary.main, - theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity, - ), - }, - }, - [`&.${listItemClasses.disabled}`]: { - opacity: (theme.vars || theme).palette.action.disabledOpacity, - }, variants: [ { props: ({ ownerState }) => !ownerState.disablePadding, @@ -165,20 +136,6 @@ export const ListItemRoot = styled('div', { backgroundColor: 'transparent', }, }, - [`&.${listItemClasses.selected}:hover`]: { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.hoverOpacity}))` - : alpha( - theme.palette.primary.main, - theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity, - ), - // Reset on touch devices, it doesn't add specificity - '@media (hover: none)': { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` - : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), - }, - }, }, }, { @@ -207,8 +164,6 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { const props = useDefaultProps({ props: inProps, name: 'MuiListItem' }); const { alignItems = 'center', - autoFocus = false, - button = false, children: childrenProp, className, component: componentProp, @@ -217,13 +172,10 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { ContainerComponent = 'li', ContainerProps: { className: ContainerClassName, ...ContainerProps } = {}, dense = false, - disabled = false, disableGutters = false, disablePadding = false, divider = false, - focusVisibleClassName, secondaryAction, - selected = false, slotProps = {}, slots = {}, ...other @@ -240,17 +192,6 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { ); const listItemRef = React.useRef(null); - useEnhancedEffect(() => { - if (autoFocus) { - if (listItemRef.current) { - listItemRef.current.focus(); - } else if (process.env.NODE_ENV !== 'production') { - console.error( - 'MUI: Unable to set focus to a ListItem whose component has not been rendered.', - ); - } - } - }, [autoFocus]); const children = React.Children.toArray(childrenProp); @@ -261,15 +202,11 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { const ownerState = { ...props, alignItems, - autoFocus, - button, dense: childContext.dense, - disabled, disableGutters, disablePadding, divider, hasSecondaryAction, - selected, }; const classes = useUtilityClasses(ownerState); @@ -281,22 +218,11 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { const componentProps = { className: clsx(classes.root, rootProps.className, className), - disabled, ...other, }; let Component = componentProp || 'li'; - if (button) { - componentProps.component = componentProp || 'div'; - componentProps.focusVisibleClassName = clsx( - listItemClasses.focusVisible, - focusVisibleClassName, - ); - - Component = ButtonBase; - } - // v4 implementation, deprecated in v6, will be removed in v7 if (hasSecondaryAction) { // Use div by default. @@ -364,20 +290,6 @@ ListItem.propTypes /* remove-proptypes */ = { * @default 'center' */ alignItems: PropTypes.oneOf(['center', 'flex-start']), - /** - * If `true`, the list item is focused during the first mount. - * Focus will also be triggered if the value changes from false to true. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - autoFocus: PropTypes.bool, - /** - * If `true`, the list item is a button (using `ButtonBase`). Props intended - * for `ButtonBase` can then be applied to `ListItem`. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - button: PropTypes.bool, /** * The content of the component if a `ListItemSecondaryAction` is used it must * be the last child. @@ -456,12 +368,6 @@ ListItem.propTypes /* remove-proptypes */ = { * @default false */ dense: PropTypes.bool, - /** - * If `true`, the component is disabled. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - disabled: PropTypes.bool, /** * If `true`, the left and right padding is removed. * @default false @@ -477,20 +383,10 @@ ListItem.propTypes /* remove-proptypes */ = { * @default false */ divider: PropTypes.bool, - /** - * @ignore - */ - focusVisibleClassName: PropTypes.string, /** * The element to display at the end of ListItem. */ secondaryAction: PropTypes.node, - /** - * Use to apply selected styling. - * @default false - * @deprecated checkout [ListItemButton](/material-ui/api/list-item-button/) instead - */ - selected: PropTypes.bool, /** * The extra props for the slot components. * You can override the existing props or add new ones. diff --git a/packages/mui-material/src/ListItem/ListItem.spec.tsx b/packages/mui-material/src/ListItem/ListItem.spec.tsx index 2814e8736ee60f..aa3d9ff870d40f 100644 --- a/packages/mui-material/src/ListItem/ListItem.spec.tsx +++ b/packages/mui-material/src/ListItem/ListItem.spec.tsx @@ -1,25 +1,7 @@ import * as React from 'react'; import ListItem from '@mui/material/ListItem'; import { styled } from '@mui/material/styles'; - -// button: boolean -function BooleanButtonTest() { - // https://github.com/mui/material-ui/issues/14971 - - function EditableItemFail(props: { editable: boolean }) { - const { editable } = props; - // @ts-expect-error 'boolean' is not assignable to type 'true' - return Editable? {editable}; - } - - function EditableItemValid(props: { editable: boolean }) { - const { editable } = props; - if (editable) { - Editable? Yes; - } - return Editable? No; - } -} +import ListItemButton from '@mui/material/ListItemButton'; // verify that https://github.com/mui/material-ui/issues/19756 already worked. function MouseEnterTest() { @@ -29,7 +11,7 @@ function MouseEnterTest() { function handleMouseEnterButton(event: React.MouseEvent) {} // @ts-expect-error ; // desired: missing property button - ; + ; } // https://github.com/mui/material-ui/issues/26469 diff --git a/packages/mui-material/src/ListItem/ListItem.test.js b/packages/mui-material/src/ListItem/ListItem.test.js index 4c9101020de95d..8df90d7167c584 100644 --- a/packages/mui-material/src/ListItem/ListItem.test.js +++ b/packages/mui-material/src/ListItem/ListItem.test.js @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import PropTypes from 'prop-types'; -import { act, createRenderer, fireEvent, queries, reactMajor } from '@mui/internal-test-utils'; +import { createRenderer, reactMajor } from '@mui/internal-test-utils'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import ListItemText from '@mui/material/ListItemText'; import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; @@ -9,10 +9,6 @@ import ListItem, { listItemClasses as classes } from '@mui/material/ListItem'; import ListContext from '../List/ListContext'; import describeConformance from '../../test/describeConformance'; -const NoContent = React.forwardRef(() => { - return null; -}); - describe('', () => { const { render } = createRenderer(); @@ -38,23 +34,11 @@ describe('', () => { expect(getByRole('listitem')).to.have.class(classes.gutters); }); - it('should render with the selected class', () => { - const { getByRole } = render(); - expect(getByRole('listitem')).to.have.class(classes.selected); - }); - it('should disable the gutters', () => { const { getByRole } = render(); expect(getByRole('listitem')).not.to.have.class(classes.gutters); }); - describe('prop: button', () => { - it('renders a div', () => { - const { container } = render(); - expect(container.firstChild).to.have.property('nodeName', 'DIV'); - }); - }); - describe('context: dense', () => { it('should forward the context', () => { let context = null; @@ -108,19 +92,6 @@ describe('', () => { expect(listItem.querySelector(`span.${classes.root}`)).not.to.equal(null); }); - it('should accept a button property', () => { - const { getByRole } = render( - - - - , - ); - const listItem = getByRole('listitem'); - - expect(listItem).to.have.class(classes.container); - expect(queries.getByRole(listItem, 'button')).not.to.equal(null); - }); - it('should accept a ContainerComponent property', () => { const { getByRole } = render( @@ -135,21 +106,6 @@ describe('', () => { expect(listItem.querySelector(`div.${classes.root}`)).not.to.equal(null); }); - it('can autofocus a custom ContainerComponent', () => { - const { getByRole } = render( - - - - , - ); - - expect(getByRole('listitem')).toHaveFocus(); - }); - it('should allow customization of the wrapper', () => { const { getByRole } = render( @@ -189,40 +145,6 @@ describe('', () => { ); }).toErrorDev('Warning: Failed prop type: MUI: You used an element'); }); - - it('should warn (but not error) with autoFocus with a function component with no content', () => { - expect(() => { - render(); - }).toErrorDev([ - 'MUI: Unable to set focus to a ListItem whose component has not been rendered.', - // React 18 Strict Effects run mount effects twice - reactMajor === 18 && - 'MUI: Unable to set focus to a ListItem whose component has not been rendered.', - ]); - }); - }); - }); - - // TODO remove in v6 in favor of ListItemButton - describe('prop: focusVisibleClassName', () => { - it('should merge the class names', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // JSDOM doesn't support :focus-visible - this.skip(); - } - - const { getByRole } = render( - , - ); - const button = getByRole('button'); - - act(() => { - fireEvent.keyDown(document.activeElement || document.body, { key: 'Tab' }); - button.focus(); - }); - - expect(button).to.have.class('focusVisibleClassName'); - expect(button).to.have.class(classes.focusVisible); }); }); diff --git a/packages/mui-material/src/ListItem/listItemClasses.ts b/packages/mui-material/src/ListItem/listItemClasses.ts index 98a317a8fe4d69..d56735452c5f5c 100644 --- a/packages/mui-material/src/ListItem/listItemClasses.ts +++ b/packages/mui-material/src/ListItem/listItemClasses.ts @@ -6,26 +6,18 @@ export interface ListItemClasses { root: string; /** Styles applied to the container element if `children` includes `ListItemSecondaryAction`. */ container: string; - /** State class applied to the `component`'s `focusVisibleClassName` prop if `button={true}`. */ - focusVisible: string; /** Styles applied to the component element if dense. */ dense: string; /** Styles applied to the component element if `alignItems="flex-start"`. */ alignItemsFlexStart: string; - /** State class applied to the inner `component` element if `disabled={true}`. */ - disabled: string; /** Styles applied to the inner `component` element if `divider={true}`. */ divider: string; /** Styles applied to the inner `component` element unless `disableGutters={true}`. */ gutters: string; /** Styles applied to the root element unless `disablePadding={true}`. */ padding: string; - /** Styles applied to the inner `component` element if `button={true}`. */ - button: string; /** Styles applied to the component element if `children` includes `ListItemSecondaryAction`. */ secondaryAction: string; - /** State class applied to the root element if `selected={true}`. */ - selected: string; } export type ListItemClassKey = keyof ListItemClasses; @@ -37,16 +29,12 @@ export function getListItemUtilityClass(slot: string): string { const listItemClasses: ListItemClasses = generateUtilityClasses('MuiListItem', [ 'root', 'container', - 'focusVisible', 'dense', 'alignItemsFlexStart', - 'disabled', 'divider', 'gutters', 'padding', - 'button', 'secondaryAction', - 'selected', ]); export default listItemClasses; diff --git a/test/integration/material-ui/components.spec.tsx b/test/integration/material-ui/components.spec.tsx index 2966e7bc153a5a..f392fd737b1b22 100644 --- a/test/integration/material-ui/components.spec.tsx +++ b/test/integration/material-ui/components.spec.tsx @@ -73,6 +73,7 @@ import { Toolbar, Tooltip, Typography, + ListItemButton, } from '@mui/material'; import { Theme } from '@mui/material/styles'; import { ButtonBaseActions } from '@mui/material/ButtonBase'; @@ -320,14 +321,14 @@ function DialogTest() {
{emails.map((email) => ( - log(e)} key={email}> + log(e)} key={email}> - + ))} { @@ -343,8 +344,7 @@ function DialogTest() { - { expectType(elem); }} @@ -359,8 +359,8 @@ function DialogTest() { - - + + component="a" ref={(elem) => { expectType(elem); @@ -369,19 +369,18 @@ function DialogTest() { expectType, typeof e>(e); log(e); }} - button > - - + + - +
@@ -561,7 +560,7 @@ function ListTest() { return ( {[0, 1, 2, 3].map((value) => ( - log(e)}> + log(e)}> @@ -569,7 +568,7 @@ function ListTest() { - +
))} an item