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 9055f6e969e514..d297d2952cc8d1 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 @@ -2230,3 +2230,65 @@ The SpeedDial's `TransitionProps` prop was deprecated in favor of `slotProps.tra + slotProps={{ transition: { unmountOnExit: true } }} /> ``` + +## SpeedDialAction + +Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#speed-dial-action-props) below to migrate the code as described in the following sections: + +```bash +npx @mui/codemod@latest deprecations/speed-dial-action-props +``` + +### FabProps + +The SpeedDialAction's `FabProps` prop was deprecated in favor of `slotProps.fab`: + +```diff + +``` + +### tooltipPlacement + +The SpeedDialAction's `tooltipPlacement` prop was deprecated in favor of `slotProps.tooltip.placement`: + +```diff + +``` + +### tooltipTitle + +The SpeedDialAction's `tooltipTitle` prop was deprecated in favor of `slotProps.tooltip.title`: + +```diff + +``` + +### tooltipOpen + +The SpeedDialAction's `tooltipOpen` prop was deprecated in favor of `slotProps.tooltip.open`: + +```diff + +``` diff --git a/docs/pages/material-ui/api/speed-dial-action.json b/docs/pages/material-ui/api/speed-dial-action.json index 59ecde42e50284..c22d0e936c6d5f 100644 --- a/docs/pages/material-ui/api/speed-dial-action.json +++ b/docs/pages/material-ui/api/speed-dial-action.json @@ -2,10 +2,29 @@ "props": { "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, "delay": { "type": { "name": "number" }, "default": "0" }, - "FabProps": { "type": { "name": "object" }, "default": "{}" }, + "FabProps": { + "type": { "name": "object" }, + "default": "{}", + "deprecated": true, + "deprecationInfo": "Use slotProps.fab instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, "icon": { "type": { "name": "node" } }, "id": { "type": { "name": "string" } }, "open": { "type": { "name": "bool" } }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ fab?: func
| object, staticTooltip?: func
| object, staticTooltipLabel?: func
| object, tooltip?: func
| object }" + }, + "default": "{}" + }, + "slots": { + "type": { + "name": "shape", + "description": "{ fab?: elementType, staticTooltip?: elementType, staticTooltipLabel?: elementType, tooltip?: elementType }" + }, + "default": "{}" + }, "sx": { "type": { "name": "union", @@ -13,53 +32,76 @@ }, "additionalInfo": { "sx": true } }, - "TooltipClasses": { "type": { "name": "object" } }, - "tooltipOpen": { "type": { "name": "bool" }, "default": "false" }, + "TooltipClasses": { + "type": { "name": "object" }, + "deprecated": true, + "deprecationInfo": "Use slotProps.tooltip.classes instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, + "tooltipOpen": { + "type": { "name": "bool" }, + "default": "false", + "deprecated": true, + "deprecationInfo": "Use slotProps.tooltip.open instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + }, "tooltipPlacement": { "type": { "name": "enum", "description": "'bottom-end'
| 'bottom-start'
| 'bottom'
| 'left-end'
| 'left-start'
| 'left'
| 'right-end'
| 'right-start'
| 'right'
| 'top-end'
| 'top-start'
| 'top'" }, - "default": "'left'" + "default": "'left'", + "deprecated": true, + "deprecationInfo": "Use slotProps.tooltip.placement instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." }, - "tooltipTitle": { "type": { "name": "node" } } + "tooltipTitle": { + "type": { "name": "node" }, + "deprecated": true, + "deprecationInfo": "Use slotProps.tooltip.title instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." + } }, "name": "SpeedDialAction", "imports": [ "import SpeedDialAction from '@mui/material/SpeedDialAction';", "import { SpeedDialAction } from '@mui/material';" ], - "classes": [ + "slots": [ { - "key": "fab", - "className": "MuiSpeedDialAction-fab", - "description": "Styles applied to the Fab component.", - "isGlobal": false + "name": "fab", + "description": "The component that renders the fab.", + "default": "Fab", + "class": "MuiSpeedDialAction-fab" + }, + { + "name": "tooltip", + "description": "The component that renders the tooltip.", + "default": "Tooltip", + "class": null + }, + { + "name": "staticTooltip", + "description": "The component that renders the static tooltip.", + "default": "'span'", + "class": "MuiSpeedDialAction-staticTooltip" }, + { + "name": "staticTooltipLabel", + "description": "The component that renders the static tooltip label.", + "default": "'span'", + "class": "MuiSpeedDialAction-staticTooltipLabel" + } + ], + "classes": [ { "key": "fabClosed", "className": "MuiSpeedDialAction-fabClosed", "description": "Styles applied to the Fab component if `open={false}`.", "isGlobal": false }, - { - "key": "staticTooltip", - "className": "MuiSpeedDialAction-staticTooltip", - "description": "Styles applied to the root element if `tooltipOpen={true}`.", - "isGlobal": false - }, { "key": "staticTooltipClosed", "className": "MuiSpeedDialAction-staticTooltipClosed", "description": "Styles applied to the root element if `tooltipOpen={true}` and `open={false}`.", "isGlobal": false }, - { - "key": "staticTooltipLabel", - "className": "MuiSpeedDialAction-staticTooltipLabel", - "description": "Styles applied to the static tooltip label if `tooltipOpen={true}`.", - "isGlobal": false - }, { "key": "tooltipPlacementLeft", "className": "MuiSpeedDialAction-tooltipPlacementLeft", diff --git a/docs/translations/api-docs/speed-dial-action/speed-dial-action.json b/docs/translations/api-docs/speed-dial-action/speed-dial-action.json index 8f13c834d2c1f0..114ecec29d8087 100644 --- a/docs/translations/api-docs/speed-dial-action/speed-dial-action.json +++ b/docs/translations/api-docs/speed-dial-action/speed-dial-action.json @@ -13,6 +13,8 @@ "description": "This prop is used to help implement the accessibility logic. If you don't provide this prop. It falls back to a randomly generated id." }, "open": { "description": "If true, the component is shown." }, + "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." }, @@ -24,32 +26,27 @@ "tooltipTitle": { "description": "Label to display in the tooltip." } }, "classDescriptions": { - "fab": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the Fab component" }, "fabClosed": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the Fab component", "conditions": "open={false}" }, - "staticTooltip": { - "description": "Styles applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the root element", - "conditions": "tooltipOpen={true}" - }, "staticTooltipClosed": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the root element", "conditions": "tooltipOpen={true} and open={false}" }, - "staticTooltipLabel": { - "description": "Styles applied to {{nodeName}} if {{conditions}}.", - "nodeName": "the static tooltip label", - "conditions": "tooltipOpen={true}" - }, "tooltipPlacementLeft": { "description": "Styles applied to the root element if tooltipOpen={true} and `tooltipPlacement="left"``" }, "tooltipPlacementRight": { "description": "Styles applied to the root element if tooltipOpen={true} and `tooltipPlacement="right"``" } + }, + "slotDescriptions": { + "fab": "The component that renders the fab.", + "staticTooltip": "The component that renders the static tooltip.", + "staticTooltipLabel": "The component that renders the static tooltip label.", + "tooltip": "The component that renders the tooltip." } } diff --git a/packages/mui-codemod/src/deprecations/speed-dial-action-props/index.js b/packages/mui-codemod/src/deprecations/speed-dial-action-props/index.js new file mode 100644 index 00000000000000..3c79488598e100 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/speed-dial-action-props/index.js @@ -0,0 +1 @@ +export { default } from './speed-dial-action-props'; diff --git a/packages/mui-codemod/src/deprecations/speed-dial-action-props/speed-dial-action-props.js b/packages/mui-codemod/src/deprecations/speed-dial-action-props/speed-dial-action-props.js new file mode 100644 index 00000000000000..797ec9ef98d1ee --- /dev/null +++ b/packages/mui-codemod/src/deprecations/speed-dial-action-props/speed-dial-action-props.js @@ -0,0 +1,52 @@ +import movePropIntoSlotProps from '../utils/movePropIntoSlotProps'; + +/** + * @param {import('jscodeshift').FileInfo} file + * @param {import('jscodeshift').API} api + */ +export default function transformer(file, api, options) { + const j = api.jscodeshift; + const root = j(file.source); + const printOptions = options.printOptions; + + movePropIntoSlotProps(j, { + root, + componentName: 'SpeedDialAction', + propName: 'tooltipTitle', + slotName: 'tooltip', + slotPropName: 'title', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'SpeedDialAction', + propName: 'tooltipPlacement', + slotName: 'tooltip', + slotPropName: 'placement', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'SpeedDialAction', + propName: 'tooltipOpen', + slotName: 'tooltip', + slotPropName: 'open', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'SpeedDialAction', + propName: 'TooltipClasses', + slotName: 'tooltip', + slotPropName: 'classes', + }); + + movePropIntoSlotProps(j, { + root, + componentName: 'SpeedDialAction', + propName: 'FabProps', + slotName: 'fab', + }); + + return root.toSource(printOptions); +} diff --git a/packages/mui-codemod/src/deprecations/speed-dial-action-props/speed-dial-action-props.test.js b/packages/mui-codemod/src/deprecations/speed-dial-action-props/speed-dial-action-props.test.js new file mode 100644 index 00000000000000..00877e25da9763 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/speed-dial-action-props/speed-dial-action-props.test.js @@ -0,0 +1,16 @@ +import { describeJscodeshiftTransform } from '../../../testUtils'; +import transform from './speed-dial-action-props'; + +describe('@mui/codemod', () => { + describe('deprecations', () => { + describeJscodeshiftTransform({ + transform, + transformName: 'speed-dial-action-props', + dirname: __dirname, + testCases: [ + { actual: '/test-cases/actual.js', expected: '/test-cases/expected.js' }, + { actual: '/test-cases/theme.actual.js', expected: '/test-cases/theme.expected.js' }, + ], + }); + }); +}); diff --git a/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/actual.js new file mode 100644 index 00000000000000..cca5167dec00d5 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/actual.js @@ -0,0 +1,33 @@ +import SpeedDialAction from '@mui/material/SpeedDialAction'; +import { SpeedDialAction as MySpeedDialAction } from '@mui/material'; + +; +; +; +; +; +; +; +; +; +; +; +; +; diff --git a/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/expected.js new file mode 100644 index 00000000000000..851da44d062610 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/expected.js @@ -0,0 +1,80 @@ +import SpeedDialAction from '@mui/material/SpeedDialAction'; +import { SpeedDialAction as MySpeedDialAction } from '@mui/material'; + +; +; +; +; +; +; +; +; +; +; +; +; +; diff --git a/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/theme.actual.js new file mode 100644 index 00000000000000..288d18ba36c140 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/theme.actual.js @@ -0,0 +1,66 @@ +fn({ + MuiSpeedDialAction: { + defaultProps: { + FabProps: { + id: 'test', + }, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + TooltipClasses: classes, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + tooltipOpen: true, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + tooltipPlacement: 'top', + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + tooltipTitle: 'test', + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + FabProps: { + id: 'test', + }, + TooltipClasses: classes, + tooltipOpen: true, + tooltipPlacement: 'top', + tooltipTitle: 'test', + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + TooltipClasses: classes, + tooltipOpen: true, + tooltipPlacement: 'top', + tooltipTitle: 'test', + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/theme.expected.js new file mode 100644 index 00000000000000..f1899b23c21d5e --- /dev/null +++ b/packages/mui-codemod/src/deprecations/speed-dial-action-props/test-cases/theme.expected.js @@ -0,0 +1,93 @@ +fn({ + MuiSpeedDialAction: { + defaultProps: { + slotProps: { + fab: { + id: 'test', + } + }, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + slotProps: { + tooltip: { + classes: classes + } + }, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + slotProps: { + tooltip: { + open: true + } + }, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + slotProps: { + tooltip: { + placement: 'top' + } + }, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + slotProps: { + tooltip: { + title: 'test' + } + }, + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + slotProps: { + tooltip: { + title: 'test', + placement: 'top', + open: true, + classes: classes + }, + + fab: { + id: 'test', + } + } + }, + }, +}); + +fn({ + MuiSpeedDialAction: { + defaultProps: { + slotProps: { + tooltip: { + title: 'test', + placement: 'top', + open: true, + classes: classes + } + } + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/utils/movePropIntoSlotProps.js b/packages/mui-codemod/src/deprecations/utils/movePropIntoSlotProps.js index 2e09e3d1d021cc..75ef17af96a032 100644 --- a/packages/mui-codemod/src/deprecations/utils/movePropIntoSlotProps.js +++ b/packages/mui-codemod/src/deprecations/utils/movePropIntoSlotProps.js @@ -3,50 +3,81 @@ import findComponentDefaultProps from '../../util/findComponentDefaultProps'; import assignObject from '../../util/assignObject'; import appendAttribute from '../../util/appendAttribute'; -function moveJsxPropIntoSlotProps(j, element, propName, slotName) { +function moveJsxPropIntoSlotProps(j, element, propName, slotName, slotPropName) { const propIndex = element.openingElement.attributes.findIndex( (attr) => attr.type === 'JSXAttribute' && attr.name.name === propName, ); if (propIndex !== -1) { - const removedValue = element.openingElement.attributes.splice(propIndex, 1)[0].value.expression; + const removedAttr = element.openingElement.attributes.splice(propIndex, 1)[0]; + const removedValue = + removedAttr.value.type === 'StringLiteral' + ? j.literal(removedAttr.value.value) + : removedAttr.value.expression; + let hasSlotProps = false; element.openingElement.attributes.forEach((attr) => { if (attr.name?.name === 'slotProps') { hasSlotProps = true; const slots = attr.value.expression; const slotIndex = slots.properties.findIndex((prop) => prop?.key?.name === slotName); + if (slotIndex === -1) { + // Create new slot + const slotValue = slotPropName + ? j.objectExpression([j.objectProperty(j.identifier(slotPropName), removedValue)]) + : removedValue; + assignObject(j, { target: attr, key: slotName, - expression: removedValue, + expression: slotValue, }); } else { - const slotPropsSlotValue = slots.properties.splice(slotIndex, 1)[0].value; - assignObject(j, { - target: attr, - key: slotName, - expression: j.objectExpression([ + // Add property to existing slot + const existingSlot = slots.properties[slotIndex].value; + if (slotPropName) { + if (existingSlot.type === 'ObjectExpression') { + existingSlot.properties.push( + j.objectProperty(j.identifier(slotPropName), removedValue), + ); + } else { + slots.properties[slotIndex].value = j.objectExpression([ + j.objectProperty(j.identifier(slotPropName), removedValue), + ]); + } + } else { + slots.properties[slotIndex].value = j.objectExpression([ j.spreadElement(removedValue), - j.spreadElement(slotPropsSlotValue), - ]), - }); + j.spreadElement(existingSlot), + ]); + } } } }); if (!hasSlotProps) { + // Create new slotProps + const slotValue = slotPropName + ? j.objectExpression([j.objectProperty(j.identifier(slotPropName), removedValue)]) + : removedValue; + appendAttribute(j, { target: element, attributeName: 'slotProps', - expression: j.objectExpression([j.objectProperty(j.identifier(slotName), removedValue)]), + expression: j.objectExpression([j.objectProperty(j.identifier(slotName), slotValue)]), }); } } } -function moveDefaultPropsPropIntoslotProps(j, defaultPropsPathCollection, propName, slotName) { +function moveDefaultPropsPropIntoslotProps( + j, + defaultPropsPathCollection, + propName, + slotName, + slotPropName, +) { defaultPropsPathCollection.find(j.ObjectProperty, { key: { name: propName } }).forEach((path) => { const removedValue = path.value.value; const defaultProps = path.parent.value; @@ -59,27 +90,45 @@ function moveDefaultPropsPropIntoslotProps(j, defaultPropsPathCollection, propNa (prop) => prop?.key?.name === slotName, ); if (slotIndex === -1) { - property.value.properties.push(j.objectProperty(j.identifier(slotName), removedValue)); + // Create new slot + const slotValue = slotPropName + ? j.objectExpression([j.objectProperty(j.identifier(slotPropName), removedValue)]) + : removedValue; + + property.value.properties.push(j.objectProperty(j.identifier(slotName), slotValue)); } else { - const slotPropsSlotValue = property.value.properties.splice(slotIndex, 1)[0].value; - property.value.properties.push( - j.objectProperty( - j.identifier(slotName), - j.objectExpression([ - j.spreadElement(removedValue), - j.spreadElement(slotPropsSlotValue), - ]), - ), - ); + // Add property to existing slot + const existingSlot = property.value.properties[slotIndex].value; + if (slotPropName) { + if (existingSlot.type === 'ObjectExpression') { + existingSlot.properties.push( + j.objectProperty(j.identifier(slotPropName), removedValue), + ); + } else { + property.value.properties[slotIndex].value = j.objectExpression([ + j.objectProperty(j.identifier(slotPropName), removedValue), + ]); + } + } else { + property.value.properties[slotIndex].value = j.objectExpression([ + j.spreadElement(removedValue), + j.spreadElement(existingSlot), + ]); + } } } }); if (!hasSlotProps) { + // Create new slotProps + const slotValue = slotPropName + ? j.objectExpression([j.objectProperty(j.identifier(slotPropName), removedValue)]) + : removedValue; + defaultProps.properties.push( j.objectProperty( j.identifier('slotProps'), - j.objectExpression([j.objectProperty(j.identifier(slotName), removedValue)]), + j.objectExpression([j.objectProperty(j.identifier(slotName), slotValue)]), ), ); } @@ -94,18 +143,25 @@ function moveDefaultPropsPropIntoslotProps(j, defaultPropsPathCollection, propNa * If there are duplicated values, the values will be spread. * * @param {import('jscodeshift')} j - * @param {{ root: import('jscodeshift').Collection; componentName: string, propName: string, slotName: string }} options + * @param {{ root: import('jscodeshift').Collection; componentName: string, propName: string, slotName: string, slotPropName?: string }} options * * @example => + * @example => */ export default function movePropIntoSlotProps(j, options) { - const { root, componentName, propName, slotName } = options; + const { root, componentName, propName, slotName, slotPropName } = options; findComponentJSX(j, { root, componentName }, (elementPath) => { - moveJsxPropIntoSlotProps(j, elementPath.node, propName, slotName); + moveJsxPropIntoSlotProps(j, elementPath.node, propName, slotName, slotPropName); }); const defaultPropsPathCollection = findComponentDefaultProps(j, { root, componentName }); - moveDefaultPropsPropIntoslotProps(j, defaultPropsPathCollection, propName, slotName); + moveDefaultPropsPropIntoslotProps( + j, + defaultPropsPathCollection, + propName, + slotName, + slotPropName, + ); } diff --git a/packages/mui-material/src/SpeedDialAction/SpeedDialAction.d.ts b/packages/mui-material/src/SpeedDialAction/SpeedDialAction.d.ts index 78754f320b817f..f11962267c3de8 100644 --- a/packages/mui-material/src/SpeedDialAction/SpeedDialAction.d.ts +++ b/packages/mui-material/src/SpeedDialAction/SpeedDialAction.d.ts @@ -5,8 +5,81 @@ import { InternalStandardProps as StandardProps } from '..'; import { FabProps } from '../Fab'; import { TooltipProps } from '../Tooltip'; import { SpeedDialActionClasses } from './speedDialActionClasses'; +import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; -export interface SpeedDialActionProps extends StandardProps, 'children'> { +export interface SpeedDialActionSlots { + /** + * The component that renders the fab. + * @default Fab + */ + fab?: React.ElementType; + /** + * The component that renders the tooltip. + * @default Tooltip + */ + tooltip?: React.ElementType; + /** + * The component that renders the static tooltip. + * @default 'span' + */ + staticTooltip?: React.ElementType; + /** + * The component that renders the static tooltip label. + * @default 'span' + */ + staticTooltipLabel?: React.ElementType; +} + +export interface SpeedDialActionFabSlotPropsOverrides {} +export interface SpeedDialActionTooltipSlotPropsOverrides {} +export interface SpeedDialActionStaticTooltipSlotPropsOverrides {} +export interface SpeedDialActionStaticTooltipLabelSlotPropsOverrides {} + +export type SpeedDialActionSlotsAndSlotProps = CreateSlotsAndSlotProps< + SpeedDialActionSlots, + { + /** + * Props forwarded to the fab slot. + * By default, the avaible props are based on the [Fab](https://mui.com/material-ui/api/fab/#props) component. + */ + fab: SlotProps< + React.ElementType, + SpeedDialActionFabSlotPropsOverrides, + SpeedDialActionOwnerState + >; + /** + * Props forwarded to the tooltip slot. + * By default, the avaible props are based on the [Tooltip](https://mui.com/material-ui/api/tooltip/#props) component. + */ + tooltip: SlotProps< + React.ElementType, + SpeedDialActionTooltipSlotPropsOverrides, + SpeedDialActionOwnerState + >; + /** + * Props forwarded to the static tooltip slot. + * By default, the avaible props are based on a span element. + */ + staticTooltip: SlotProps< + 'span', + SpeedDialActionStaticTooltipSlotPropsOverrides, + SpeedDialActionOwnerState + >; + /** + * Props forwarded to the static tooltip label slot. + * By default, the avaible props are based on a span element. + */ + staticTooltipLabel: SlotProps< + 'span', + SpeedDialActionStaticTooltipLabelSlotPropsOverrides, + SpeedDialActionOwnerState + >; + } +>; + +export interface SpeedDialActionProps + extends Omit, 'children'>, 'slotProps' | 'slots'>, + SpeedDialActionSlotsAndSlotProps { /** * Override or extend the styles applied to the component. */ @@ -14,6 +87,7 @@ export interface SpeedDialActionProps extends StandardProps; /** @@ -31,20 +105,24 @@ export interface SpeedDialActionProps extends StandardProps; /** * `classes` prop applied to the [`Tooltip`](https://mui.com/material-ui/api/tooltip/) element. + * @deprecated Use `slotProps.tooltip.classes` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ TooltipClasses?: TooltipProps['classes']; /** * Placement of the tooltip. * @default 'left' + * @deprecated Use `slotProps.tooltip.placement` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ tooltipPlacement?: TooltipProps['placement']; /** * Label to display in the tooltip. + * @deprecated Use `slotProps.tooltip.title` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ tooltipTitle?: React.ReactNode; /** * Make the tooltip always visible when the SpeedDial is open. * @default false + * @deprecated Use `slotProps.tooltip.open` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ tooltipOpen?: boolean; } @@ -61,3 +139,6 @@ export interface SpeedDialActionProps extends StandardProps {} diff --git a/packages/mui-material/src/SpeedDialAction/SpeedDialAction.js b/packages/mui-material/src/SpeedDialAction/SpeedDialAction.js index 340633f5617e94..1242c456c5d8bc 100644 --- a/packages/mui-material/src/SpeedDialAction/SpeedDialAction.js +++ b/packages/mui-material/src/SpeedDialAction/SpeedDialAction.js @@ -12,6 +12,8 @@ import Fab from '../Fab'; import Tooltip from '../Tooltip'; import capitalize from '../utils/capitalize'; import speedDialActionClasses, { getSpeedDialActionUtilityClass } from './speedDialActionClasses'; +import useSlot from '../utils/useSlot'; +import { mergeSlotProps } from '../utils'; const useUtilityClasses = (ownerState) => { const { open, tooltipPlacement, classes } = ownerState; @@ -155,13 +157,34 @@ const SpeedDialAction = React.forwardRef(function SpeedDialAction(inProps, ref) tooltipOpen: tooltipOpenProp = false, tooltipPlacement = 'left', tooltipTitle, + slots = {}, + slotProps = {}, ...other } = props; const ownerState = { ...props, tooltipPlacement }; const classes = useUtilityClasses(ownerState); - const [tooltipOpen, setTooltipOpen] = React.useState(tooltipOpenProp); + const externalForwardedProps = { + slots, + slotProps: { + fab: FabProps, + ...slotProps, + tooltip: mergeSlotProps( + typeof slotProps.tooltip === 'function' ? slotProps.tooltip(ownerState) : slotProps.tooltip, + { + title: tooltipTitle, + open: tooltipOpenProp, + placement: tooltipPlacement, + classes: TooltipClasses, + }, + ), + }, + }; + + const [tooltipOpen, setTooltipOpen] = React.useState( + externalForwardedProps.slotProps.tooltip?.open, + ); const handleTooltipClose = () => { setTooltipOpen(false); @@ -173,44 +196,76 @@ const SpeedDialAction = React.forwardRef(function SpeedDialAction(inProps, ref) const transitionStyle = { transitionDelay: `${delay}ms` }; - const fab = ( - - {icon} - - ); + const [FabSlot, fabSlotProps] = useSlot('fab', { + elementType: SpeedDialActionFab, + externalForwardedProps, + ownerState, + shouldForwardComponentProp: true, + className: clsx(classes.fab, className), + additionalProps: { + style: transitionStyle, + tabIndex: -1, + role: 'menuitem', + size: 'small', + }, + }); + + const [TooltipSlot, tooltipSlotProps] = useSlot('tooltip', { + elementType: Tooltip, + externalForwardedProps, + shouldForwardComponentProp: true, + ref, + additionalProps: { + id, + }, + ownerState, + getSlotProps: (handlers) => ({ + ...handlers, + onClose: (event) => { + handlers.onClose?.(event); + handleTooltipClose(); + }, + onOpen: (event) => { + handlers.onOpen?.(event); + handleTooltipOpen(); + }, + }), + }); - if (tooltipOpenProp) { + const [StaticTooltipSlot, staticTooltipSlotProps] = useSlot('staticTooltip', { + elementType: SpeedDialActionStaticTooltip, + externalForwardedProps, + ownerState, + ref, + className: classes.staticTooltip, + additionalProps: { + id, + }, + }); + + const [StaticTooltipLabelSlot, staticTooltipLabelSlotProps] = useSlot('staticTooltipLabel', { + elementType: SpeedDialActionStaticTooltipLabel, + externalForwardedProps, + ownerState, + className: classes.staticTooltipLabel, + additionalProps: { + style: transitionStyle, + id: `${id}-label`, + }, + }); + + const fab = {icon}; + + if (tooltipSlotProps.open) { return ( - - - {tooltipTitle} - + + + {tooltipSlotProps.title} + {React.cloneElement(fab, { 'aria-labelledby': `${id}-label`, })} - + ); } @@ -219,19 +274,16 @@ const SpeedDialAction = React.forwardRef(function SpeedDialAction(inProps, ref) } return ( - {fab} - + ); }); @@ -256,6 +308,7 @@ SpeedDialAction.propTypes /* remove-proptypes */ = { /** * Props applied to the [`Fab`](https://mui.com/material-ui/api/fab/) component. * @default {} + * @deprecated Use `slotProps.fab` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ FabProps: PropTypes.object, /** @@ -271,6 +324,26 @@ SpeedDialAction.propTypes /* remove-proptypes */ = { * If `true`, the component is shown. */ open: PropTypes.bool, + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + fab: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + staticTooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + staticTooltipLabel: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + tooltip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + fab: PropTypes.elementType, + staticTooltip: PropTypes.elementType, + staticTooltipLabel: PropTypes.elementType, + tooltip: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -281,16 +354,19 @@ SpeedDialAction.propTypes /* remove-proptypes */ = { ]), /** * `classes` prop applied to the [`Tooltip`](https://mui.com/material-ui/api/tooltip/) element. + * @deprecated Use `slotProps.tooltip.classes` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ TooltipClasses: PropTypes.object, /** * Make the tooltip always visible when the SpeedDial is open. * @default false + * @deprecated Use `slotProps.tooltip.open` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ tooltipOpen: PropTypes.bool, /** * Placement of the tooltip. * @default 'left' + * @deprecated Use `slotProps.tooltip.placement` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ tooltipPlacement: PropTypes.oneOf([ 'bottom-end', @@ -308,6 +384,7 @@ SpeedDialAction.propTypes /* remove-proptypes */ = { ]), /** * Label to display in the tooltip. + * @deprecated Use `slotProps.tooltip.title` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */ tooltipTitle: PropTypes.node, }; diff --git a/packages/mui-material/src/SpeedDialAction/SpeedDialAction.test.js b/packages/mui-material/src/SpeedDialAction/SpeedDialAction.test.js index a0bf7d80df823f..9cf1e7f9a1eb9f 100644 --- a/packages/mui-material/src/SpeedDialAction/SpeedDialAction.test.js +++ b/packages/mui-material/src/SpeedDialAction/SpeedDialAction.test.js @@ -7,6 +7,14 @@ import { fabClasses } from '@mui/material/Fab'; import SpeedDialAction, { speedDialActionClasses as classes } from '@mui/material/SpeedDialAction'; import describeConformance from '../../test/describeConformance'; +const CustomButton = React.forwardRef(({ ownerState, ...props }, ref) => ( +