diff --git a/docs/pages/api-docs/fab.json b/docs/pages/api-docs/fab.json
index 67e5ff3272d189..c5928f084db175 100644
--- a/docs/pages/api-docs/fab.json
+++ b/docs/pages/api-docs/fab.json
@@ -21,6 +21,7 @@
},
"default": "'large'"
},
+ "sx": { "type": { "name": "object" } },
"variant": {
"type": {
"name": "union",
@@ -52,6 +53,6 @@
"filename": "/packages/material-ui/src/Fab/Fab.js",
"inheritance": { "component": "ButtonBase", "pathname": "/api/button-base/" },
"demos": "
",
- "styledComponent": false,
+ "styledComponent": true,
"cssComponent": false
}
diff --git a/docs/translations/api-docs/fab/fab.json b/docs/translations/api-docs/fab/fab.json
index 7216e76d8fa3fb..408c2621ecd582 100644
--- a/docs/translations/api-docs/fab/fab.json
+++ b/docs/translations/api-docs/fab/fab.json
@@ -10,6 +10,7 @@
"disableRipple": "If true, the ripple effect is disabled.",
"href": "The URL to link to when the button is clicked. If defined, an a element will be used as the root node.",
"size": "The size of the component. small is equivalent to the dense button styling.",
+ "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.",
"variant": "The variant to use."
},
"classDescriptions": {
diff --git a/framer/scripts/framerConfig.js b/framer/scripts/framerConfig.js
index d4ce7dc1b9748a..473f8f45ff1d71 100644
--- a/framer/scripts/framerConfig.js
+++ b/framer/scripts/framerConfig.js
@@ -165,6 +165,7 @@ export const componentSettings = {
'disableFocusRipple',
// FIXME: `Union`
'variant',
+ 'sx',
],
propValues: {
icon: "'add'",
diff --git a/packages/material-ui/src/Fab/Fab.d.ts b/packages/material-ui/src/Fab/Fab.d.ts
index f60f5b979d05f8..8ee98a096b1f69 100644
--- a/packages/material-ui/src/Fab/Fab.d.ts
+++ b/packages/material-ui/src/Fab/Fab.d.ts
@@ -1,5 +1,6 @@
import { OverridableStringUnion } from '@material-ui/types';
-import { PropTypes } from '..';
+import { SxProps } from '@material-ui/system';
+import { PropTypes, Theme } from '..';
import { ExtendButtonBase, ExtendButtonBaseTypeMap } from '../ButtonBase';
import { OverrideProps } from '../OverridableComponent';
@@ -74,6 +75,10 @@ export type FabTypeMap = ExtendB
* @default 'circular'
*/
variant?: OverridableStringUnion;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
};
defaultComponent: D;
}>;
diff --git a/packages/material-ui/src/Fab/Fab.js b/packages/material-ui/src/Fab/Fab.js
index 14e82ea90080f7..cc34a706bbd71a 100644
--- a/packages/material-ui/src/Fab/Fab.js
+++ b/packages/material-ui/src/Fab/Fab.js
@@ -1,14 +1,56 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
-import { useThemeVariants } from '@material-ui/styles';
-import withStyles from '../styles/withStyles';
+import { deepmerge } from '@material-ui/utils';
+import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled';
import ButtonBase from '../ButtonBase';
import capitalize from '../utils/capitalize';
+import useThemeProps from '../styles/useThemeProps';
+import fabClasses, { getFabUtilityClass } from './fabClasses';
+import experimentalStyled from '../styles/experimentalStyled';
-export const styles = (theme) => ({
- /* Styles applied to the root element. */
- root: {
+const overridesResolver = (props, styles) => {
+ const { styleProps } = props;
+
+ return deepmerge(styles.root || {}, {
+ ...styles[styleProps.variant],
+ ...styles[`size${capitalize(styleProps.size)}`],
+ ...(styleProps.color === 'inherit' && styles.colorInherit),
+ ...(styleProps.color === 'primary' && styles.primary),
+ ...(styleProps.color === 'secondary' && styles.secondary),
+ [`& .${fabClasses.label}`]: styles.label,
+ });
+};
+
+const useUtilityClasses = (styleProps) => {
+ const { color, variant, classes, size } = styleProps;
+
+ const slots = {
+ root: [
+ 'root',
+ variant,
+ `size${capitalize(size)}`,
+ color === 'inherit' && 'colorInherit',
+ color === 'primary' && 'primary',
+ color === 'secondary' && 'secondary',
+ ],
+ label: ['label'],
+ };
+
+ return composeClasses(slots, getFabUtilityClass, classes);
+};
+
+const FabRoot = experimentalStyled(
+ ButtonBase,
+ {},
+ {
+ name: 'MuiFab',
+ slot: 'Root',
+ overridesResolver,
+ },
+)(
+ ({ theme, styleProps }) => ({
+ /* Styles applied to the root element. */
...theme.typography.button,
minHeight: 36,
transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color'], {
@@ -33,95 +75,101 @@ export const styles = (theme) => ({
},
textDecoration: 'none',
},
- '&$focusVisible': {
+ '&.Mui-focusVisible': {
boxShadow: theme.shadows[6],
},
- '&$disabled': {
+ '&.Mui-disabled': {
color: theme.palette.action.disabled,
boxShadow: theme.shadows[0],
backgroundColor: theme.palette.action.disabledBackground,
},
- },
- /* Styles applied to the span element that wraps the children. */
- label: {
- width: '100%', // assure the correct width for iOS Safari
- display: 'inherit',
- alignItems: 'inherit',
- justifyContent: 'inherit',
- },
- /* Styles applied to the root element if `color="primary"`. */
- primary: {
- color: theme.palette.primary.contrastText,
- backgroundColor: theme.palette.primary.main,
- '&:hover': {
- backgroundColor: theme.palette.primary.dark,
- // Reset on touch devices, it doesn't add specificity
- '@media (hover: none)': {
- backgroundColor: theme.palette.primary.main,
+ /* Styles applied to the root element if `size="small"``. */
+ ...(styleProps.size === 'small' && {
+ width: 40,
+ height: 40,
+ }),
+ /* Styles applied to the root element if `size="medium"``. */
+ ...(styleProps.size === 'medium' && {
+ width: 48,
+ height: 48,
+ }),
+ /* Styles applied to the root element if `variant="extended"`. */
+ ...(styleProps.variant === 'extended' && {
+ borderRadius: 48 / 2,
+ padding: '0 16px',
+ width: 'auto',
+ minHeight: 'auto',
+ minWidth: 48,
+ height: 48,
+ }),
+ ...(styleProps.variant === 'extended' &&
+ styleProps.size === 'small' && {
+ width: 'auto',
+ padding: '0 8px',
+ borderRadius: 34 / 2,
+ minWidth: 34,
+ height: 34,
+ }),
+ ...(styleProps.variant === 'extended' &&
+ styleProps.size === 'medium' && {
+ width: 'auto',
+ padding: '0 16px',
+ borderRadius: 40 / 2,
+ minWidth: 40,
+ height: 40,
+ }),
+ /* Styles applied to the root element if `color="inherit"`. */
+ ...(styleProps.color === 'inherit' && {
+ color: 'inherit',
+ }),
+ }),
+ ({ theme, styleProps }) => ({
+ /* Styles applied to the root element if `color="primary"`. */
+ ...(styleProps.color === 'primary' && {
+ color: theme.palette.primary.contrastText,
+ backgroundColor: theme.palette.primary.main,
+ '&:hover': {
+ backgroundColor: theme.palette.primary.dark,
+ // Reset on touch devices, it doesn't add specificity
+ '@media (hover: none)': {
+ backgroundColor: theme.palette.primary.main,
+ },
},
- },
- },
- /* Styles applied to the root element if `color="secondary"`. */
- secondary: {
- color: theme.palette.secondary.contrastText,
- backgroundColor: theme.palette.secondary.main,
- '&:hover': {
- backgroundColor: theme.palette.secondary.dark,
- // Reset on touch devices, it doesn't add specificity
- '@media (hover: none)': {
- backgroundColor: theme.palette.secondary.main,
+ }),
+ /* Styles applied to the root element if `color="secondary"`. */
+ ...(styleProps.color === 'secondary' && {
+ color: theme.palette.secondary.contrastText,
+ backgroundColor: theme.palette.secondary.main,
+ '&:hover': {
+ backgroundColor: theme.palette.secondary.dark,
+ // Reset on touch devices, it doesn't add specificity
+ '@media (hover: none)': {
+ backgroundColor: theme.palette.secondary.main,
+ },
},
- },
- },
- /* Styles applied to the root element if `variant="extended"`. */
- extended: {
- borderRadius: 48 / 2,
- padding: '0 16px',
- width: 'auto',
- minHeight: 'auto',
- minWidth: 48,
- height: 48,
- '&$sizeSmall': {
- width: 'auto',
- padding: '0 8px',
- borderRadius: 34 / 2,
- minWidth: 34,
- height: 34,
- },
- '&$sizeMedium': {
- width: 'auto',
- padding: '0 16px',
- borderRadius: 40 / 2,
- minWidth: 40,
- height: 40,
- },
- },
- /* Styles applied to the root element if `variant="circular"`. */
- circular: {},
- /* Pseudo-class applied to the ButtonBase root element if the button is keyboard focused. */
- focusVisible: {},
- /* Pseudo-class applied to the root element if `disabled={true}`. */
- disabled: {},
- /* Styles applied to the root element if `color="inherit"`. */
- colorInherit: {
- color: 'inherit',
- },
- /* Styles applied to the root element if `size="small"``. */
- sizeSmall: {
- width: 40,
- height: 40,
- },
- /* Styles applied to the root element if `size="medium"``. */
- sizeMedium: {
- width: 48,
- height: 48,
+ }),
+ }),
+);
+
+const FabLabel = experimentalStyled(
+ 'span',
+ {},
+ {
+ name: 'MuiFab',
+ slot: 'Label',
},
+)({
+ /* Styles applied to the span element that wraps the children. */
+ width: '100%', // assure the correct width for iOS Safari
+ display: 'inherit',
+ alignItems: 'inherit',
+ justifyContent: 'inherit',
});
-const Fab = React.forwardRef(function Fab(props, ref) {
+const Fab = React.forwardRef(function Fab(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiFab' });
const {
children,
- classes,
className,
color = 'default',
component = 'button',
@@ -133,43 +181,33 @@ const Fab = React.forwardRef(function Fab(props, ref) {
...other
} = props;
- const themeVariantsClasses = useThemeVariants(
- {
- ...props,
- color,
- component,
- disabled,
- disableFocusRipple,
- size,
- variant,
- },
- 'MuiFab',
- );
+ const styleProps = {
+ ...props,
+ color,
+ component,
+ disabled,
+ disableFocusRipple,
+ size,
+ variant,
+ };
+
+ const classes = useUtilityClasses(styleProps);
return (
-
- {children}
-
+
+ {children}
+
+
);
});
@@ -229,6 +267,10 @@ Fab.propTypes = {
* @default 'large'
*/
size: PropTypes.oneOf(['large', 'medium', 'small']),
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.object,
/**
* The variant to use.
* @default 'circular'
@@ -239,4 +281,4 @@ Fab.propTypes = {
]),
};
-export default withStyles(styles, { name: 'MuiFab' })(Fab);
+export default Fab;
diff --git a/packages/material-ui/src/Fab/Fab.test.js b/packages/material-ui/src/Fab/Fab.test.js
index ed0a36bdd20bce..fa5e8a8969bada 100644
--- a/packages/material-ui/src/Fab/Fab.test.js
+++ b/packages/material-ui/src/Fab/Fab.test.js
@@ -1,8 +1,7 @@
import * as React from 'react';
import { expect } from 'chai';
import {
- getClasses,
- describeConformance,
+ describeConformanceV5,
createClientRender,
createMount,
createServerRender,
@@ -12,22 +11,22 @@ import {
import Fab from './Fab';
import ButtonBase, { touchRippleClasses } from '../ButtonBase';
import Icon from '../Icon';
+import classes from './fabClasses';
describe('', () => {
const mount = createMount();
const render = createClientRender({ strict: false });
- let classes;
- before(() => {
- classes = getClasses(Fab);
- });
-
- describeConformance(Conformance?, () => ({
+ describeConformanceV5(Conformance?, () => ({
classes,
inheritComponent: ButtonBase,
mount,
+ muiName: 'MuiFab',
+ testVariantProps: { variant: 'extended' },
+ testDeepOverrides: { slotName: 'label', slotClassName: classes.label },
+ testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' },
refInstanceof: window.HTMLButtonElement,
- skip: ['componentProp'],
+ skip: ['componentsProp'],
}));
it('should render with the root class but no others', () => {
diff --git a/packages/material-ui/src/Fab/fabClasses.d.ts b/packages/material-ui/src/Fab/fabClasses.d.ts
new file mode 100644
index 00000000000000..07c7c2478f200e
--- /dev/null
+++ b/packages/material-ui/src/Fab/fabClasses.d.ts
@@ -0,0 +1,20 @@
+export interface FabClasses {
+ root: string;
+ label: string;
+ primary: string;
+ secondary: string;
+ extended: string;
+ circular: string;
+ focusVisible: string;
+ disabled: string;
+ colorInherit: string;
+ sizeSmall: string;
+ sizeMedium: string;
+ sizeLarge: string;
+}
+
+declare const fabClasses: FabClasses;
+
+export function getFabUtilityClass(slot: string): string;
+
+export default fabClasses;
diff --git a/packages/material-ui/src/Fab/fabClasses.js b/packages/material-ui/src/Fab/fabClasses.js
new file mode 100644
index 00000000000000..4e52490393d9e7
--- /dev/null
+++ b/packages/material-ui/src/Fab/fabClasses.js
@@ -0,0 +1,22 @@
+import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled';
+
+export function getFabUtilityClass(slot) {
+ return generateUtilityClass('MuiFab', slot);
+}
+
+const fabClasses = generateUtilityClasses('MuiFab', [
+ 'root',
+ 'label',
+ 'primary',
+ 'secondary',
+ 'extended',
+ 'circular',
+ 'focusVisible',
+ 'disabled',
+ 'colorInherit',
+ 'sizeSmall',
+ 'sizeMedium',
+ 'sizeLarge',
+]);
+
+export default fabClasses;
diff --git a/packages/material-ui/src/Fab/index.d.ts b/packages/material-ui/src/Fab/index.d.ts
index 898b9dbebe7c83..b1751bf8ec2032 100644
--- a/packages/material-ui/src/Fab/index.d.ts
+++ b/packages/material-ui/src/Fab/index.d.ts
@@ -1,2 +1,5 @@
export { default } from './Fab';
export * from './Fab';
+
+export { default as fabClasses } from './fabClasses';
+export * from './fabClasses';
diff --git a/packages/material-ui/src/Fab/index.js b/packages/material-ui/src/Fab/index.js
index e27f6caceabbcb..e5fd203ad858c0 100644
--- a/packages/material-ui/src/Fab/index.js
+++ b/packages/material-ui/src/Fab/index.js
@@ -1 +1,4 @@
export { default } from './Fab';
+
+export { default as fabClasses } from './fabClasses';
+export * from './fabClasses';
diff --git a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js
index e2e760b3f31154..48f3c5bf253b82 100644
--- a/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js
+++ b/packages/material-ui/src/SpeedDialAction/SpeedDialAction.test.js
@@ -11,7 +11,7 @@ import {
import { useFakeTimers } from 'sinon';
import Icon from '@material-ui/core/Icon';
import Tooltip from '@material-ui/core/Tooltip';
-import Fab from '@material-ui/core/Fab';
+import { fabClasses } from '@material-ui/core/Fab';
import SpeedDialAction from './SpeedDialAction';
describe('', () => {
@@ -27,11 +27,9 @@ describe('', () => {
const mount = createMount({ strict: true });
const render = createClientRender();
let classes;
- let fabClasses;
before(() => {
classes = getClasses(add} tooltipTitle="placeholder" />);
- fabClasses = getClasses(Fab);
});
describeConformance(