diff --git a/docs/pages/api-docs/tab.json b/docs/pages/api-docs/tab.json
index b2d7a97088679d..70668576cd2a58 100644
--- a/docs/pages/api-docs/tab.json
+++ b/docs/pages/api-docs/tab.json
@@ -7,6 +7,7 @@
"disableRipple": { "type": { "name": "bool" } },
"icon": { "type": { "name": "union", "description": "element
| string" } },
"label": { "type": { "name": "node" } },
+ "sx": { "type": { "name": "object" } },
"value": { "type": { "name": "any" } },
"wrapped": { "type": { "name": "bool" } }
},
@@ -32,6 +33,6 @@
"filename": "/packages/material-ui/src/Tab/Tab.js",
"inheritance": { "component": "ButtonBase", "pathname": "/api/button-base/" },
"demos": "
",
- "styledComponent": false,
+ "styledComponent": true,
"cssComponent": false
}
diff --git a/docs/translations/api-docs/tab/tab.json b/docs/translations/api-docs/tab/tab.json
index 7f420f0f1759d7..53eb736ef65917 100644
--- a/docs/translations/api-docs/tab/tab.json
+++ b/docs/translations/api-docs/tab/tab.json
@@ -8,6 +8,7 @@
"disableRipple": "If true
, the ripple effect is disabled.
⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure to highlight the element by applying separate styles with the .Mui-focusedVisible
class.",
"icon": "The icon to display.",
"label": "The label element.",
+ "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.",
"value": "You can provide your own value. Otherwise, we fallback to the child position index.",
"wrapped": "Tab labels appear in a single row. They can use a second line if needed."
},
diff --git a/packages/material-ui/src/Tab/Tab.d.ts b/packages/material-ui/src/Tab/Tab.d.ts
index a8300f1de085c3..e540237e6b1f73 100644
--- a/packages/material-ui/src/Tab/Tab.d.ts
+++ b/packages/material-ui/src/Tab/Tab.d.ts
@@ -1,4 +1,6 @@
+import { SxProps } from '@material-ui/system';
import * as React from 'react';
+import { Theme } from '..';
import { ExtendButtonBase, ExtendButtonBaseTypeMap } from '../ButtonBase';
import { OverrideProps } from '../OverridableComponent';
@@ -52,6 +54,10 @@ export type TabTypeMap = ExtendButt
* The label element.
*/
label?: React.ReactNode;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
/**
* You can provide your own value. Otherwise, we fallback to the child position index.
*/
diff --git a/packages/material-ui/src/Tab/Tab.js b/packages/material-ui/src/Tab/Tab.js
index f06956517e6b8a..7abf5edcccacef 100644
--- a/packages/material-ui/src/Tab/Tab.js
+++ b/packages/material-ui/src/Tab/Tab.js
@@ -1,96 +1,142 @@
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 ButtonBase from '../ButtonBase';
import capitalize from '../utils/capitalize';
+import useThemeProps from '../styles/useThemeProps';
+import experimentalStyled from '../styles/experimentalStyled';
import unsupportedProp from '../utils/unsupportedProp';
+import tabClasses, { getTabUtilityClass } from './tabClasses';
-export const styles = (theme) => ({
+const overridesResolver = (props, styles) => {
+ const { styleProps } = props;
+
+ return deepmerge(styles.root || {}, {
+ ...(styleProps.label && styleProps.icon && styles.labelIcon),
+ ...styles[`textColor${capitalize(styleProps.textColor)}`],
+ ...(styleProps.fullWidth && styles.fullWidth),
+ ...(styleProps.wrapped && styles.wrapped),
+ [`& .${tabClasses.wrapper}`]: styles.wrapper,
+ });
+};
+
+const useUtilityClasses = (styleProps) => {
+ const { classes, textColor, fullWidth, wrapped, icon, label, selected, disabled } = styleProps;
+
+ const slots = {
+ root: [
+ 'root',
+ icon && label && 'labelIcon',
+ `textColor${capitalize(textColor)}`,
+ fullWidth && 'fullWidth',
+ wrapped && 'wrapped',
+ selected && 'selected',
+ disabled && 'disabled',
+ ],
+ wrapper: ['wrapper'],
+ };
+
+ return composeClasses(slots, getTabUtilityClass, classes);
+};
+
+const TabRoot = experimentalStyled(
+ ButtonBase,
+ {},
+ {
+ name: 'MuiTab',
+ slot: 'Root',
+ overridesResolver,
+ },
+)(({ theme, styleProps }) => ({
/* Styles applied to the root element. */
- root: {
- ...theme.typography.button,
- maxWidth: 264,
- minWidth: 72,
- position: 'relative',
- minHeight: 48,
- flexShrink: 0,
- padding: '6px 12px',
- overflow: 'hidden',
- whiteSpace: 'normal',
- textAlign: 'center',
- [theme.breakpoints.up('sm')]: {
- minWidth: 160,
- },
+ ...theme.typography.button,
+ maxWidth: 264,
+ minWidth: 72,
+ position: 'relative',
+ minHeight: 48,
+ flexShrink: 0,
+ padding: '6px 12px',
+ overflow: 'hidden',
+ whiteSpace: 'normal',
+ textAlign: 'center',
+ [theme.breakpoints.up('sm')]: {
+ minWidth: 160,
},
/* Styles applied to the root element if both `icon` and `label` are provided. */
- labelIcon: {
- minHeight: 72,
- paddingTop: 9,
- '& $wrapper > *:first-child': {
- marginBottom: 6,
- },
- },
+ ...(styleProps.icon &&
+ styleProps.label && {
+ minHeight: 72,
+ paddingTop: 9,
+ [`& .${tabClasses.wrapper} > *:first-child`]: {
+ marginBottom: 6,
+ },
+ }),
/* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor="inherit"`. */
- textColorInherit: {
+ ...(styleProps.textColor === 'inherit' && {
color: 'inherit',
opacity: 0.7,
- '&$selected': {
+ '&.Mui-selected': {
opacity: 1,
},
- '&$disabled': {
+ '&.Mui-disabled': {
opacity: theme.palette.action.disabledOpacity,
},
- },
+ }),
/* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor="primary"`. */
- textColorPrimary: {
+ ...(styleProps.textColor === 'primary' && {
color: theme.palette.text.secondary,
- '&$selected': {
+ '&.Mui-selected': {
color: theme.palette.primary.main,
},
- '&$disabled': {
+ '&.Mui-disabled': {
color: theme.palette.text.disabled,
},
- },
+ }),
/* Styles applied to the root element if the parent [`Tabs`](/api/tabs/) has `textColor="secondary"`. */
- textColorSecondary: {
+ ...(styleProps.textColor === 'secondary' && {
color: theme.palette.text.secondary,
- '&$selected': {
+ '&.Mui-selected': {
color: theme.palette.secondary.main,
},
- '&$disabled': {
+ '&.Mui-disabled': {
color: theme.palette.text.disabled,
},
- },
- /* Pseudo-class applied to the root element if `selected={true}` (controlled by the Tabs component). */
- selected: {},
- /* Pseudo-class applied to the root element if `disabled={true}` (controlled by the Tabs component). */
- disabled: {},
- /* Styles applied to the root element if `fullWidth={true}` (controlled by the Tabs component). */
- fullWidth: {
+ }),
+ /* Styles applied to the root element if `fullWidth={true}` */
+ ...(styleProps.fullWidth && {
flexShrink: 1,
flexGrow: 1,
flexBasis: 0,
maxWidth: 'none',
- },
+ }),
/* Styles applied to the root element if `wrapped={true}`. */
- wrapped: {
+ ...(styleProps.wrapped && {
fontSize: theme.typography.pxToRem(12),
lineHeight: 1.5,
+ }),
+}));
+
+const TabWrapper = experimentalStyled(
+ 'span',
+ {},
+ {
+ name: 'MuiTab',
+ slot: 'Wrapper',
},
+)({
/* Styles applied to the `icon` and `label`'s wrapper element. */
- wrapper: {
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- width: '100%',
- flexDirection: 'column',
- },
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100%',
+ flexDirection: 'column',
});
-const Tab = React.forwardRef(function Tab(props, ref) {
+const Tab = React.forwardRef(function Tab(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiTab' });
const {
- classes,
className,
disabled = false,
disableFocusRipple = false,
@@ -114,6 +160,20 @@ const Tab = React.forwardRef(function Tab(props, ref) {
...other
} = props;
+ const styleProps = {
+ ...props,
+ disabled,
+ disableFocusRipple,
+ selected,
+ icon: !!icon,
+ label: !!label,
+ fullWidth,
+ textColor,
+ wrapped,
+ };
+
+ const classes = useUtilityClasses(styleProps);
+
const handleClick = (event) => {
if (!selected && onChange) {
onChange(event, value);
@@ -135,35 +195,25 @@ const Tab = React.forwardRef(function Tab(props, ref) {
};
return (
-
-
+
{icon}
{label}
-
+
{indicator}
-
+
);
});
@@ -223,6 +273,10 @@ Tab.propTypes = {
* @ignore
*/
onFocus: PropTypes.func,
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.object,
/**
* You can provide your own value. Otherwise, we fallback to the child position index.
*/
@@ -235,4 +289,4 @@ Tab.propTypes = {
wrapped: PropTypes.bool,
};
-export default withStyles(styles, { name: 'MuiTab' })(Tab);
+export default Tab;
diff --git a/packages/material-ui/src/Tab/Tab.test.js b/packages/material-ui/src/Tab/Tab.test.js
index f91db82edd35af..53d8eabd081ffe 100644
--- a/packages/material-ui/src/Tab/Tab.test.js
+++ b/packages/material-ui/src/Tab/Tab.test.js
@@ -1,31 +1,24 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
-import {
- getClasses,
- createMount,
- describeConformance,
- act,
- createClientRender,
- fireEvent,
-} from 'test/utils';
+import { createMount, describeConformanceV5, act, createClientRender, fireEvent } from 'test/utils';
import Tab from './Tab';
import ButtonBase from '../ButtonBase';
+import classes from './tabClasses';
describe('', () => {
const mount = createMount();
const render = createClientRender();
- let classes;
- before(() => {
- classes = getClasses();
- });
-
- describeConformance(, () => ({
+ describeConformanceV5(, () => ({
classes,
inheritComponent: ButtonBase,
mount,
+ muiName: 'MuiTab',
+ testDeepOverrides: { slotName: 'wrapper', slotClassName: classes.wrapper },
+ testVariantProps: { variant: 'foo' },
refInstanceof: window.HTMLButtonElement,
+ skip: ['componentProp', 'componentsProp'],
}));
it('should have a ripple by default', () => {
diff --git a/packages/material-ui/src/Tab/index.d.ts b/packages/material-ui/src/Tab/index.d.ts
index 9d664d03f2e2d4..dae620b149a0af 100644
--- a/packages/material-ui/src/Tab/index.d.ts
+++ b/packages/material-ui/src/Tab/index.d.ts
@@ -1,2 +1,5 @@
export { default } from './Tab';
export * from './Tab';
+
+export { default as tabClasses } from './tabClasses';
+export * from './tabClasses';
diff --git a/packages/material-ui/src/Tab/index.js b/packages/material-ui/src/Tab/index.js
index dcfd2afd792227..7c828ba9dd2757 100644
--- a/packages/material-ui/src/Tab/index.js
+++ b/packages/material-ui/src/Tab/index.js
@@ -1 +1,4 @@
export { default } from './Tab';
+
+export { default as tabClasses } from './tabClasses';
+export * from './tabClasses';
diff --git a/packages/material-ui/src/Tab/tabClasses.d.ts b/packages/material-ui/src/Tab/tabClasses.d.ts
new file mode 100644
index 00000000000000..9389b19a2fdb6f
--- /dev/null
+++ b/packages/material-ui/src/Tab/tabClasses.d.ts
@@ -0,0 +1,18 @@
+export interface TabClasses {
+ root: string;
+ labelIcon: string;
+ textColorInherit: string;
+ textColorPrimary: string;
+ textColorSecondary: string;
+ selected: string;
+ disabled: string;
+ fullWidth: string;
+ wrapped: string;
+ wrapper: string;
+}
+
+declare const tabClasses: TabClasses;
+
+export function getTabUtilityClass(slot: string): string;
+
+export default tabClasses;
diff --git a/packages/material-ui/src/Tab/tabClasses.js b/packages/material-ui/src/Tab/tabClasses.js
new file mode 100644
index 00000000000000..598a9f783ea5ee
--- /dev/null
+++ b/packages/material-ui/src/Tab/tabClasses.js
@@ -0,0 +1,20 @@
+import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled';
+
+export function getTabUtilityClass(slot) {
+ return generateUtilityClass('MuiTab', slot);
+}
+
+const tabClasses = generateUtilityClasses('MuiTab', [
+ 'root',
+ 'labelIcon',
+ 'textColorInherit',
+ 'textColorPrimary',
+ 'textColorSecondary',
+ 'selected',
+ 'disabled',
+ 'fullWidth',
+ 'wrapped',
+ 'wrapper',
+]);
+
+export default tabClasses;