diff --git a/docs/data/material/components/material-icons/SearchIcons.js b/docs/data/material/components/material-icons/SearchIcons.js index 4e252ebcca769c..f1d8cba2e84783 100644 --- a/docs/data/material/components/material-icons/SearchIcons.js +++ b/docs/data/material/components/material-icons/SearchIcons.js @@ -376,8 +376,10 @@ const DialogDetails = React.memo(function DialogDetails(props) { setCopied1(false), + slotProps={{ + transition: { + onExited: () => setCopied1(false), + }, }} > @@ -388,7 +390,9 @@ const DialogDetails = React.memo(function DialogDetails(props) { <Tooltip placement="top" title={copied2 ? t('copied') : t('clickToCopy')} - TransitionProps={{ onExited: () => setCopied2(false) }} + slotProps={{ + transition: { onExited: () => setCopied2(false) }, + }} > <Markdown copyButtonHidden diff --git a/docs/data/material/components/tooltips/AnchorElTooltips.js b/docs/data/material/components/tooltips/AnchorElTooltips.js index ecbc291b2d2116..cbfaaf36dd61f6 100644 --- a/docs/data/material/components/tooltips/AnchorElTooltips.js +++ b/docs/data/material/components/tooltips/AnchorElTooltips.js @@ -23,16 +23,18 @@ export default function AnchorElTooltips() { title="Add" placement="top" arrow - PopperProps={{ - popperRef, - anchorEl: { - getBoundingClientRect: () => { - return new DOMRect( - positionRef.current.x, - areaRef.current.getBoundingClientRect().y, - 0, - 0, - ); + slotProps={{ + popper: { + popperRef, + anchorEl: { + getBoundingClientRect: () => { + return new DOMRect( + positionRef.current.x, + areaRef.current.getBoundingClientRect().y, + 0, + 0, + ); + }, }, }, }} diff --git a/docs/data/material/components/tooltips/AnchorElTooltips.tsx b/docs/data/material/components/tooltips/AnchorElTooltips.tsx index 9e5ef5ea7bfe9e..736a61ef39712d 100644 --- a/docs/data/material/components/tooltips/AnchorElTooltips.tsx +++ b/docs/data/material/components/tooltips/AnchorElTooltips.tsx @@ -24,16 +24,18 @@ export default function AnchorElTooltips() { title="Add" placement="top" arrow - PopperProps={{ - popperRef, - anchorEl: { - getBoundingClientRect: () => { - return new DOMRect( - positionRef.current.x, - areaRef.current!.getBoundingClientRect().y, - 0, - 0, - ); + slotProps={{ + popper: { + popperRef, + anchorEl: { + getBoundingClientRect: () => { + return new DOMRect( + positionRef.current.x, + areaRef.current!.getBoundingClientRect().y, + 0, + 0, + ); + }, }, }, }} diff --git a/docs/data/material/components/tooltips/TransitionsTooltips.js b/docs/data/material/components/tooltips/TransitionsTooltips.js index f468119c6b1a0c..f2b08180781486 100644 --- a/docs/data/material/components/tooltips/TransitionsTooltips.js +++ b/docs/data/material/components/tooltips/TransitionsTooltips.js @@ -11,13 +11,22 @@ export default function TransitionsTooltips() { <Button>Grow</Button> </Tooltip> <Tooltip - TransitionComponent={Fade} - TransitionProps={{ timeout: 600 }} title="Add" + slots={{ + transition: Fade, + }} + slotProps={{ + transition: { timeout: 600 }, + }} > <Button>Fade</Button> </Tooltip> - <Tooltip TransitionComponent={Zoom} title="Add"> + <Tooltip + title="Add" + slots={{ + transition: Zoom, + }} + > <Button>Zoom</Button> </Tooltip> </div> diff --git a/docs/data/material/components/tooltips/TransitionsTooltips.tsx b/docs/data/material/components/tooltips/TransitionsTooltips.tsx index f468119c6b1a0c..f2b08180781486 100644 --- a/docs/data/material/components/tooltips/TransitionsTooltips.tsx +++ b/docs/data/material/components/tooltips/TransitionsTooltips.tsx @@ -11,13 +11,22 @@ export default function TransitionsTooltips() { <Button>Grow</Button> </Tooltip> <Tooltip - TransitionComponent={Fade} - TransitionProps={{ timeout: 600 }} title="Add" + slots={{ + transition: Fade, + }} + slotProps={{ + transition: { timeout: 600 }, + }} > <Button>Fade</Button> </Tooltip> - <Tooltip TransitionComponent={Zoom} title="Add"> + <Tooltip + title="Add" + slots={{ + transition: Zoom, + }} + > <Button>Zoom</Button> </Tooltip> </div> diff --git a/docs/data/material/components/tooltips/TransitionsTooltips.tsx.preview b/docs/data/material/components/tooltips/TransitionsTooltips.tsx.preview deleted file mode 100644 index 3a298ce9881fe9..00000000000000 --- a/docs/data/material/components/tooltips/TransitionsTooltips.tsx.preview +++ /dev/null @@ -1,13 +0,0 @@ -<Tooltip title="Add"> - <Button>Grow</Button> -</Tooltip> -<Tooltip - TransitionComponent={Fade} - TransitionProps={{ timeout: 600 }} - title="Add" -> - <Button>Fade</Button> -</Tooltip> -<Tooltip TransitionComponent={Zoom} title="Add"> - <Button>Zoom</Button> -</Tooltip> \ No newline at end of file diff --git a/docs/data/material/components/tooltips/TriggersTooltips.js b/docs/data/material/components/tooltips/TriggersTooltips.js index 24894fbe9f3b7a..822022c564d979 100644 --- a/docs/data/material/components/tooltips/TriggersTooltips.js +++ b/docs/data/material/components/tooltips/TriggersTooltips.js @@ -37,15 +37,17 @@ export default function TriggersTooltips() { <ClickAwayListener onClickAway={handleTooltipClose}> <div> <Tooltip - PopperProps={{ - disablePortal: true, - }} onClose={handleTooltipClose} open={open} disableFocusListener disableHoverListener disableTouchListener title="Add" + slotProps={{ + popper: { + disablePortal: true, + }, + }} > <Button onClick={handleTooltipOpen}>Click</Button> </Tooltip> diff --git a/docs/data/material/components/tooltips/TriggersTooltips.tsx b/docs/data/material/components/tooltips/TriggersTooltips.tsx index 24894fbe9f3b7a..822022c564d979 100644 --- a/docs/data/material/components/tooltips/TriggersTooltips.tsx +++ b/docs/data/material/components/tooltips/TriggersTooltips.tsx @@ -37,15 +37,17 @@ export default function TriggersTooltips() { <ClickAwayListener onClickAway={handleTooltipClose}> <div> <Tooltip - PopperProps={{ - disablePortal: true, - }} onClose={handleTooltipClose} open={open} disableFocusListener disableHoverListener disableTouchListener title="Add" + slotProps={{ + popper: { + disablePortal: true, + }, + }} > <Button onClick={handleTooltipOpen}>Click</Button> </Tooltip> 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 e59a553b13259d..0135a0482427e0 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 @@ -1687,6 +1687,36 @@ The Tooltip's prop `componentsProps` was deprecated in favor of `slotProps`: /> ``` +### \*Component props + +All of the Tooltip's slot (`*Component`) props were deprecated in favor of equivalent `slots` entries: + +```diff + <Tooltip +- PopperComponent={CustomPopperComponent} +- TransitionComponent={CustomTransitionComponent} ++ slots={{ ++ popper: CustomPopperComponent, ++ transition: CustomTransitionComponent, ++ }} + /> +``` + +### \*Props props + +All of the Tooltip's slot props (`*Props`) props were deprecated in favor of equivalent `slotProps` entries: + +```diff + <Tooltip +- PopperProps={CustomPopperProps} +- TransitionProps={CustomTransitionProps} ++ slotProps={{ ++ popper: CustomPopperProps, ++ transition: CustomTransitionProps, ++ }} + /> +``` + ## Typography Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#typography-props) below to migrate the code as described in the following sections: diff --git a/docs/pages/material-ui/api/tooltip.json b/docs/pages/material-ui/api/tooltip.json index 9433e5b4a16481..e984629c6e788c 100644 --- a/docs/pages/material-ui/api/tooltip.json +++ b/docs/pages/material-ui/api/tooltip.json @@ -10,7 +10,7 @@ }, "default": "{}", "deprecated": true, - "deprecationInfo": "use the <code>slots</code> prop instead. This prop will be removed in v7. <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">How to migrate</a>." + "deprecationInfo": "use the <code>slots</code> prop instead. This prop will be removed in v7. See <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">Migrating from deprecated APIs</a> for more details." }, "componentsProps": { "type": { @@ -19,7 +19,7 @@ }, "default": "{}", "deprecated": true, - "deprecationInfo": "use the <code>slotProps</code> prop instead. This prop will be removed in v7. <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">How to migrate</a>." + "deprecationInfo": "use the <code>slotProps</code> prop instead. This prop will be removed in v7. See <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">Migrating from deprecated APIs</a> for more details." }, "describeChild": { "type": { "name": "bool" }, "default": "false" }, "disableFocusListener": { "type": { "name": "bool" }, "default": "false" }, @@ -55,12 +55,21 @@ }, "default": "'bottom'" }, - "PopperComponent": { "type": { "name": "elementType" }, "default": "Popper" }, - "PopperProps": { "type": { "name": "object" }, "default": "{}" }, + "PopperComponent": { + "type": { "name": "elementType" }, + "deprecated": true, + "deprecationInfo": "use the <code>slots.popper</code> prop instead. This prop will be removed in v7. See <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">Migrating from deprecated APIs</a> for more details." + }, + "PopperProps": { + "type": { "name": "object" }, + "default": "{}", + "deprecated": true, + "deprecationInfo": "use the <code>slotProps.popper</code> prop instead. This prop will be removed in v7. See <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">Migrating from deprecated APIs</a> for more details." + }, "slotProps": { "type": { "name": "shape", - "description": "{ arrow?: object, popper?: object, tooltip?: object, transition?: object }" + "description": "{ arrow?: func<br>| object, popper?: func<br>| object, tooltip?: func<br>| object, transition?: func<br>| object }" }, "default": "{}" }, @@ -79,27 +88,48 @@ "additionalInfo": { "sx": true } }, "title": { "type": { "name": "node" } }, - "TransitionComponent": { "type": { "name": "elementType" }, "default": "Grow" }, - "TransitionProps": { "type": { "name": "object" } } + "TransitionComponent": { + "type": { "name": "elementType" }, + "deprecated": true, + "deprecationInfo": "use the <code>slots.transition</code> prop instead. This prop will be removed in v7. See <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">Migrating from deprecated APIs</a> for more details." + }, + "TransitionProps": { + "type": { "name": "object" }, + "default": "{}", + "deprecated": true, + "deprecationInfo": "use the <code>slotProps.transition</code> prop instead. This prop will be removed in v7. See <a href=\"https://mui.com/material-ui/migration/migrating-from-deprecated-apis/\">Migrating from deprecated APIs</a> for more details." + } }, "name": "Tooltip", "imports": [ "import Tooltip from '@mui/material/Tooltip';", "import { Tooltip } from '@mui/material';" ], - "classes": [ + "slots": [ { - "key": "arrow", - "className": "MuiTooltip-arrow", - "description": "Styles applied to the arrow element.", - "isGlobal": false + "name": "popper", + "description": "The component used for the popper.", + "default": "Popper", + "class": "MuiTooltip-popper" }, { - "key": "popper", - "className": "MuiTooltip-popper", - "description": "Styles applied to the Popper component.", - "isGlobal": false + "name": "transition", + "description": "The component used for the transition.\n[Follow this guide](https://mui.com/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.", + "default": "Grow", + "class": null + }, + { + "name": "tooltip", + "description": "The component used for the tooltip.", + "class": "MuiTooltip-tooltip" }, + { + "name": "arrow", + "description": "The component used for the arrow.", + "class": "MuiTooltip-arrow" + } + ], + "classes": [ { "key": "popperArrow", "className": "MuiTooltip-popperArrow", @@ -118,12 +148,6 @@ "description": "Styles applied to the Popper component unless `disableInteractive={true}`.", "isGlobal": false }, - { - "key": "tooltip", - "className": "MuiTooltip-tooltip", - "description": "Styles applied to the tooltip (label wrapper) element.", - "isGlobal": false - }, { "key": "tooltipArrow", "className": "MuiTooltip-tooltipArrow", diff --git a/docs/translations/api-docs/tooltip/tooltip.json b/docs/translations/api-docs/tooltip/tooltip.json index 27477d14a534a5..4b8e1c89648827 100644 --- a/docs/translations/api-docs/tooltip/tooltip.json +++ b/docs/translations/api-docs/tooltip/tooltip.json @@ -52,12 +52,8 @@ "PopperProps": { "description": "Props applied to the <a href=\"https://mui.com/material-ui/api/popper/\"><code>Popper</code></a> element." }, - "slotProps": { - "description": "The extra props for the slot components. You can override the existing props or add new ones.<br>This prop is an alias for the <code>componentsProps</code> prop, which will be deprecated in the future." - }, - "slots": { - "description": "The components used for each slot inside.<br>This prop is an alias for the <code>components</code> 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." }, @@ -72,11 +68,6 @@ } }, "classDescriptions": { - "arrow": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the arrow element" }, - "popper": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the Popper component" - }, "popperArrow": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the Popper component", @@ -92,10 +83,6 @@ "nodeName": "the Popper component", "conditions": "<code>disableInteractive={true}</code>" }, - "tooltip": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the tooltip (label wrapper) element" - }, "tooltipArrow": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the tooltip (label wrapper) element", @@ -126,5 +113,11 @@ "nodeName": "the tooltip (label wrapper) element", "conditions": "the tooltip is opened by touch" } + }, + "slotDescriptions": { + "arrow": "The component used for the arrow.", + "popper": "The component used for the popper.", + "tooltip": "The component used for the tooltip.", + "transition": "The component used for the transition. <a href=\"https://mui.com/material-ui/transitions/#transitioncomponent-prop\">Follow this guide</a> to learn more about the requirements for this component." } } diff --git a/packages-internal/test-utils/src/describeConformance.tsx b/packages-internal/test-utils/src/describeConformance.tsx index 64c5600dad12c1..c8892f4160bc20 100644 --- a/packages-internal/test-utils/src/describeConformance.tsx +++ b/packages-internal/test-utils/src/describeConformance.tsx @@ -321,7 +321,9 @@ function testSlotsProp(element: React.ReactElement<any>, getOptions: () => Confo const renderedElement = queryByTestId('customized'); expect(renderedElement).not.to.equal(null); - expect(renderedElement!.nodeName.toLowerCase()).to.equal(slotElement); + if (typeof slotElement === 'string') { + expect(renderedElement!.nodeName.toLowerCase()).to.equal(slotElement); + } if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -425,7 +427,9 @@ function testSlotsProp(element: React.ReactElement<any>, getOptions: () => Confo const renderedElement = queryByTestId('customized'); expect(renderedElement).not.to.equal(null); - expect(renderedElement!.nodeName.toLowerCase()).to.equal(slotElement); + if (typeof slotElement === 'string') { + expect(renderedElement!.nodeName.toLowerCase()).to.equal(slotElement); + } if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } diff --git a/packages/mui-codemod/README.md b/packages/mui-codemod/README.md index 7c65a8f0d7e0e3..0d4ea76fe6e344 100644 --- a/packages/mui-codemod/README.md +++ b/packages/mui-codemod/README.md @@ -1412,19 +1412,43 @@ npx @mui/codemod@latest deprecations/slider-props <path> <Tooltip - components={{ Arrow: CustomArrow }} - componentsProps={{ arrow: { testid: 'test-id' } }} -+ slots={{ arrow: CustomArrow }} -+ slotProps={{ arrow: { testid: 'test-id' } }} +- PopperComponent={CustomPopperComponent} +- TransitionComponent={CustomTransitionComponent} +- PopperProps={CustomPopperProps} +- TransitionProps={CustomTransitionProps} ++ slots={{ ++ arrow: CustomArrow, ++ popper: CustomPopperComponent, ++ transition: CustomTransitionComponent, ++ }} ++ slotProps={{ ++ arrow: { testid: 'test-id' }, ++ popper: CustomPopperProps, ++ transition: CustomTransitionProps, ++ }} /> ``` ```diff MuiTooltip: { defaultProps: { +- PopperComponent: CustomPopperComponent, +- TransitionComponent: CustomTransitionComponent, +- PopperProps: CustomPopperProps, +- TransitionProps: CustomTransitionProps, - components: { Arrow: CustomArrow } -+ slots: { arrow: CustomArrow }, ++ slots: { ++ arrow: CustomArrow, ++ popper: CustomPopperComponent, ++ transition: CustomTransitionComponent, ++ }, - componentsProps: { arrow: { testid: 'test-id' }} -+ slotProps: { arrow: { testid: 'test-id' } }, - }, ++ slotProps: { ++ arrow: { testid: 'test-id' }, ++ popper: CustomPopperProps, ++ transition: CustomTransitionProps, ++ }, + }, }, ``` @@ -1455,6 +1479,10 @@ JS transforms: }, ``` +```bash +npx @mui/codemod@next deprecations/step-connector-classes <path> +``` + #### `step-label-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 eccb9e3d130375..1dcd1d225d378e 100644 --- a/packages/mui-codemod/src/deprecations/all/deprecations-all.js +++ b/packages/mui-codemod/src/deprecations/all/deprecations-all.js @@ -25,6 +25,7 @@ import transformStepLabelProps from '../step-label-props'; import transformTextFieldProps from '../text-field-props'; import transformTabClasses from '../tab-classes'; import transformToggleButtonGroupClasses from '../toggle-button-group-classes'; +import transformTooltipProps from '../tooltip-props'; /** * @param {import('jscodeshift').FileInfo} file @@ -58,6 +59,7 @@ export default function deprecationsAll(file, api, options) { file.source = transformTextFieldProps(file, api, options); file.source = transformTabClasses(file, api, options); file.source = transformToggleButtonGroupClasses(file, api, options); + file.source = transformTooltipProps(file, api, options); return file.source; } diff --git a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/actual.js index a02495c65013ca..94c937c0584b78 100644 --- a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/actual.js +++ b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/actual.js @@ -1,4 +1,5 @@ import Tooltip from '@mui/material/Tooltip'; +import { Tooltip as MyTooltip } from '@mui/material'; <Tooltip components={{ @@ -58,3 +59,37 @@ import Tooltip from '@mui/material/Tooltip'; transition: componentsTransitionProps }} />; + +<Tooltip + PopperComponent={CustomPopper} + TransitionComponent={CustomTransition} + PopperProps={{ disablePortal: true }} + TransitionProps={{ timeout: 200 }} +/>; + +<Tooltip + PopperComponent={CustomPopper} + TransitionComponent={CustomTransition} + PopperProps={{ disablePortal: true }} + TransitionProps={{ timeout: 200 }} + slotProps={{ + tooltip: { height: 20 } + }} + slots={{ + tooltip: "div", + }} +/>; + +<MyTooltip + PopperComponent={CustomPopper} + TransitionComponent={CustomTransition} + PopperProps={{ disablePortal: true }} + TransitionProps={{ timeout: 200 }} +/>; + +<CustomTooltip + PopperComponent={CustomPopper} + TransitionComponent={CustomTransition} + PopperProps={{ disablePortal: true }} + TransitionProps={{ timeout: 200 }} +/> diff --git a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/expected.js index a6e62ef6c7d73f..eba838e727c3f2 100644 --- a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/expected.js +++ b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/expected.js @@ -1,4 +1,5 @@ import Tooltip from '@mui/material/Tooltip'; +import { Tooltip as MyTooltip } from '@mui/material'; <Tooltip slots={{ @@ -63,3 +64,42 @@ import Tooltip from '@mui/material/Tooltip'; ...slotsTransitionProps } }} />; + +<Tooltip + slots={{ + popper: CustomPopper, + transition: CustomTransition + }} + slotProps={{ + popper: { disablePortal: true }, + transition: { timeout: 200 } + }} />; + +<Tooltip + slotProps={{ + tooltip: { height: 20 }, + popper: { disablePortal: true }, + transition: { timeout: 200 } + }} + slots={{ + tooltip: "div", + popper: CustomPopper, + transition: CustomTransition + }} />; + +<MyTooltip + slots={{ + popper: CustomPopper, + transition: CustomTransition + }} + slotProps={{ + popper: { disablePortal: true }, + transition: { timeout: 200 } + }} />; + +<CustomTooltip + PopperComponent={CustomPopper} + TransitionComponent={CustomTransition} + PopperProps={{ disablePortal: true }} + TransitionProps={{ timeout: 200 }} +/> diff --git a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.actual.js index d6933e8bc29102..6fd591c423774d 100644 --- a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.actual.js +++ b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.actual.js @@ -39,3 +39,30 @@ fn({ }, }, }); +fn({ + MuiTooltip: { + defaultProps: { + PopperComponent: CustomPopper, + TransitionComponent: CustomTransition, + PopperProps: { disablePortal: true }, + TransitionProps: { timeout: 200 }, + }, + }, +}); + +fn({ + MuiTooltip: { + defaultProps: { + PopperComponent: CustomPopper, + TransitionComponent: CustomTransition, + PopperProps: { disablePortal: true }, + TransitionProps: { timeout: 200 }, + slotProps: { + tooltip: { height: 20 } + }, + slots: { + tooltip: "div", + }, + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.expected.js index ad6b795e2c4d53..8d077ba5aa033d 100644 --- a/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.expected.js +++ b/packages/mui-codemod/src/deprecations/tooltip-props/test-cases/theme.expected.js @@ -70,3 +70,36 @@ fn({ }, }, }); +fn({ + MuiTooltip: { + defaultProps: { + slots: { + popper: CustomPopper, + transition: CustomTransition + }, + + slotProps: { + popper: { disablePortal: true }, + transition: { timeout: 200 } + } + }, + }, +}); + +fn({ + MuiTooltip: { + defaultProps: { + slotProps: { + tooltip: { height: 20 }, + popper: { disablePortal: true }, + transition: { timeout: 200 } + }, + + slots: { + tooltip: "div", + popper: CustomPopper, + transition: CustomTransition + } + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/tooltip-props/tooltip-props.js b/packages/mui-codemod/src/deprecations/tooltip-props/tooltip-props.js index feb7e35348d69b..fc9af0dcd84939 100644 --- a/packages/mui-codemod/src/deprecations/tooltip-props/tooltip-props.js +++ b/packages/mui-codemod/src/deprecations/tooltip-props/tooltip-props.js @@ -1,4 +1,6 @@ import replaceComponentsWithSlots from '../utils/replaceComponentsWithSlots'; +import movePropIntoSlots from '../utils/movePropIntoSlots'; +import movePropIntoSlotProps from '../utils/movePropIntoSlotProps'; /** * @param {import('jscodeshift').FileInfo} file @@ -11,5 +13,33 @@ export default function transformer(file, api, options) { replaceComponentsWithSlots(j, { root, componentName: 'Tooltip' }); + movePropIntoSlots(j, { + root, + componentName: 'Tooltip', + propName: 'PopperComponent', + slotName: 'popper', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'Tooltip', + propName: 'PopperProps', + slotName: 'popper', + }); + + movePropIntoSlots(j, { + root, + componentName: 'Tooltip', + propName: 'TransitionComponent', + slotName: 'transition', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'Tooltip', + propName: 'TransitionProps', + slotName: 'transition', + }); + return root.toSource(printOptions); } diff --git a/packages/mui-material/src/Tooltip/Tooltip.d.ts b/packages/mui-material/src/Tooltip/Tooltip.d.ts index fab6531ac41d63..ea3d996befec52 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.d.ts +++ b/packages/mui-material/src/Tooltip/Tooltip.d.ts @@ -2,12 +2,71 @@ import * as React from 'react'; import { MUIStyledCommonProps, SxProps } from '@mui/system'; import { PopperProps } from '@mui/material/Popper'; import { InternalStandardProps as StandardProps, Theme } from '..'; +import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; import { TransitionProps } from '../transitions/transition'; import { TooltipClasses } from './tooltipClasses'; export interface TooltipComponentsPropsOverrides {} -export interface TooltipProps extends StandardProps<React.HTMLAttributes<HTMLDivElement>, 'title'> { +export interface TooltipPopperSlotPropsOverrides {} + +export interface TooltipTransitionSlotPropsOverrides {} + +export interface TooltipTooltipSlotPropsOverrides {} + +export interface TooltipArrowSlotPropsOverrides {} + +export interface TooltipSlots { + /** + * The component used for the popper. + * @default Popper + */ + popper: React.ElementType<PopperProps>; + /** + * 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. + * @default Grow + */ + transition: React.ElementType; + /** + * The component used for the tooltip. + */ + tooltip: React.ElementType; + /** + * The component used for the arrow. + */ + arrow: React.ElementType; +} + +export type TooltipSlotsAndSlotProps = CreateSlotsAndSlotProps< + TooltipSlots, + { + popper: SlotProps< + React.ElementType<PopperProps>, + TooltipPopperSlotPropsOverrides, + TooltipOwnerState + >; + transition: SlotProps< + React.ElementType<TransitionProps>, + TooltipTransitionSlotPropsOverrides, + TooltipOwnerState + >; + tooltip: SlotProps< + React.ElementType<React.HTMLProps<HTMLDivElement>>, + TooltipTooltipSlotPropsOverrides, + TooltipOwnerState + >; + arrow: SlotProps< + React.ElementType<React.HTMLProps<HTMLSpanElement>>, + TooltipArrowSlotPropsOverrides, + TooltipOwnerState + >; + } +>; + +export interface TooltipProps + extends StandardProps<React.HTMLAttributes<HTMLDivElement>, 'title'>, + TooltipSlotsAndSlotProps { /** * If `true`, adds an arrow to the tooltip. * @default false @@ -24,7 +83,7 @@ export interface TooltipProps extends StandardProps<React.HTMLAttributes<HTMLDiv /** * The components used for each slot inside. * - * @deprecated use the `slots` prop instead. This prop will be removed in v7. [How to migrate](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/). + * @deprecated use the `slots` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. * * @default {} */ @@ -38,7 +97,7 @@ export interface TooltipProps extends StandardProps<React.HTMLAttributes<HTMLDiv * The extra props for the slot components. * You can override the existing props or add new ones. * - * @deprecated use the `slotProps` prop instead. This prop will be removed in v7. [How to migrate](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/). + * @deprecated use the `slotProps` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. * * @default {} */ @@ -151,45 +210,15 @@ export interface TooltipProps extends StandardProps<React.HTMLAttributes<HTMLDiv | 'top'; /** * The component used for the popper. - * @default Popper + * @deprecated use the `slots.popper` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ PopperComponent?: React.JSXElementConstructor<PopperProps>; /** * Props applied to the [`Popper`](https://mui.com/material-ui/api/popper/) element. + * @deprecated use the `slotProps.popper` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. * @default {} */ PopperProps?: Partial<PopperProps>; - /** - * 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?: { - popper?: Partial<PopperProps> & TooltipComponentsPropsOverrides; - transition?: TransitionProps & TooltipComponentsPropsOverrides; - tooltip?: React.HTMLProps<HTMLDivElement> & - MUIStyledCommonProps<Theme> & - TooltipComponentsPropsOverrides; - arrow?: React.HTMLProps<HTMLSpanElement> & - MUIStyledCommonProps<Theme> & - TooltipComponentsPropsOverrides; - }; - /** - * 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?: { - popper?: React.ElementType<PopperProps>; - transition?: React.ElementType; - tooltip?: React.ElementType; - arrow?: React.ElementType; - }; /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -201,7 +230,7 @@ export interface TooltipProps extends StandardProps<React.HTMLAttributes<HTMLDiv /** * 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. - * @default Grow + * @deprecated use the `slots.transition` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ TransitionComponent?: React.JSXElementConstructor< TransitionProps & { children: React.ReactElement<unknown, any> } @@ -209,10 +238,14 @@ export interface TooltipProps extends StandardProps<React.HTMLAttributes<HTMLDiv /** * 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 the `slotProps.transition` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. + * @default {} */ TransitionProps?: TransitionProps; } +export interface TooltipOwnerState extends TooltipProps {} + /** * * Demos: diff --git a/packages/mui-material/src/Tooltip/Tooltip.js b/packages/mui-material/src/Tooltip/Tooltip.js index fa067ab0558c9c..289bf8a0a47f07 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.js +++ b/packages/mui-material/src/Tooltip/Tooltip.js @@ -8,7 +8,6 @@ import composeClasses from '@mui/utils/composeClasses'; import { alpha } from '@mui/system/colorManipulator'; import { useRtl } from '@mui/system/RtlProvider'; import isFocusVisible from '@mui/utils/isFocusVisible'; -import appendOwnerState from '@mui/utils/appendOwnerState'; import getReactElementRef from '@mui/utils/getReactElementRef'; import { styled, useTheme } from '../zero-styled'; import memoTheme from '../utils/memoTheme'; @@ -20,6 +19,7 @@ import useEventCallback from '../utils/useEventCallback'; import useForkRef from '../utils/useForkRef'; import useId from '../utils/useId'; import useControlled from '../utils/useControlled'; +import useSlot from '../utils/useSlot'; import tooltipClasses, { getTooltipUtilityClass } from './tooltipClasses'; function round(value) { @@ -340,7 +340,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { slotProps = {}, slots = {}, title, - TransitionComponent: TransitionComponentProp = Grow, + TransitionComponent: TransitionComponentProp, TransitionProps, ...other } = props; @@ -653,6 +653,18 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { } } + const ownerState = { + ...props, + isRtl, + arrow, + disableInteractive, + placement, + PopperComponentProp, + touch: ignoreNonTouchEvents.current, + }; + + const resolvedPopperProps = + typeof slotProps.popper === 'function' ? slotProps.popper(ownerState) : slotProps.popper; const popperOptions = React.useMemo(() => { let tooltipModifiers = [ { @@ -669,72 +681,73 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { tooltipModifiers = tooltipModifiers.concat(PopperProps.popperOptions.modifiers); } + if (resolvedPopperProps?.popperOptions?.modifiers) { + tooltipModifiers = tooltipModifiers.concat(resolvedPopperProps.popperOptions.modifiers); + } + return { ...PopperProps.popperOptions, + ...resolvedPopperProps?.popperOptions, modifiers: tooltipModifiers, }; - }, [arrowRef, PopperProps]); - - const ownerState = { - ...props, - isRtl, - arrow, - disableInteractive, - placement, - PopperComponentProp, - touch: ignoreNonTouchEvents.current, - }; + }, [arrowRef, PopperProps.popperOptions, resolvedPopperProps?.popperOptions]); const classes = useUtilityClasses(ownerState); - - const PopperComponent = slots.popper ?? components.Popper ?? TooltipPopper; - const TransitionComponent = - slots.transition ?? components.Transition ?? TransitionComponentProp ?? Grow; - const TooltipComponent = slots.tooltip ?? components.Tooltip ?? TooltipTooltip; - const ArrowComponent = slots.arrow ?? components.Arrow ?? TooltipArrow; - - const popperProps = appendOwnerState( - PopperComponent, - { - ...PopperProps, - ...(slotProps.popper ?? componentsProps.popper), - className: clsx( - classes.popper, - PopperProps?.className, - (slotProps.popper ?? componentsProps.popper)?.className, - ), + const resolvedTransitionProps = + typeof slotProps.transition === 'function' + ? slotProps.transition(ownerState) + : slotProps.transition; + const externalForwardedProps = { + slots: { + popper: components.Popper, + transition: components.Transition ?? TransitionComponentProp, + tooltip: components.Tooltip, + arrow: components.Arrow, + ...slots, + }, + slotProps: { + arrow: slotProps.arrow ?? componentsProps.arrow, + popper: { ...PopperProps, ...(resolvedPopperProps ?? componentsProps.popper) }, // resolvedPopperProps can be spread because it's already an object + tooltip: slotProps.tooltip ?? componentsProps.tooltip, + transition: { + ...TransitionProps, + ...(resolvedTransitionProps ?? componentsProps.transition), + }, }, + }; + + const [PopperSlot, popperSlotProps] = useSlot('popper', { + elementType: TooltipPopper, + externalForwardedProps, ownerState, - ); + className: clsx(classes.popper, PopperProps?.className), + }); - const transitionProps = appendOwnerState( - TransitionComponent, - { ...TransitionProps, ...(slotProps.transition ?? componentsProps.transition) }, + const [TransitionSlot, transitionSlotProps] = useSlot('transition', { + elementType: Grow, + externalForwardedProps, ownerState, - ); + }); - const tooltipProps = appendOwnerState( - TooltipComponent, - { - ...(slotProps.tooltip ?? componentsProps.tooltip), - className: clsx(classes.tooltip, (slotProps.tooltip ?? componentsProps.tooltip)?.className), - }, + const [TooltipSlot, tooltipSlotProps] = useSlot('tooltip', { + elementType: TooltipTooltip, + className: classes.tooltip, + externalForwardedProps, ownerState, - ); + }); - const tooltipArrowProps = appendOwnerState( - ArrowComponent, - { - ...(slotProps.arrow ?? componentsProps.arrow), - className: clsx(classes.arrow, (slotProps.arrow ?? componentsProps.arrow)?.className), - }, + const [ArrowSlot, arrowSlotProps] = useSlot('arrow', { + elementType: TooltipArrow, + className: classes.arrow, + externalForwardedProps, ownerState, - ); + ref: setArrowRef, + }); return ( <React.Fragment> {React.cloneElement(children, childrenProps)} - <PopperComponent + <PopperSlot as={PopperComponentProp ?? Popper} placement={placement} anchorEl={ @@ -756,22 +769,22 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { id={id} transition {...interactiveWrapperListeners} - {...popperProps} + {...popperSlotProps} popperOptions={popperOptions} > {({ TransitionProps: TransitionPropsInner }) => ( - <TransitionComponent + <TransitionSlot timeout={theme.transitions.duration.shorter} {...TransitionPropsInner} - {...transitionProps} + {...transitionSlotProps} > - <TooltipComponent {...tooltipProps}> + <TooltipSlot {...tooltipSlotProps}> {title} - {arrow ? <ArrowComponent {...tooltipArrowProps} ref={setArrowRef} /> : null} - </TooltipComponent> - </TransitionComponent> + {arrow ? <ArrowSlot {...arrowSlotProps} /> : null} + </TooltipSlot> + </TransitionSlot> )} - </PopperComponent> + </PopperSlot> </React.Fragment> ); }); @@ -801,7 +814,7 @@ Tooltip.propTypes /* remove-proptypes */ = { /** * The components used for each slot inside. * - * @deprecated use the `slots` prop instead. This prop will be removed in v7. [How to migrate](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/). + * @deprecated use the `slots` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. * * @default {} */ @@ -815,7 +828,7 @@ Tooltip.propTypes /* remove-proptypes */ = { * The extra props for the slot components. * You can override the existing props or add new ones. * - * @deprecated use the `slotProps` prop instead. This prop will be removed in v7. [How to migrate](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/). + * @deprecated use the `slotProps` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. * * @default {} */ @@ -925,33 +938,27 @@ Tooltip.propTypes /* remove-proptypes */ = { ]), /** * The component used for the popper. - * @default Popper + * @deprecated use the `slots.popper` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ PopperComponent: PropTypes.elementType, /** * Props applied to the [`Popper`](https://mui.com/material-ui/api/popper/) element. + * @deprecated use the `slotProps.popper` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. * @default {} */ PopperProps: PropTypes.object, /** - * 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({ - arrow: PropTypes.object, - popper: PropTypes.object, - tooltip: PropTypes.object, - transition: PropTypes.object, + arrow: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + popper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + tooltip: 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({ @@ -975,12 +982,14 @@ Tooltip.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. - * @default Grow + * @deprecated use the `slots.transition` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ TransitionComponent: PropTypes.elementType, /** * 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 the `slotProps.transition` prop instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details. + * @default {} */ TransitionProps: PropTypes.object, }; diff --git a/packages/mui-material/src/Tooltip/Tooltip.spec.tsx b/packages/mui-material/src/Tooltip/Tooltip.spec.tsx new file mode 100644 index 00000000000000..1611f1b1ef5b63 --- /dev/null +++ b/packages/mui-material/src/Tooltip/Tooltip.spec.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import Tooltip from '@mui/material/Tooltip'; +import Popper from '@mui/material/Popper'; + +<Tooltip title="Hello"> + <button type="button">Hover or touch me</button> +</Tooltip>; + +<Tooltip + title="Hello" + slots={{ + popper: Popper, + arrow: 'span', + tooltip: 'div', + transition: 'div', + }} + slotProps={{ + popper: { + modifiers: [ + { + name: 'offset', + options: { + offset: [0, 8], + }, + }, + ], + }, + arrow: { + 'aria-hidden': true, + }, + tooltip: {}, + transition: { + timeout: 500, + }, + }} +> + <button type="button">Hover or touch me</button> +</Tooltip>; diff --git a/packages/mui-material/src/Tooltip/Tooltip.test.js b/packages/mui-material/src/Tooltip/Tooltip.test.js index 0a4d51d7a2fa39..4092e11be4b885 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.test.js +++ b/packages/mui-material/src/Tooltip/Tooltip.test.js @@ -32,6 +32,10 @@ describe('<Tooltip />', () => { ); } + const TestTooltipSlot = React.forwardRef(function TestTooltipSlot({ ownerState, ...props }, ref) { + return <div data-testid="custom" ref={ref} {...props} />; + }); + describeConformance( <Tooltip title="Hello World" arrow open> <button type="submit">Hello World</button> @@ -52,15 +56,14 @@ describe('<Tooltip />', () => { testWithElement: null, }, transition: { testWithElement: null }, - tooltip: { expectedClassName: classes.tooltip, testWithElement: null }, + tooltip: { + expectedClassName: classes.tooltip, + testWithComponent: TestTooltipSlot, + testWithElement: TestTooltipSlot, + }, arrow: { expectedClassName: classes.arrow }, }, - skip: [ - 'componentProp', - 'componentsProp', - 'themeVariants', - 'slotPropsCallback', // not supported yet - ], + skip: ['componentProp', 'componentsProp', 'themeVariants'], }), ); @@ -862,6 +865,82 @@ describe('<Tooltip />', () => { }); }); + describe('prop: slotProps.popper', () => { + it('should merge popperOptions with arrow modifier', () => { + const popperRef = React.createRef(); + render( + <Tooltip + title="Hello World" + open + arrow + slotProps={{ + popper: { + popperRef, + popperOptions: { + modifiers: [ + { + name: 'arrow', + options: { + padding: 8, + }, + }, + ], + }, + }, + }} + > + <button id="testChild" type="submit"> + Hello World + </button> + </Tooltip>, + ); + + const appliedArrowModifier = popperRef.current.state.orderedModifiers.find( + (modifier) => modifier.name === 'arrow', + ); + + expect(appliedArrowModifier).not.to.equal(undefined); + expect(appliedArrowModifier.enabled).to.equal(true); + expect(appliedArrowModifier.options.padding).to.equal(8); + }); + + it('should merge popperOptions with custom modifier', () => { + const popperRef = React.createRef(); + render( + <Tooltip + title="Hello World" + open + arrow + slotProps={{ + popper: { + popperRef, + popperOptions: { + modifiers: [ + { + name: 'foo', + enabled: true, + phase: 'main', + fn: () => {}, + }, + ], + }, + }, + }} + > + <button id="testChild" type="submit"> + Hello World + </button> + </Tooltip>, + ); + + const appliedComputeStylesModifier = popperRef.current.state.orderedModifiers.find( + (modifier) => modifier.name === 'foo', + ); + + expect(appliedComputeStylesModifier).not.to.equal(undefined); + }); + }); + describe('prop forwarding', () => { it('should forward props to the child element', () => { const { getByText } = render( @@ -1247,6 +1326,50 @@ describe('<Tooltip />', () => { }); }); + describe('prop: slots', () => { + it('can render a different Popper component', () => { + function CustomPopper() { + return <div data-testid="CustomPopper" />; + } + render( + <Tooltip title="Hello World" open slots={{ popper: CustomPopper }}> + <button id="testChild" type="submit"> + Hello World + </button> + </Tooltip>, + ); + expect(screen.getByTestId('CustomPopper')).toBeVisible(); + }); + + it('can render a different Tooltip component', () => { + const CustomTooltip = React.forwardRef((props, ref) => ( + <div data-testid="CustomTooltip" ref={ref} /> + )); + render( + <Tooltip title="Hello World" open slots={{ tooltip: CustomTooltip }}> + <button id="testChild" type="submit"> + Hello World + </button> + </Tooltip>, + ); + expect(screen.getByTestId('CustomTooltip')).toBeVisible(); + }); + + it('can render a different Arrow component', () => { + const CustomArrow = React.forwardRef((props, ref) => ( + <div data-testid="CustomArrow" ref={ref} /> + )); + render( + <Tooltip title="Hello World" open arrow slots={{ arrow: CustomArrow }}> + <button id="testChild" type="submit"> + Hello World + </button> + </Tooltip>, + ); + expect(screen.getByTestId('CustomArrow')).toBeVisible(); + }); + }); + describe('user-select state', () => { let prevWebkitUserSelect;