From c637eeddfebbb0ecdb4a9f57041557506b353319 Mon Sep 17 00:00:00 2001 From: Sean Campbell Date: Wed, 27 Jan 2021 10:16:59 -0500 Subject: [PATCH 1/3] Tab to emotion --- docs/pages/api-docs/tab.json | 3 +- docs/translations/api-docs/tab/tab.json | 1 + packages/material-ui/src/Tab/Tab.d.ts | 6 + packages/material-ui/src/Tab/Tab.js | 190 ++++++++++++------- packages/material-ui/src/Tab/Tab.test.js | 21 +- packages/material-ui/src/Tab/index.d.ts | 3 + packages/material-ui/src/Tab/index.js | 3 + packages/material-ui/src/Tab/tabClasses.d.ts | 18 ++ packages/material-ui/src/Tab/tabClasses.js | 20 ++ 9 files changed, 181 insertions(+), 84 deletions(-) create mode 100644 packages/material-ui/src/Tab/tabClasses.d.ts create mode 100644 packages/material-ui/src/Tab/tabClasses.js 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..a6206a1f8ff4fa 100644 --- a/packages/material-ui/src/Tab/Tab.js +++ b/packages/material-ui/src/Tab/Tab.js @@ -1,96 +1,141 @@ 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 } = styleProps; + + const slots = { + root: [ + 'root', + icon && label && 'labelIcon', + `textColor${capitalize(textColor)}`, + fullWidth && 'fullWidth', + wrapped && 'wrapped', + selected && 'selected', + ], + 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 +159,19 @@ const Tab = React.forwardRef(function Tab(props, ref) { ...other } = props; + const styleProps = { + ...props, + disabled, + selected, + icon: !!icon, + label: !!label, + fullWidth, + textColor, + wrapped, + }; + + const classes = useUtilityClasses(styleProps); + const handleClick = (event) => { if (!selected && onChange) { onChange(event, value); @@ -135,35 +193,25 @@ const Tab = React.forwardRef(function Tab(props, ref) { }; return ( - - + {icon} {label} - + {indicator} - + ); }); @@ -223,6 +271,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 +287,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; From 2742df2637b85a54168e6399ae344cd3d0c78157 Mon Sep 17 00:00:00 2001 From: Sean Campbell Date: Wed, 27 Jan 2021 11:15:50 -0500 Subject: [PATCH 2/3] add disabled to class slots Tab --- packages/material-ui/src/Tab/Tab.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/material-ui/src/Tab/Tab.js b/packages/material-ui/src/Tab/Tab.js index a6206a1f8ff4fa..6a25d71f5a7d30 100644 --- a/packages/material-ui/src/Tab/Tab.js +++ b/packages/material-ui/src/Tab/Tab.js @@ -23,7 +23,7 @@ const overridesResolver = (props, styles) => { }; const useUtilityClasses = (styleProps) => { - const { classes, textColor, fullWidth, wrapped, icon, label, selected } = styleProps; + const { classes, textColor, fullWidth, wrapped, icon, label, selected, disabled } = styleProps; const slots = { root: [ @@ -33,6 +33,7 @@ const useUtilityClasses = (styleProps) => { fullWidth && 'fullWidth', wrapped && 'wrapped', selected && 'selected', + disabled && 'disabled', ], wrapper: ['wrapper'], }; From e0b74e7693efc69298f541ee8492515c6ea99d9f Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Wed, 27 Jan 2021 19:15:48 +0100 Subject: [PATCH 3/3] Update packages/material-ui/src/Tab/Tab.js --- packages/material-ui/src/Tab/Tab.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/material-ui/src/Tab/Tab.js b/packages/material-ui/src/Tab/Tab.js index 6a25d71f5a7d30..7abf5edcccacef 100644 --- a/packages/material-ui/src/Tab/Tab.js +++ b/packages/material-ui/src/Tab/Tab.js @@ -163,6 +163,7 @@ const Tab = React.forwardRef(function Tab(inProps, ref) { const styleProps = { ...props, disabled, + disableFocusRipple, selected, icon: !!icon, label: !!label,