diff --git a/docs/pages/api-docs/speed-dial-action.json b/docs/pages/api-docs/speed-dial-action.json index 6c7a508c90e1cb..6ea304af1311c9 100644 --- a/docs/pages/api-docs/speed-dial-action.json +++ b/docs/pages/api-docs/speed-dial-action.json @@ -6,6 +6,7 @@ "icon": { "type": { "name": "node" } }, "id": { "type": { "name": "string" } }, "open": { "type": { "name": "bool" } }, + "sx": { "type": { "name": "object" } }, "TooltipClasses": { "type": { "name": "object" } }, "tooltipOpen": { "type": { "name": "bool" } }, "tooltipPlacement": { @@ -36,6 +37,6 @@ "filename": "/packages/material-ui/src/SpeedDialAction/SpeedDialAction.js", "inheritance": { "component": "Tooltip", "pathname": "/api/tooltip/" }, "demos": "", - "styledComponent": false, + "styledComponent": true, "cssComponent": false } diff --git a/docs/pages/api-docs/speed-dial-icon.json b/docs/pages/api-docs/speed-dial-icon.json index e477af61435bf1..72c28346b293e0 100644 --- a/docs/pages/api-docs/speed-dial-icon.json +++ b/docs/pages/api-docs/speed-dial-icon.json @@ -2,7 +2,8 @@ "props": { "classes": { "type": { "name": "object" } }, "icon": { "type": { "name": "node" } }, - "openIcon": { "type": { "name": "node" } } + "openIcon": { "type": { "name": "node" } }, + "sx": { "type": { "name": "object" } } }, "name": "SpeedDialIcon", "styles": { @@ -15,6 +16,6 @@ "filename": "/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.js", "inheritance": null, "demos": "", - "styledComponent": false, + "styledComponent": true, "cssComponent": false } diff --git a/docs/pages/api-docs/speed-dial.json b/docs/pages/api-docs/speed-dial.json index 804c415d64e8dd..ab8e43aac6819a 100644 --- a/docs/pages/api-docs/speed-dial.json +++ b/docs/pages/api-docs/speed-dial.json @@ -17,6 +17,7 @@ "onOpen": { "type": { "name": "func" } }, "open": { "type": { "name": "bool" } }, "openIcon": { "type": { "name": "node" } }, + "sx": { "type": { "name": "object" } }, "TransitionComponent": { "type": { "name": "elementType" }, "default": "Zoom" }, "transitionDuration": { "type": { @@ -47,6 +48,6 @@ "filename": "/packages/material-ui/src/SpeedDial/SpeedDial.js", "inheritance": null, "demos": "", - "styledComponent": false, + "styledComponent": true, "cssComponent": false } 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 e72832fd29fe9d..9a276d7199877e 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 @@ -7,6 +7,7 @@ "icon": "The icon to display in the SpeedDial Fab.", "id": "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": "If true, the component is shown.", + "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "TooltipClasses": "classes prop applied to the Tooltip element.", "tooltipOpen": "Make the tooltip always visible when the SpeedDial is open.", "tooltipPlacement": "Placement of the tooltip.", diff --git a/docs/translations/api-docs/speed-dial-icon/speed-dial-icon.json b/docs/translations/api-docs/speed-dial-icon/speed-dial-icon.json index 3950ebb3809f15..e620a411ef6232 100644 --- a/docs/translations/api-docs/speed-dial-icon/speed-dial-icon.json +++ b/docs/translations/api-docs/speed-dial-icon/speed-dial-icon.json @@ -3,7 +3,8 @@ "propDescriptions": { "classes": "Override or extend the styles applied to the component. See CSS API below for more details.", "icon": "The icon to display.", - "openIcon": "The icon to display in the SpeedDial Floating Action Button when the SpeedDial is open." + "openIcon": "The icon to display in the SpeedDial Floating Action Button when the SpeedDial is open.", + "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details." }, "classDescriptions": { "root": { "description": "Styles applied to the root element." }, diff --git a/docs/translations/api-docs/speed-dial/speed-dial.json b/docs/translations/api-docs/speed-dial/speed-dial.json index c2ad427cce9584..226f09910dd595 100644 --- a/docs/translations/api-docs/speed-dial/speed-dial.json +++ b/docs/translations/api-docs/speed-dial/speed-dial.json @@ -12,6 +12,7 @@ "onOpen": "Callback fired when the component requests to be open.

Signature:
function(event: object, reason: string) => void
event: The event source of the callback.
reason: Can be: "toggle", "focus", "mouseEnter".", "open": "If true, the component is shown.", "openIcon": "The icon to display in the SpeedDial Fab when the SpeedDial is open.", + "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "TransitionComponent": "The component used for the transition. Follow this guide to learn more about the requirements for this component.", "transitionDuration": "The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object.", "TransitionProps": "Props applied to the transition element. By default, the element is based on this Transition component." diff --git a/packages/material-ui/src/SpeedDial/SpeedDial.d.ts b/packages/material-ui/src/SpeedDial/SpeedDial.d.ts index a39bfb847053ff..e16ffb4b715a68 100644 --- a/packages/material-ui/src/SpeedDial/SpeedDial.d.ts +++ b/packages/material-ui/src/SpeedDial/SpeedDial.d.ts @@ -1,4 +1,6 @@ import * as React from 'react'; +import { SxProps } from '@material-ui/system'; +import { Theme } from '../styles'; import { InternalStandardProps as StandardProps } from '..'; import { FabProps } from '../Fab'; import { TransitionHandlerProps, TransitionProps } from '../transitions'; @@ -83,6 +85,10 @@ export interface SpeedDialProps * The icon to display in the SpeedDial Fab when the SpeedDial is open. */ openIcon?: React.ReactNode; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; /** * The component used for the transition. * [Follow this guide](/components/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. diff --git a/packages/material-ui/src/SpeedDial/SpeedDial.js b/packages/material-ui/src/SpeedDial/SpeedDial.js index 6451b0fb309cfb..3daec5a6535b2b 100644 --- a/packages/material-ui/src/SpeedDial/SpeedDial.js +++ b/packages/material-ui/src/SpeedDial/SpeedDial.js @@ -2,14 +2,46 @@ import * as React from 'react'; import { isFragment } from 'react-is'; import PropTypes from 'prop-types'; import clsx from 'clsx'; +import { deepmerge } from '@material-ui/utils'; +import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; +import experimentalStyled from '../styles/experimentalStyled'; +import useThemeProps from '../styles/useThemeProps'; import { duration } from '../styles/transitions'; -import withStyles from '../styles/withStyles'; import Zoom from '../Zoom'; import Fab from '../Fab'; import capitalize from '../utils/capitalize'; import isMuiElement from '../utils/isMuiElement'; import useForkRef from '../utils/useForkRef'; import useControlled from '../utils/useControlled'; +import speedDialClasses, { getSpeedDialUtilityClass } from './speedDialClasses'; + +const overridesResolver = (props, styles) => { + const { styleProps } = props; + + return deepmerge( + { + ...styles[`direction${capitalize(styleProps.direction)}`], + [`& .${speedDialClasses.fab}`]: styles.fab, + [`& .${speedDialClasses.actions}`]: { + ...styles.actions, + ...(!styleProps.open && styles.actionsClosed), + }, + }, + styles.root || {}, + ); +}; + +const useUtilityClasses = (styleProps) => { + const { classes, open, direction } = styleProps; + + const slots = { + root: ['root', `direction${capitalize(direction)}`], + fab: ['fab'], + actions: ['actions', !open && 'actionsClosed'], + }; + + return composeClasses(slots, getSpeedDialUtilityClass, classes); +}; function getOrientation(direction) { if (direction === 'up' || direction === 'down') { @@ -34,72 +66,80 @@ function clamp(value, min, max) { const dialRadius = 32; const spacingActions = 16; -export const styles = (theme) => ({ - /* Styles applied to the root element. */ - root: { - zIndex: theme.zIndex.speedDial, - display: 'flex', - alignItems: 'center', - pointerEvents: 'none', - }, - /* Styles applied to the Fab component. */ - fab: { - pointerEvents: 'auto', +const SpeedDialRoot = experimentalStyled( + 'div', + {}, + { + name: 'MuiSpeedDial', + slot: 'Root', + overridesResolver, }, - /* Styles applied to the root element if direction="up" */ - directionUp: { +)(({ theme, styleProps }) => ({ + zIndex: theme.zIndex.speedDial, + display: 'flex', + alignItems: 'center', + pointerEvents: 'none', + ...(styleProps.direction === 'up' && { flexDirection: 'column-reverse', - '& $actions': { + [`& .${speedDialClasses.actions}`]: { flexDirection: 'column-reverse', marginBottom: -dialRadius, paddingBottom: spacingActions + dialRadius, }, - }, - /* Styles applied to the root element if direction="down" */ - directionDown: { + }), + ...(styleProps.direction === 'down' && { flexDirection: 'column', - '& $actions': { + [`& .${speedDialClasses.actions}`]: { flexDirection: 'column', marginTop: -dialRadius, paddingTop: spacingActions + dialRadius, }, - }, - /* Styles applied to the root element if direction="left" */ - directionLeft: { + }), + ...(styleProps.direction === 'left' && { flexDirection: 'row-reverse', - '& $actions': { + [`& .${speedDialClasses.actions}`]: { flexDirection: 'row-reverse', marginRight: -dialRadius, paddingRight: spacingActions + dialRadius, }, - }, - /* Styles applied to the root element if direction="right" */ - directionRight: { + }), + ...(styleProps.direction === 'right' && { flexDirection: 'row', - '& $actions': { + [`& .${speedDialClasses.actions}`]: { flexDirection: 'row', marginLeft: -dialRadius, paddingLeft: spacingActions + dialRadius, }, - }, - /* Styles applied to the actions (`children` wrapper) element. */ - actions: { - display: 'flex', - pointerEvents: 'auto', - }, - /* Styles applied to the actions (`children` wrapper) element if `open={false}`. */ - actionsClosed: { + }), +})); + +const SpeedDialFab = experimentalStyled( + Fab, + {}, + { name: 'MuiSpeedDial', slot: 'Fab' }, +)(() => ({ + pointerEvents: 'auto', +})); + +const SpeedDialActions = experimentalStyled( + 'div', + {}, + { name: 'MuiSpeedDial', slot: 'Actions' }, +)(({ styleProps }) => ({ + display: 'flex', + pointerEvents: 'auto', + ...(!styleProps.open && { transition: 'top 0s linear 0.2s', pointerEvents: 'none', - }, -}); + }), +})); -const SpeedDial = React.forwardRef(function SpeedDial(props, ref) { +const SpeedDial = React.forwardRef(function SpeedDial(inProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiSpeedDial' }); const { ariaLabel, FabProps: { ref: origDialButtonRef, ...FabProps } = {}, children: childrenProp, - classes, className, direction = 'up', hidden = false, @@ -129,6 +169,9 @@ const SpeedDial = React.forwardRef(function SpeedDial(props, ref) { state: 'open', }); + const styleProps = { ...props, open, direction }; + const classes = useUtilityClasses(styleProps); + const eventTimer = React.useRef(); React.useEffect(() => { @@ -331,8 +374,8 @@ const SpeedDial = React.forwardRef(function SpeedDial(props, ref) { }); return ( -
- {React.isValidElement(icon) && isMuiElement(icon, ['SpeedDialIcon']) ? React.cloneElement(icon, { open }) : icon} - + - -
+ + ); }); @@ -460,6 +506,10 @@ SpeedDial.propTypes = { * The icon to display in the SpeedDial Fab when the SpeedDial is open. */ openIcon: PropTypes.node, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.object, /** * The component used for the transition. * [Follow this guide](/components/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. @@ -489,4 +539,4 @@ SpeedDial.propTypes = { TransitionProps: PropTypes.object, }; -export default withStyles(styles, { name: 'MuiSpeedDial' })(SpeedDial); +export default SpeedDial; diff --git a/packages/material-ui/src/SpeedDial/SpeedDial.test.js b/packages/material-ui/src/SpeedDial/SpeedDial.test.js index 7a776fe23a83d3..85d787e4344ce7 100644 --- a/packages/material-ui/src/SpeedDial/SpeedDial.test.js +++ b/packages/material-ui/src/SpeedDial/SpeedDial.test.js @@ -2,23 +2,21 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy, useFakeTimers } from 'sinon'; import { - getClasses, createMount, createClientRender, act, fireEvent, screen, - describeConformance, + describeConformanceV5, } from 'test/utils'; import Icon from '@material-ui/core/Icon'; -import SpeedDial from './SpeedDial'; -import SpeedDialAction from '../SpeedDialAction'; +import SpeedDial, { speedDialClasses as classes } from '@material-ui/core/SpeedDial'; +import SpeedDialAction from '@material-ui/core/SpeedDialAction'; describe('', () => { // StrictModeViolation: not using act(), prefer test/utils/createClientRender const mount = createMount({ strict: false }); const render = createClientRender({ strict: false }); - let classes; const icon = font_icon; const FakeAction = () =>
; @@ -28,21 +26,17 @@ describe('', () => { ariaLabel: 'mySpeedDial', }; - before(() => { - classes = getClasses( - -
- , - ); - }); - - describeConformance(, () => ({ + describeConformanceV5(, () => ({ classes, inheritComponent: 'div', mount, + render, refInstanceof: window.HTMLDivElement, + muiName: 'MuiSpeedDial', + testVariantProps: { direction: 'right' }, skip: [ 'componentProp', // react-transition-group issue + 'componentsProp', 'reactTestRenderer', ], })); diff --git a/packages/material-ui/src/SpeedDial/index.d.ts b/packages/material-ui/src/SpeedDial/index.d.ts index 1d1c4c58f2e23d..66f605986245f9 100644 --- a/packages/material-ui/src/SpeedDial/index.d.ts +++ b/packages/material-ui/src/SpeedDial/index.d.ts @@ -1,2 +1,5 @@ export { default } from './SpeedDial'; export * from './SpeedDial'; + +export { default as speedDialClasses } from './speedDialClasses'; +export * from './speedDialClasses'; diff --git a/packages/material-ui/src/SpeedDial/index.js b/packages/material-ui/src/SpeedDial/index.js index d32154436367a9..22eeb5a48f0852 100644 --- a/packages/material-ui/src/SpeedDial/index.js +++ b/packages/material-ui/src/SpeedDial/index.js @@ -1 +1,4 @@ export { default } from './SpeedDial'; + +export { default as speedDialClasses } from './speedDialClasses'; +export * from './speedDialClasses'; diff --git a/packages/material-ui/src/SpeedDial/speedDialClasses.d.ts b/packages/material-ui/src/SpeedDial/speedDialClasses.d.ts new file mode 100644 index 00000000000000..4ba1e9b5a76e6e --- /dev/null +++ b/packages/material-ui/src/SpeedDial/speedDialClasses.d.ts @@ -0,0 +1,9 @@ +import { SpeedDialClassKey } from './SpeedDial'; + +export type SpeedDialClasses = Record; + +declare const speedDialClasses: SpeedDialClasses; + +export function getSpeedDialUtilityClass(slot: string): string; + +export default speedDialClasses; diff --git a/packages/material-ui/src/SpeedDial/speedDialClasses.js b/packages/material-ui/src/SpeedDial/speedDialClasses.js new file mode 100644 index 00000000000000..676609a9c8ee73 --- /dev/null +++ b/packages/material-ui/src/SpeedDial/speedDialClasses.js @@ -0,0 +1,18 @@ +import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled'; + +export function getSpeedDialUtilityClass(slot) { + return generateUtilityClass('MuiSpeedDial', slot); +} + +const speedDialClasses = generateUtilityClasses('MuiSpeedDial', [ + 'root', + 'fab', + 'directionUp', + 'directionDown', + 'directionLeft', + 'directionRight', + 'actions', + 'actionsClosed', +]); + +export default speedDialClasses; diff --git a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.d.ts b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.d.ts index c2273bb2fc7525..e3661976b7a836 100644 --- a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.d.ts +++ b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.d.ts @@ -1,4 +1,6 @@ import * as React from 'react'; +import { SxProps } from '@material-ui/system'; +import { Theme } from '../styles'; import { InternalStandardProps as StandardProps } from '..'; import { FabProps } from '../Fab'; import { TooltipProps } from '../Tooltip'; @@ -37,6 +39,10 @@ export interface SpeedDialActionProps extends StandardProps; /** * `classes` prop applied to the [`Tooltip`](/api/tooltip/) element. */ diff --git a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.js b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.js index f1107c233e05a5..8557d632cee06c 100644 --- a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.js +++ b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.js @@ -3,83 +3,136 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; -import withStyles from '../styles/withStyles'; +import { deepmerge } from '@material-ui/utils'; +import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; +import experimentalStyled from '../styles/experimentalStyled'; +import useThemeProps from '../styles/useThemeProps'; import { emphasize } from '../styles/colorManipulator'; import Fab from '../Fab'; import Tooltip from '../Tooltip'; import capitalize from '../utils/capitalize'; +import speedDialActionClasses, { getSpeedDialActionUtilityClass } from './speedDialActionClasses'; -export const styles = (theme) => ({ - /* Styles applied to the Fab component. */ - fab: { - margin: 8, - color: theme.palette.text.secondary, - backgroundColor: theme.palette.background.paper, - '&:hover': { - backgroundColor: emphasize(theme.palette.background.paper, 0.15), +const overridesResolverFab = (props, styles) => { + const { styleProps } = props; + + return deepmerge( + { + ...(!styleProps.open && styles.fabClosed), }, - transition: `${theme.transitions.create('transform', { - duration: theme.transitions.duration.shorter, - })}, opacity 0.8s`, - opacity: 1, + styles.fab || {}, + ); +}; + +const overridesResolverStatic = (props, styles) => { + const { styleProps } = props; + + return deepmerge( + { + ...(!styleProps.open && styles.staticTooltipClosed), + ...styles[`tooltipPlacement${capitalize(styleProps.tooltipPlacement)}`], + [`& .${speedDialActionClasses.staticTooltipLabel}`]: styles.staticTooltipLabel, + }, + styles.staticTooltip || {}, + ); +}; + +const useUtilityClasses = (styleProps) => { + const { open, tooltipPlacement, classes } = styleProps; + + const slots = { + fab: ['fab', !open && 'fabClosed'], + staticTooltip: [ + 'staticTooltip', + `tooltipPlacement${capitalize(tooltipPlacement)}`, + !open && 'staticTooltipClosed', + ], + staticTooltipLabel: ['staticTooltipLabel'], + }; + + return composeClasses(slots, getSpeedDialActionUtilityClass, classes); +}; + +const SpeedDialActionFab = experimentalStyled( + Fab, + {}, + { + name: 'MuiSpeedDialAction', + slot: 'Fab', + overridesResolver: overridesResolverFab, + }, +)(({ theme, styleProps }) => ({ + margin: 8, + color: theme.palette.text.secondary, + backgroundColor: theme.palette.background.paper, + '&:hover': { + backgroundColor: emphasize(theme.palette.background.paper, 0.15), }, - /* Styles applied to the Fab component if `open={false}`. */ - fabClosed: { + transition: `${theme.transitions.create('transform', { + duration: theme.transitions.duration.shorter, + })}, opacity 0.8s`, + opacity: 1, + ...(!styleProps.open && { opacity: 0, transform: 'scale(0)', + }), +})); + +const SpeedDialActionStaticTooltip = experimentalStyled( + 'span', + {}, + { + name: 'MuiSpeedDialAction', + slot: 'StaticTooltip', + overridesResolver: overridesResolverStatic, }, - /* Styles applied to the root element if `tooltipOpen={true}`. */ - staticTooltip: { - position: 'relative', - display: 'flex', - '& $staticTooltipLabel': { - transition: theme.transitions.create(['transform', 'opacity'], { - duration: theme.transitions.duration.shorter, - }), - opacity: 1, - }, - }, - /* Styles applied to the root element if `tooltipOpen={true}` and `open={false}`. */ - staticTooltipClosed: { - '& $staticTooltipLabel': { +)(({ theme, styleProps }) => ({ + position: 'relative', + display: 'flex', + alignItems: 'center', + [`& .${speedDialActionClasses.staticTooltipLabel}`]: { + transition: theme.transitions.create(['transform', 'opacity'], { + duration: theme.transitions.duration.shorter, + }), + opacity: 1, + ...(!styleProps.open && { opacity: 0, transform: 'scale(0.5)', - }, - }, - /* Styles applied to the static tooltip label if `tooltipOpen={true}`. */ - staticTooltipLabel: { - position: 'absolute', - ...theme.typography.body1, - backgroundColor: theme.palette.background.paper, - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[1], - color: theme.palette.text.secondary, - padding: '4px 16px', - wordBreak: 'keep-all', - }, - /* Styles applied to the root element if `tooltipOpen={true}` and `tooltipPlacement="left"`` */ - tooltipPlacementLeft: { - alignItems: 'center', - '& $staticTooltipLabel': { + }), + ...(styleProps.tooltipPlacement === 'left' && { transformOrigin: '100% 50%', right: '100%', marginRight: 8, - }, - }, - /* Styles applied to the root element if `tooltipOpen={true}` and `tooltipPlacement="right"`` */ - tooltipPlacementRight: { - alignItems: 'center', - '& $staticTooltipLabel': { + }), + ...(styleProps.tooltipPlacement === 'right' && { transformOrigin: '0% 50%', left: '100%', marginLeft: 8, - }, + }), }, -}); +})); + +const SpeedDialActionStaticTooltipLabel = experimentalStyled( + 'span', + {}, + { + name: 'MuiSpeedDialAction', + slot: 'StaticTooltipLabel', + }, +)(({ theme }) => ({ + position: 'absolute', + ...theme.typography.body1, + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[1], + color: theme.palette.text.secondary, + padding: '4px 16px', + wordBreak: 'keep-all', +})); -const SpeedDialAction = React.forwardRef(function SpeedDialAction(props, ref) { +const SpeedDialAction = React.forwardRef(function SpeedDialAction(inProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiSpeedDialAction' }); const { - classes, className, delay = 0, FabProps = {}, @@ -93,6 +146,9 @@ const SpeedDialAction = React.forwardRef(function SpeedDialAction(props, ref) { ...other } = props; + const styleProps = { ...props, tooltipPlacement }; + const classes = useUtilityClasses(styleProps); + const [tooltipOpen, setTooltipOpen] = React.useState(tooltipOpenProp); const handleTooltipClose = () => { @@ -106,12 +162,13 @@ const SpeedDialAction = React.forwardRef(function SpeedDialAction(props, ref) { const transitionStyle = { transitionDelay: `${delay}ms` }; const fab = ( - {icon} - + ); if (tooltipOpenProp) { return ( - - + {tooltipTitle} - + {fab} - + ); } @@ -195,6 +254,10 @@ SpeedDialAction.propTypes = { * If `true`, the component is shown. */ open: PropTypes.bool, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.object, /** * `classes` prop applied to the [`Tooltip`](/api/tooltip/) element. */ @@ -228,4 +291,4 @@ SpeedDialAction.propTypes = { tooltipTitle: PropTypes.node, }; -export default withStyles(styles, { name: 'MuiSpeedDialAction' })(SpeedDialAction); +export default SpeedDialAction; diff --git a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js index 48f3c5bf253b82..4740cb8ffb2694 100644 --- a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js +++ b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js @@ -1,18 +1,13 @@ import * as React from 'react'; import { expect } from 'chai'; -import { - getClasses, - createMount, - describeConformance, - act, - createClientRender, - fireEvent, -} from 'test/utils'; +import { createMount, describeConformanceV5, act, createClientRender, fireEvent } from 'test/utils'; import { useFakeTimers } from 'sinon'; import Icon from '@material-ui/core/Icon'; import Tooltip from '@material-ui/core/Tooltip'; import { fabClasses } from '@material-ui/core/Fab'; -import SpeedDialAction from './SpeedDialAction'; +import SpeedDialAction, { + speedDialActionClasses as classes, +} from '@material-ui/core/SpeedDialAction'; describe('', () => { let clock; @@ -26,20 +21,19 @@ describe('', () => { const mount = createMount({ strict: true }); const render = createClientRender(); - let classes; - before(() => { - classes = getClasses(add} tooltipTitle="placeholder" />); - }); - - describeConformance( + describeConformanceV5( add} tooltipTitle="placeholder" />, () => ({ classes, inheritComponent: Tooltip, mount, + render, refInstanceof: window.HTMLButtonElement, - skip: ['componentProp', 'reactTestRenderer'], + muiName: 'MuiSpeedDialAction', + testRootOverrides: { slotName: 'fab' }, + testVariantProps: { tooltipPlacement: 'right' }, + skip: ['componentProp', 'reactTestRenderer', 'componentsProp'], }), ); diff --git a/packages/material-ui/src/SpeedDialAction/index.d.ts b/packages/material-ui/src/SpeedDialAction/index.d.ts index 0ac4379e2c64ea..26c65f4d8dfa06 100644 --- a/packages/material-ui/src/SpeedDialAction/index.d.ts +++ b/packages/material-ui/src/SpeedDialAction/index.d.ts @@ -1,2 +1,5 @@ export { default } from './SpeedDialAction'; export * from './SpeedDialAction'; + +export { default as speedDialActionClasses } from './speedDialActionClasses'; +export * from './speedDialActionClasses'; diff --git a/packages/material-ui/src/SpeedDialAction/index.js b/packages/material-ui/src/SpeedDialAction/index.js index ac364668754b26..6a6d8319ff1784 100644 --- a/packages/material-ui/src/SpeedDialAction/index.js +++ b/packages/material-ui/src/SpeedDialAction/index.js @@ -1 +1,4 @@ export { default } from './SpeedDialAction'; + +export { default as speedDialActionClasses } from './speedDialActionClasses'; +export * from './speedDialActionClasses'; diff --git a/packages/material-ui/src/SpeedDialAction/speedDialActionClasses.d.ts b/packages/material-ui/src/SpeedDialAction/speedDialActionClasses.d.ts new file mode 100644 index 00000000000000..32fd6281596c84 --- /dev/null +++ b/packages/material-ui/src/SpeedDialAction/speedDialActionClasses.d.ts @@ -0,0 +1,9 @@ +import { SpeedDialActionClassKey } from './SpeedDialAction'; + +export type SpeedDialActionClasses = Record; + +declare const speedDialActionClasses: SpeedDialActionClasses; + +export function getSpeedDialActionUtilityClass(slot: string): string; + +export default speedDialActionClasses; diff --git a/packages/material-ui/src/SpeedDialAction/speedDialActionClasses.js b/packages/material-ui/src/SpeedDialAction/speedDialActionClasses.js new file mode 100644 index 00000000000000..6da34161a14564 --- /dev/null +++ b/packages/material-ui/src/SpeedDialAction/speedDialActionClasses.js @@ -0,0 +1,17 @@ +import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled'; + +export function getSpeedDialActionUtilityClass(slot) { + return generateUtilityClass('MuiSpeedDialAction', slot); +} + +const speedDialActionClasses = generateUtilityClasses('MuiSpeedDialAction', [ + 'fab', + 'fabClosed', + 'staticTooltip', + 'staticTooltipClosed', + 'staticTooltipLabel', + 'tooltipPlacementLeft', + 'tooltipPlacementRight', +]); + +export default speedDialActionClasses; diff --git a/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.d.ts b/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.d.ts index e6490fb9b85865..d7ab93804c098c 100644 --- a/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.d.ts +++ b/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.d.ts @@ -1,4 +1,6 @@ import * as React from 'react'; +import { SxProps } from '@material-ui/system'; +import { Theme } from '../styles'; import { InternalStandardProps as StandardProps } from '..'; export interface SpeedDialIconProps @@ -33,6 +35,10 @@ export interface SpeedDialIconProps * If `true`, the component is shown. */ open?: boolean; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; } export type SpeedDialIconClassKey = keyof NonNullable; diff --git a/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.js b/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.js index cf8ef6f20c88d7..0ca40903fcb476 100644 --- a/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.js +++ b/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.js @@ -1,53 +1,85 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; -import withStyles from '../styles/withStyles'; +import { deepmerge } from '@material-ui/utils'; +import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled'; +import experimentalStyled from '../styles/experimentalStyled'; +import useThemeProps from '../styles/useThemeProps'; import AddIcon from '../internal/svg-icons/Add'; +import speedDialIconClasses, { getSpeedDialIconUtilityClass } from './speedDialIconClasses'; -export const styles = (theme) => ({ - /* Styles applied to the root element. */ - root: { - height: 24, +const overridesResolver = (props, styles) => { + const { styleProps } = props; + + return deepmerge( + { + [`& .${speedDialIconClasses.icon}`]: { + ...styles.icon, + ...(styleProps.open && styles.iconOpen), + ...(styleProps.open && styleProps.openIcon && styles.iconWithOpenIconOpen), + }, + [`& .${speedDialIconClasses.openIcon}`]: { + ...styles.openIcon, + ...(styleProps.open && styles.openIconOpen), + }, + }, + styles.root || {}, + ); +}; + +const useUtilityClasses = (styleProps) => { + const { classes, open, openIcon } = styleProps; + + const slots = { + root: ['root'], + icon: ['icon', open && 'iconOpen', openIcon && open && 'iconWithOpenIconOpen'], + openIcon: ['openIcon', open && 'openIconOpen'], + }; + + return composeClasses(slots, getSpeedDialIconUtilityClass, classes); +}; + +const SpeedDialIconRoot = experimentalStyled( + 'span', + {}, + { + name: 'MuiSpeedDialIcon', + slot: 'Root', + overridesResolver, }, - /* Styles applied to the icon component. */ - icon: { +)(({ theme, styleProps }) => ({ + height: 24, + [`& .${speedDialIconClasses.icon}`]: { transition: theme.transitions.create(['transform', 'opacity'], { duration: theme.transitions.duration.short, }), + ...(styleProps.open && { + transform: 'rotate(45deg)', + ...(styleProps.openIcon && { + opacity: 0, + }), + }), }, - /* Styles applied to the icon component if `open={true}`. */ - iconOpen: { - transform: 'rotate(45deg)', - }, - /* Styles applied to the icon when an `openIcon` is provided and if `open={true}`. */ - iconWithOpenIconOpen: { - opacity: 0, - }, - /* Styles applied to the `openIcon` if provided. */ - openIcon: { + [`& .${speedDialIconClasses.openIcon}`]: { position: 'absolute', transition: theme.transitions.create(['transform', 'opacity'], { duration: theme.transitions.duration.short, }), opacity: 0, transform: 'rotate(-45deg)', + ...(styleProps.open && { + transform: 'rotate(0deg)', + opacity: 1, + }), }, - /* Styles applied to the `openIcon` if provided and if `open={true}`. */ - openIconOpen: { - transform: 'rotate(0deg)', - opacity: 1, - }, -}); - -const SpeedDialIcon = React.forwardRef(function SpeedDialIcon(props, ref) { - const { className, classes, icon: iconProp, open, openIcon: openIconProp, ...other } = props; +})); - const iconClassName = clsx(classes.icon, { - [classes.iconOpen]: open, - [classes.iconWithOpenIconOpen]: openIconProp && open, - }); +const SpeedDialIcon = React.forwardRef(function SpeedDialIcon(inProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiSpeedDialIcon' }); + const { className, icon: iconProp, open, openIcon: openIconProp, ...other } = props; - const openIconClassName = clsx(classes.openIcon, { [classes.openIconOpen]: open }); + const styleProps = { ...props }; + const classes = useUtilityClasses(styleProps); function formatIcon(icon, newClassName) { if (React.isValidElement(icon)) { @@ -58,10 +90,15 @@ const SpeedDialIcon = React.forwardRef(function SpeedDialIcon(props, ref) { } return ( - - {openIconProp ? formatIcon(openIconProp, openIconClassName) : null} - {iconProp ? formatIcon(iconProp, iconClassName) : } - + + {openIconProp ? formatIcon(openIconProp, classes.openIcon) : null} + {iconProp ? formatIcon(iconProp, classes.icon) : } + ); }); @@ -91,8 +128,12 @@ SpeedDialIcon.propTypes = { * The icon to display in the SpeedDial Floating Action Button when the SpeedDial is open. */ openIcon: PropTypes.node, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.object, }; SpeedDialIcon.muiName = 'SpeedDialIcon'; -export default withStyles(styles, { name: 'MuiSpeedDialIcon' })(SpeedDialIcon); +export default SpeedDialIcon; diff --git a/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.test.js b/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.test.js index 1f164590afc55c..9692d0a49be85f 100644 --- a/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.test.js +++ b/packages/material-ui/src/SpeedDialIcon/SpeedDialIcon.test.js @@ -1,25 +1,23 @@ import * as React from 'react'; import { expect } from 'chai'; -import { getClasses, createMount, createClientRender, describeConformance } from 'test/utils'; +import { createMount, createClientRender, describeConformanceV5 } from 'test/utils'; import Icon from '@material-ui/core/Icon'; -import SpeedDialIcon from './SpeedDialIcon'; +import SpeedDialIcon, { speedDialIconClasses as classes } from '@material-ui/core/SpeedDialIcon'; describe('', () => { const mount = createMount(); const render = createClientRender(); - let classes; const icon = font_icon; - before(() => { - classes = getClasses(); - }); - - describeConformance(, () => ({ + describeConformanceV5(, () => ({ classes, inheritComponent: 'span', mount, + render, refInstanceof: window.HTMLSpanElement, - skip: ['componentProp'], + muiName: 'MuiSpeedDialIcon', + testVariantProps: { icon }, + skip: ['componentProp', 'componentsProp'], })); it('should render the Add icon by default', () => { diff --git a/packages/material-ui/src/SpeedDialIcon/index.d.ts b/packages/material-ui/src/SpeedDialIcon/index.d.ts index b99978d5f24665..ffda1b4724462b 100644 --- a/packages/material-ui/src/SpeedDialIcon/index.d.ts +++ b/packages/material-ui/src/SpeedDialIcon/index.d.ts @@ -1,2 +1,5 @@ export { default } from './SpeedDialIcon'; export * from './SpeedDialIcon'; + +export { default as speedDialIconClasses } from './speedDialIconClasses'; +export * from './speedDialIconClasses'; diff --git a/packages/material-ui/src/SpeedDialIcon/index.js b/packages/material-ui/src/SpeedDialIcon/index.js index a76f5f6aa6dd94..19c450e2c69450 100644 --- a/packages/material-ui/src/SpeedDialIcon/index.js +++ b/packages/material-ui/src/SpeedDialIcon/index.js @@ -1 +1,4 @@ export { default } from './SpeedDialIcon'; + +export { default as speedDialIconClasses } from './speedDialIconClasses'; +export * from './speedDialIconClasses'; diff --git a/packages/material-ui/src/SpeedDialIcon/speedDialIconClasses.d.ts b/packages/material-ui/src/SpeedDialIcon/speedDialIconClasses.d.ts new file mode 100644 index 00000000000000..5022f5ab4f070a --- /dev/null +++ b/packages/material-ui/src/SpeedDialIcon/speedDialIconClasses.d.ts @@ -0,0 +1,9 @@ +import { SpeedDialIconClassKey } from './SpeedDialIcon'; + +export type SpeedDialIconClasses = Record; + +declare const speedDialIconClasses: SpeedDialIconClasses; + +export function getSpeedDialIconUtilityClass(slot: string): string; + +export default speedDialIconClasses; diff --git a/packages/material-ui/src/SpeedDialIcon/speedDialIconClasses.js b/packages/material-ui/src/SpeedDialIcon/speedDialIconClasses.js new file mode 100644 index 00000000000000..8145dbd98c0ccc --- /dev/null +++ b/packages/material-ui/src/SpeedDialIcon/speedDialIconClasses.js @@ -0,0 +1,16 @@ +import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled'; + +export function getSpeedDialIconUtilityClass(slot) { + return generateUtilityClass('MuiSpeedDialIcon', slot); +} + +const speedDialIconClasses = generateUtilityClasses('MuiSpeedDialIcon', [ + 'root', + 'icon', + 'iconOpen', + 'iconWithOpenIconOpen', + 'openIcon', + 'openIconOpen', +]); + +export default speedDialIconClasses;