diff --git a/docs/pages/api-docs/tooltip.json b/docs/pages/api-docs/tooltip.json
index 5396fc2ffba5fc..82519ec2cfa8e9 100644
--- a/docs/pages/api-docs/tooltip.json
+++ b/docs/pages/api-docs/tooltip.json
@@ -28,6 +28,7 @@
},
"PopperComponent": { "type": { "name": "elementType" }, "default": "Popper" },
"PopperProps": { "type": { "name": "object" }, "default": "{}" },
+ "sx": { "type": { "name": "object" } },
"TransitionComponent": { "type": { "name": "elementType" }, "default": "Grow" },
"TransitionProps": { "type": { "name": "object" } }
},
@@ -54,6 +55,6 @@
"filename": "/packages/material-ui/src/Tooltip/Tooltip.js",
"inheritance": null,
"demos": "
",
- "styledComponent": false,
+ "styledComponent": true,
"cssComponent": false
}
diff --git a/docs/translations/api-docs/tooltip/tooltip.json b/docs/translations/api-docs/tooltip/tooltip.json
index 97a1810bb61c72..47a96ff44a6f61 100644
--- a/docs/translations/api-docs/tooltip/tooltip.json
+++ b/docs/translations/api-docs/tooltip/tooltip.json
@@ -22,6 +22,7 @@
"placement": "Tooltip placement.",
"PopperComponent": "The component used for the popper.",
"PopperProps": "Props applied to the Popper
element.",
+ "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.",
"title": "Tooltip title. Zero-length titles string are never displayed.",
"TransitionComponent": "The component used for the transition. Follow this guide to learn more about the requirements for this component.",
"TransitionProps": "Props applied to the transition element. By default, the element is based on this Transition
component."
diff --git a/packages/material-ui/src/Tooltip/Tooltip.d.ts b/packages/material-ui/src/Tooltip/Tooltip.d.ts
index f17846cf2028d3..4c605603cff377 100644
--- a/packages/material-ui/src/Tooltip/Tooltip.d.ts
+++ b/packages/material-ui/src/Tooltip/Tooltip.d.ts
@@ -1,5 +1,6 @@
import * as React from 'react';
-import { InternalStandardProps as StandardProps } from '..';
+import { SxProps } from '@material-ui/system';
+import { InternalStandardProps as StandardProps, Theme } from '..';
import { TransitionProps } from '../transitions/transition';
import { PopperProps } from '../Popper/Popper';
@@ -147,6 +148,10 @@ export interface TooltipProps extends StandardProps;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
/**
* Tooltip title. Zero-length titles string are never displayed.
*/
diff --git a/packages/material-ui/src/Tooltip/Tooltip.js b/packages/material-ui/src/Tooltip/Tooltip.js
index a24c6c90bc382b..aac7d5d23165b0 100644
--- a/packages/material-ui/src/Tooltip/Tooltip.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.js
@@ -1,9 +1,11 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
-import { elementAcceptingRef } from '@material-ui/utils';
+import { deepmerge, elementAcceptingRef } from '@material-ui/utils';
+import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled';
import { alpha } from '../styles/colorManipulator';
-import withStyles from '../styles/withStyles';
+import experimentalStyled from '../styles/experimentalStyled';
+import useThemeProps from '../styles/useThemeProps';
import capitalize from '../utils/capitalize';
import Grow from '../Grow';
import Popper from '../Popper';
@@ -13,14 +15,64 @@ import useId from '../utils/useId';
import useIsFocusVisible from '../utils/useIsFocusVisible';
import useControlled from '../utils/useControlled';
import useTheme from '../styles/useTheme';
+import tooltipClasses, { getTooltipUtilityClass } from './tooltipClasses';
function round(value) {
return Math.round(value * 1e5) / 1e5;
}
-function arrowGenerator() {
- return {
- '&[data-popper-placement*="bottom"] $arrow': {
+const overridesResolver = (props, styles) => {
+ const { styleProps } = props;
+
+ return deepmerge(styles.popper || {}, {
+ ...(!styleProps.disableInteractive && styles.popperInteractive),
+ ...(styleProps.arrow && styles.popperArrow),
+ [`& .${tooltipClasses.tooltip}`]: styles.tooltip,
+ ...(styleProps.arrow && { [`& .${tooltipClasses.tooltip}`]: styles.tooltipArrow }),
+ [`& .${tooltipClasses.arrow}`]: styles.arrow,
+ ...(styleProps.touch && { [`& .${tooltipClasses.tooltip}`]: styles.touch }),
+ [`& .${tooltipClasses.tooltip}`]: styles[
+ `tooltipPlacement${capitalize(styleProps.placement.split('-')[0])}`
+ ],
+ });
+};
+
+const useUtilityClasses = (styleProps) => {
+ const { classes, disableInteractive, arrow, touch, placement } = styleProps;
+
+ const slots = {
+ popper: ['popper', !disableInteractive && 'popperInteractive', arrow && 'popperArrow'],
+ tooltip: [
+ 'tooltip',
+ arrow && 'tooltipArrow',
+ touch && 'touch',
+ `tooltipPlacement${capitalize(placement.split('-')[0])}`,
+ ],
+ arrow: ['arrow'],
+ };
+
+ return composeClasses(slots, getTooltipUtilityClass, classes);
+};
+
+const TooltipPopper = experimentalStyled(
+ Popper,
+ {},
+ {
+ name: 'MuiTooltip',
+ slot: 'Popper',
+ overridesResolver,
+ },
+)(({ theme, styleProps }) => ({
+ /* Styles applied to the Popper element. */
+ zIndex: theme.zIndex.tooltip,
+ pointerEvents: 'none', // disable jss-rtl plugin
+ /* Styles applied to the Popper component unless `disableInteractive={true}`. */
+ ...(!styleProps.disableInteractive && {
+ pointerEvents: 'auto',
+ }),
+ /* Styles applied to the Popper element if `arrow={true}`. */
+ ...(styleProps.arrow && {
+ [`&[data-popper-placement*="bottom"] .${tooltipClasses.arrow}`]: {
top: 0,
left: 0,
marginTop: '-0.71em',
@@ -28,7 +80,7 @@ function arrowGenerator() {
transformOrigin: '0 100%',
},
},
- '&[data-popper-placement*="top"] $arrow': {
+ [`&[data-popper-placement*="top"] .${tooltipClasses.arrow}`]: {
bottom: 0,
left: 0,
marginBottom: '-0.71em',
@@ -36,7 +88,7 @@ function arrowGenerator() {
transformOrigin: '100% 0',
},
},
- '&[data-popper-placement*="right"] $arrow': {
+ [`&[data-popper-placement*="right"] .${tooltipClasses.arrow}`]: {
left: 0,
marginLeft: '-0.71em',
height: '1em',
@@ -45,7 +97,7 @@ function arrowGenerator() {
transformOrigin: '100% 100%',
},
},
- '&[data-popper-placement*="left"] $arrow': {
+ [`&[data-popper-placement*="left"] .${tooltipClasses.arrow}`]: {
right: 0,
marginRight: '-0.71em',
height: '1em',
@@ -54,97 +106,101 @@ function arrowGenerator() {
transformOrigin: '0 0',
},
},
- };
-}
-
-export const styles = (theme) => ({
- /* Styles applied to the Popper component. */
- popper: {
- zIndex: theme.zIndex.tooltip,
- pointerEvents: 'none', // disable jss-rtl plugin
- },
- /* Styles applied to the Popper component unless `disableInteractive={true}`. */
- popperInteractive: {
- pointerEvents: 'auto',
+ }),
+}));
+
+const TooltipLabel = experimentalStyled(
+ 'div',
+ {},
+ {
+ name: 'MuiTooltip',
+ slot: 'Tooltip',
},
- /* Styles applied to the Popper component if `arrow={true}`. */
- popperArrow: arrowGenerator(),
+)(({ theme, styleProps }) => ({
/* Styles applied to the tooltip (label wrapper) element. */
- tooltip: {
- backgroundColor: alpha(theme.palette.grey[700], 0.92),
- borderRadius: theme.shape.borderRadius,
- color: theme.palette.common.white,
- fontFamily: theme.typography.fontFamily,
- padding: '4px 8px',
- fontSize: theme.typography.pxToRem(11),
- maxWidth: 300,
- margin: 2,
- wordWrap: 'break-word',
- fontWeight: theme.typography.fontWeightMedium,
- },
+ backgroundColor: alpha(theme.palette.grey[700], 0.92),
+ borderRadius: theme.shape.borderRadius,
+ color: theme.palette.common.white,
+ fontFamily: theme.typography.fontFamily,
+ padding: '4px 8px',
+ fontSize: theme.typography.pxToRem(11),
+ maxWidth: 300,
+ margin: 2,
+ wordWrap: 'break-word',
+ fontWeight: theme.typography.fontWeightMedium,
/* Styles applied to the tooltip (label wrapper) element if `arrow={true}`. */
- tooltipArrow: {
+ ...(styleProps.arrow && {
position: 'relative',
margin: 0,
- },
- /* Styles applied to the arrow element. */
- arrow: {
- overflow: 'hidden',
- position: 'absolute',
- width: '1em',
- height: '0.71em' /* = width / sqrt(2) = (length of the hypotenuse) */,
- boxSizing: 'border-box',
- color: alpha(theme.palette.grey[700], 0.9),
- '&::before': {
- content: '""',
- margin: 'auto',
- display: 'block',
- width: '100%',
- height: '100%',
- backgroundColor: 'currentColor',
- transform: 'rotate(45deg)',
- },
- },
+ }),
/* Styles applied to the tooltip (label wrapper) element if the tooltip is opened by touch. */
- touch: {
+ ...(styleProps.touch && {
padding: '8px 16px',
fontSize: theme.typography.pxToRem(14),
lineHeight: `${round(16 / 14)}em`,
fontWeight: theme.typography.fontWeightRegular,
- },
+ }),
/* Styles applied to the tooltip (label wrapper) element if `placement` contains "left". */
- tooltipPlacementLeft: {
+ ...(styleProps.placement.split('-')[0] === 'left' && {
transformOrigin: 'right center',
marginRight: '24px',
[theme.breakpoints.up('sm')]: {
marginRight: '14px',
},
- },
+ }),
/* Styles applied to the tooltip (label wrapper) element if `placement` contains "right". */
- tooltipPlacementRight: {
+ ...(styleProps.placement.split('-')[0] === 'right' && {
transformOrigin: 'left center',
marginLeft: '24px',
[theme.breakpoints.up('sm')]: {
marginLeft: '14px',
},
- },
+ }),
/* Styles applied to the tooltip (label wrapper) element if `placement` contains "top". */
- tooltipPlacementTop: {
+ ...(styleProps.placement.split('-')[0] === 'top' && {
transformOrigin: 'center bottom',
marginBottom: '24px',
[theme.breakpoints.up('sm')]: {
marginBottom: '14px',
},
- },
+ }),
/* Styles applied to the tooltip (label wrapper) element if `placement` contains "bottom". */
- tooltipPlacementBottom: {
+ ...(styleProps.placement.split('-')[0] === 'bottom' && {
transformOrigin: 'center top',
marginTop: '24px',
[theme.breakpoints.up('sm')]: {
marginTop: '14px',
},
+ }),
+}));
+
+const TooltipArrow = experimentalStyled(
+ 'span',
+ {},
+ {
+ name: 'MuiTooltip',
+ slot: 'Arrow',
},
-});
+)(({ theme }) => ({
+ /* Styles applied to the arrow element. */
+ arrow: {
+ overflow: 'hidden',
+ position: 'absolute',
+ width: '1em',
+ height: '0.71em' /* = width / sqrt(2) = (length of the hypotenuse) */,
+ boxSizing: 'border-box',
+ color: alpha(theme.palette.grey[700], 0.9),
+ '&::before': {
+ content: '""',
+ margin: 'auto',
+ display: 'block',
+ width: '100%',
+ height: '100%',
+ backgroundColor: 'currentColor',
+ transform: 'rotate(45deg)',
+ },
+ },
+}));
let hystersisOpen = false;
let hystersisTimer = null;
@@ -163,11 +219,11 @@ function composeEventHandler(handler, eventHandler) {
};
}
-const Tooltip = React.forwardRef(function Tooltip(props, ref) {
+const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'MuiTooltip' });
const {
arrow = false,
children,
- classes,
describeChild = false,
disableFocusListener = false,
disableHoverListener = false,
@@ -548,14 +604,23 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
};
}, [arrowRef, PopperProps]);
+ const styleProps = {
+ ...props,
+ arrow,
+ disableInteractive,
+ PopperComponent,
+ placement,
+ touch: ignoreNonTouchEvents.current,
+ };
+
+ const classes = useUtilityClasses(styleProps);
+
return (
{React.cloneElement(children, childrenProps)}
-
- {({ placement: placementInner, TransitionProps: TransitionPropsInner }) => (
+ {({ TransitionProps: TransitionPropsInner }) => (
-
+
{title}
- {arrow ? : null}
-
+ {arrow ? (
+
+ ) : null}
+
)}
-
+
);
});
@@ -735,6 +794,10 @@ Tooltip.propTypes = {
* @default {}
*/
PopperProps: PropTypes.object,
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.object,
/**
* Tooltip title. Zero-length titles string are never displayed.
*/
@@ -752,4 +815,4 @@ Tooltip.propTypes = {
TransitionProps: PropTypes.object,
};
-export default withStyles(styles, { name: 'MuiTooltip', flip: false })(Tooltip);
+export default Tooltip;
diff --git a/packages/material-ui/src/Tooltip/Tooltip.test.js b/packages/material-ui/src/Tooltip/Tooltip.test.js
index 552e91e9d56b0d..db0d2617e17211 100644
--- a/packages/material-ui/src/Tooltip/Tooltip.test.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.test.js
@@ -2,9 +2,8 @@ import * as React from 'react';
import { expect } from 'chai';
import { spy, useFakeTimers } from 'sinon';
import {
- getClasses,
createMount,
- describeConformance,
+ describeConformanceV5,
act,
createClientRender,
fireEvent,
@@ -16,6 +15,7 @@ import {
import { camelCase } from 'lodash/string';
import Tooltip, { testReset } from './Tooltip';
import Input from '../Input';
+import classes from './tooltipClasses';
async function raf() {
return new Promise((resolve) => {
@@ -47,18 +47,9 @@ describe('', () => {
});
const mount = createMount({ strict: true });
- let classes;
const render = createClientRender();
- before(() => {
- classes = getClasses(
-
-
- ,
- );
- });
-
- describeConformance(
+ describeConformanceV5(
,
@@ -66,7 +57,10 @@ describe('', () => {
classes,
inheritComponent: 'button',
mount,
+ muiName: 'MuiTooltip',
refInstanceof: window.HTMLButtonElement,
+ testVariantProps: { arrow: true },
+ testDeepOverrides: { slotName: 'tooltip', slotClassName: classes.tooltip },
skip: [
'componentProp',
// react-transition-group issue
diff --git a/packages/material-ui/src/Tooltip/index.d.ts b/packages/material-ui/src/Tooltip/index.d.ts
index de73a9e2b6514e..c44b819bdc9bff 100644
--- a/packages/material-ui/src/Tooltip/index.d.ts
+++ b/packages/material-ui/src/Tooltip/index.d.ts
@@ -1,2 +1,5 @@
export { default } from './Tooltip';
export * from './Tooltip';
+
+export { default as tooltipClasses } from './tooltipClasses';
+export * from './tooltipClasses';
diff --git a/packages/material-ui/src/Tooltip/index.js b/packages/material-ui/src/Tooltip/index.js
index cdc0fab160f86c..41217639d8cc24 100644
--- a/packages/material-ui/src/Tooltip/index.js
+++ b/packages/material-ui/src/Tooltip/index.js
@@ -1 +1,4 @@
export { default } from './Tooltip';
+
+export { default as tooltipClasses } from './tooltipClasses';
+export * from './tooltipClasses';
diff --git a/packages/material-ui/src/Tooltip/tooltipClasses.d.ts b/packages/material-ui/src/Tooltip/tooltipClasses.d.ts
new file mode 100644
index 00000000000000..b463645331760c
--- /dev/null
+++ b/packages/material-ui/src/Tooltip/tooltipClasses.d.ts
@@ -0,0 +1,19 @@
+export interface TooltipClasses {
+ popper: string;
+ popperInteractive: string;
+ popperArrow: string;
+ tooltip: string;
+ tooltipArrow: string;
+ touch: string;
+ tooltipPlacementLeft: string;
+ tooltipPlacementRight: string;
+ tooltipPlacementTop: string;
+ tooltipPlacementBottom: string;
+ arrow: string;
+}
+
+declare const tooltipClasses: TooltipClasses;
+
+export function getTooltipUtilityClass(slot: string): string;
+
+export default tooltipClasses;
diff --git a/packages/material-ui/src/Tooltip/tooltipClasses.js b/packages/material-ui/src/Tooltip/tooltipClasses.js
new file mode 100644
index 00000000000000..a381f9daa76b52
--- /dev/null
+++ b/packages/material-ui/src/Tooltip/tooltipClasses.js
@@ -0,0 +1,21 @@
+import { generateUtilityClass, generateUtilityClasses } from '@material-ui/unstyled';
+
+export function getTooltipUtilityClass(slot) {
+ return generateUtilityClass('MuiTooltip', slot);
+}
+
+const tooltipClasses = generateUtilityClasses('MuiTooltip', [
+ 'popper',
+ 'popperInteractive',
+ 'popperArrow',
+ 'tooltip',
+ 'tooltipArrow',
+ 'touch',
+ 'tooltipPlacementLeft',
+ 'tooltipPlacementRight',
+ 'tooltipPlacementTop',
+ 'tooltipPlacementBottom',
+ 'arrow',
+]);
+
+export default tooltipClasses;