diff --git a/docs/pages/api/tooltip.md b/docs/pages/api/tooltip.md index acd919954777e4..aeb35d6931008b 100644 --- a/docs/pages/api/tooltip.md +++ b/docs/pages/api/tooltip.md @@ -24,6 +24,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| +| arrow | bool | false | If `true`, adds an arrow to the tooltip. | | children * | element | | Tooltip reference element.
⚠️ [Needs to be able to hold a ref](/guides/composition/#caveat-with-refs). | | classes | object | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. | | disableFocusListener | bool | false | Do not respond to focus events. | @@ -57,7 +58,10 @@ Any other props supplied will be provided to the root element (native element). |:-----|:-------------|:------------| | popper | .MuiTooltip-popper | Styles applied to the Popper component. | popperInteractive | .MuiTooltip-popperInteractive | Styles applied to the Popper component if `interactive={true}`. +| popperArrow | .MuiTooltip-popperArrow | Styles applied to the Popper component if `arrow={true}`. | tooltip | .MuiTooltip-tooltip | Styles applied to the tooltip (label wrapper) element. +| tooltipArrow | .MuiTooltip-tooltipArrow | Styles applied to the tooltip (label wrapper) element if `arrow={true}`. +| arrow | .MuiTooltip-arrow | Styles applied to the arrow element. | touch | .MuiTooltip-touch | Styles applied to the tooltip (label wrapper) element if the tooltip is opened by touch. | tooltipPlacementLeft | .MuiTooltip-tooltipPlacementLeft | Styles applied to the tooltip (label wrapper) element if `placement` contains "left". | tooltipPlacementRight | .MuiTooltip-tooltipPlacementRight | Styles applied to the tooltip (label wrapper) element if `placement` contains "right". diff --git a/docs/src/pages/components/tooltips/ArrowTooltips.js b/docs/src/pages/components/tooltips/ArrowTooltips.js new file mode 100644 index 00000000000000..71dbf544b6e317 --- /dev/null +++ b/docs/src/pages/components/tooltips/ArrowTooltips.js @@ -0,0 +1,11 @@ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; + +export default function ArrowTooltips() { + return ( + + + + ); +} diff --git a/docs/src/pages/components/tooltips/ArrowTooltips.tsx b/docs/src/pages/components/tooltips/ArrowTooltips.tsx new file mode 100644 index 00000000000000..71dbf544b6e317 --- /dev/null +++ b/docs/src/pages/components/tooltips/ArrowTooltips.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; + +export default function ArrowTooltips() { + return ( + + + + ); +} diff --git a/docs/src/pages/components/tooltips/CustomizedTooltips.js b/docs/src/pages/components/tooltips/CustomizedTooltips.js index caa6a85bd44806..3ab92ce2bb8cc3 100644 --- a/docs/src/pages/components/tooltips/CustomizedTooltips.js +++ b/docs/src/pages/components/tooltips/CustomizedTooltips.js @@ -1,57 +1,9 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { withStyles, makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; import Typography from '@material-ui/core/Typography'; -function arrowGenerator(color) { - return { - '&[x-placement*="bottom"] $arrow': { - top: 0, - left: 0, - marginTop: '-0.95em', - width: '2em', - height: '1em', - '&::before': { - borderWidth: '0 1em 1em 1em', - borderColor: `transparent transparent ${color} transparent`, - }, - }, - '&[x-placement*="top"] $arrow': { - bottom: 0, - left: 0, - marginBottom: '-0.95em', - width: '2em', - height: '1em', - '&::before': { - borderWidth: '1em 1em 0 1em', - borderColor: `${color} transparent transparent transparent`, - }, - }, - '&[x-placement*="right"] $arrow': { - left: 0, - marginLeft: '-0.95em', - height: '2em', - width: '1em', - '&::before': { - borderWidth: '1em 1em 1em 0', - borderColor: `transparent ${color} transparent transparent`, - }, - }, - '&[x-placement*="left"] $arrow': { - right: 0, - marginRight: '-0.95em', - height: '2em', - width: '1em', - '&::before': { - borderWidth: '1em 0 1em 1em', - borderColor: `transparent transparent transparent ${color}`, - }, - }, - }; -} - const LightTooltip = withStyles(theme => ({ tooltip: { backgroundColor: theme.palette.common.white, @@ -61,121 +13,21 @@ const LightTooltip = withStyles(theme => ({ }, }))(Tooltip); -const useStylesArrow = makeStyles(theme => ({ - tooltip: { - position: 'relative', - }, - arrow: { - position: 'absolute', - fontSize: 6, - '&::before': { - content: '""', - margin: 'auto', - display: 'block', - width: 0, - height: 0, - borderStyle: 'solid', - }, - }, - popper: arrowGenerator(theme.palette.grey[700]), -})); - -function ArrowTooltip(props) { - const { arrow, ...classes } = useStylesArrow(); - const [arrowRef, setArrowRef] = React.useState(null); - - return ( - - {props.title} - - - } - /> - ); -} - -ArrowTooltip.propTypes = { - title: PropTypes.node, -}; - const useStylesBootstrap = makeStyles(theme => ({ arrow: { - position: 'absolute', - fontSize: 6, - '&::before': { - content: '""', - margin: 'auto', - display: 'block', - width: 0, - height: 0, - borderStyle: 'solid', - }, + color: theme.palette.common.black, }, - popper: arrowGenerator(theme.palette.common.black), tooltip: { - position: 'relative', backgroundColor: theme.palette.common.black, }, - tooltipPlacementLeft: { - margin: '0 8px', - }, - tooltipPlacementRight: { - margin: '0 8px', - }, - tooltipPlacementTop: { - margin: '8px 0', - }, - tooltipPlacementBottom: { - margin: '8px 0', - }, })); function BootstrapTooltip(props) { - const { arrow, ...classes } = useStylesBootstrap(); - const [arrowRef, setArrowRef] = React.useState(null); + const classes = useStylesBootstrap(); - return ( - - {props.title} - - - } - /> - ); + return ; } -BootstrapTooltip.propTypes = { - title: PropTypes.node, -}; - const HtmlTooltip = withStyles(theme => ({ tooltip: { backgroundColor: '#f5f5f9', @@ -192,9 +44,6 @@ export default function CustomizedTooltips() { - - - diff --git a/docs/src/pages/components/tooltips/CustomizedTooltips.tsx b/docs/src/pages/components/tooltips/CustomizedTooltips.tsx index ce947047eedb14..f5b6ed0e7a92c5 100644 --- a/docs/src/pages/components/tooltips/CustomizedTooltips.tsx +++ b/docs/src/pages/components/tooltips/CustomizedTooltips.tsx @@ -1,56 +1,9 @@ import React from 'react'; -import { withStyles, Theme, makeStyles, createStyles } from '@material-ui/core/styles'; +import { withStyles, Theme, makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import Tooltip, { TooltipProps } from '@material-ui/core/Tooltip'; import Typography from '@material-ui/core/Typography'; -function arrowGenerator(color: string) { - return { - '&[x-placement*="bottom"] $arrow': { - top: 0, - left: 0, - marginTop: '-0.95em', - width: '2em', - height: '1em', - '&::before': { - borderWidth: '0 1em 1em 1em', - borderColor: `transparent transparent ${color} transparent`, - }, - }, - '&[x-placement*="top"] $arrow': { - bottom: 0, - left: 0, - marginBottom: '-0.95em', - width: '2em', - height: '1em', - '&::before': { - borderWidth: '1em 1em 0 1em', - borderColor: `${color} transparent transparent transparent`, - }, - }, - '&[x-placement*="right"] $arrow': { - left: 0, - marginLeft: '-0.95em', - height: '2em', - width: '1em', - '&::before': { - borderWidth: '1em 1em 1em 0', - borderColor: `transparent ${color} transparent transparent`, - }, - }, - '&[x-placement*="left"] $arrow': { - right: 0, - marginRight: '-0.95em', - height: '2em', - width: '1em', - '&::before': { - borderWidth: '1em 0 1em 1em', - borderColor: `transparent transparent transparent ${color}`, - }, - }, - }; -} - const LightTooltip = withStyles((theme: Theme) => ({ tooltip: { backgroundColor: theme.palette.common.white, @@ -60,115 +13,19 @@ const LightTooltip = withStyles((theme: Theme) => ({ }, }))(Tooltip); -const useStylesArrow = makeStyles((theme: Theme) => - createStyles({ - tooltip: { - position: 'relative', - }, - arrow: { - position: 'absolute', - fontSize: 6, - '&::before': { - content: '""', - margin: 'auto', - display: 'block', - width: 0, - height: 0, - borderStyle: 'solid', - }, - }, - popper: arrowGenerator(theme.palette.grey[700]), - }), -); - -function ArrowTooltip(props: TooltipProps) { - const { arrow, ...classes } = useStylesArrow(); - const [arrowRef, setArrowRef] = React.useState(null); - - return ( - - {props.title} - - - } - /> - ); -} - -const useStylesBootstrap = makeStyles((theme: Theme) => - createStyles({ - arrow: { - position: 'absolute', - fontSize: 6, - '&::before': { - content: '""', - margin: 'auto', - display: 'block', - width: 0, - height: 0, - borderStyle: 'solid', - }, - }, - popper: arrowGenerator(theme.palette.common.black), - tooltip: { - position: 'relative', - backgroundColor: theme.palette.common.black, - }, - tooltipPlacementLeft: { - margin: '0 8px', - }, - tooltipPlacementRight: { - margin: '0 8px', - }, - tooltipPlacementTop: { - margin: '8px 0', - }, - tooltipPlacementBottom: { - margin: '8px 0', - }, - }), -); +const useStylesBootstrap = makeStyles((theme: Theme) => ({ + arrow: { + color: theme.palette.common.black, + }, + tooltip: { + backgroundColor: theme.palette.common.black, + }, +})); function BootstrapTooltip(props: TooltipProps) { - const { arrow, ...classes } = useStylesBootstrap(); - const [arrowRef, setArrowRef] = React.useState(null); + const classes = useStylesBootstrap(); - return ( - - {props.title} - - - } - /> - ); + return ; } const HtmlTooltip = withStyles((theme: Theme) => ({ @@ -187,9 +44,6 @@ export default function CustomizedTooltips() { - - - diff --git a/docs/src/pages/components/tooltips/InteractiveTooltips.js b/docs/src/pages/components/tooltips/InteractiveTooltips.js index 1b7ff5269877ca..aea7fbef116666 100644 --- a/docs/src/pages/components/tooltips/InteractiveTooltips.js +++ b/docs/src/pages/components/tooltips/InteractiveTooltips.js @@ -1,25 +1,11 @@ import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; -const useStyles = makeStyles(theme => ({ - button: { - margin: theme.spacing(1), - }, -})); - export default function InteractiveTooltips() { - const classes = useStyles(); - return ( -
- - - - - - -
+ + + ); } diff --git a/docs/src/pages/components/tooltips/InteractiveTooltips.tsx b/docs/src/pages/components/tooltips/InteractiveTooltips.tsx index 428efbf477be3e..aea7fbef116666 100644 --- a/docs/src/pages/components/tooltips/InteractiveTooltips.tsx +++ b/docs/src/pages/components/tooltips/InteractiveTooltips.tsx @@ -1,27 +1,11 @@ import React from 'react'; -import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; -const useStyles = makeStyles((theme: Theme) => - createStyles({ - button: { - margin: theme.spacing(1), - }, - }), -); - export default function InteractiveTooltips() { - const classes = useStyles(); - return ( -
- - - - - - -
+ + + ); } diff --git a/docs/src/pages/components/tooltips/tooltips.md b/docs/src/pages/components/tooltips/tooltips.md index 529ff1e4a42b36..16b5b4e6d3e566 100644 --- a/docs/src/pages/components/tooltips/tooltips.md +++ b/docs/src/pages/components/tooltips/tooltips.md @@ -27,16 +27,22 @@ Here are some examples of customizing the component. You can learn more about th {{"demo": "pages/components/tooltips/CustomizedTooltips.js"}} +## Arrow Tooltips + +You can use the `arrow` prop to give your tooltip an arrow indicating which element it refers to. + +{{"demo": "pages/components/tooltips/ArrowTooltips.js"}} + ## Custom child element The tooltip needs to apply DOM event listeners to its child element. If the child is a custom React element, you need to make sure that it spreads its properties to the underlying DOM element. ```jsx -function MyComponent(props) { - // Spread the properties to the underlying DOM element. - return
Bin
-} +const MyComponent = React.forwardRef(function MyComponent(props, ref) { + // Spread the props to the underlying DOM element. + return
Bin
+}); // ... diff --git a/packages/material-ui/src/Tooltip/Tooltip.d.ts b/packages/material-ui/src/Tooltip/Tooltip.d.ts index b405267eb110fc..4347a73c24fa87 100644 --- a/packages/material-ui/src/Tooltip/Tooltip.d.ts +++ b/packages/material-ui/src/Tooltip/Tooltip.d.ts @@ -5,6 +5,7 @@ import { PopperProps } from '../Popper/Popper'; export interface TooltipProps extends StandardProps, TooltipClassKey, 'title'> { + arrow?: boolean; children: React.ReactElement; disableFocusListener?: boolean; disableHoverListener?: boolean; diff --git a/packages/material-ui/src/Tooltip/Tooltip.js b/packages/material-ui/src/Tooltip/Tooltip.js index 54ec52c68a07c7..d7e7ee85e9aa41 100644 --- a/packages/material-ui/src/Tooltip/Tooltip.js +++ b/packages/material-ui/src/Tooltip/Tooltip.js @@ -17,6 +17,53 @@ function round(value) { return Math.round(value * 1e5) / 1e5; } +function arrowGenerator() { + return { + '&[x-placement*="bottom"] $arrow': { + top: 0, + left: 0, + marginTop: '-0.95em', + width: '2em', + height: '1em', + '&::before': { + borderWidth: '0 1em 1em 1em', + borderColor: 'transparent transparent currentcolor transparent', + }, + }, + '&[x-placement*="top"] $arrow': { + bottom: 0, + left: 0, + marginBottom: '-0.95em', + width: '2em', + height: '1em', + '&::before': { + borderWidth: '1em 1em 0 1em', + borderColor: 'currentcolor transparent transparent transparent', + }, + }, + '&[x-placement*="right"] $arrow': { + left: 0, + marginLeft: '-0.95em', + height: '2em', + width: '1em', + '&::before': { + borderWidth: '1em 1em 1em 0', + borderColor: 'transparent currentcolor transparent transparent', + }, + }, + '&[x-placement*="left"] $arrow': { + right: 0, + marginRight: '-0.95em', + height: '2em', + width: '1em', + '&::before': { + borderWidth: '1em 0 1em 1em', + borderColor: 'transparent transparent transparent currentcolor', + }, + }, + }; +} + export const styles = theme => ({ /* Styles applied to the Popper component. */ popper: { @@ -28,6 +75,8 @@ export const styles = theme => ({ popperInteractive: { pointerEvents: 'auto', }, + /* Styles applied to the Popper component if `arrow={true}`. */ + popperArrow: arrowGenerator(), /* Styles applied to the tooltip (label wrapper) element. */ tooltip: { backgroundColor: fade(theme.palette.grey[700], 0.9), @@ -41,6 +90,25 @@ export const styles = theme => ({ wordWrap: 'break-word', fontWeight: theme.typography.fontWeightMedium, }, + /* Styles applied to the tooltip (label wrapper) element if `arrow={true}`. */ + tooltipArrow: { + position: 'relative', + margin: '0', + }, + /* Styles applied to the arrow element. */ + arrow: { + position: 'absolute', + fontSize: 6, + color: fade(theme.palette.grey[700], 0.9), + '&::before': { + content: '""', + margin: 'auto', + display: 'block', + width: 0, + height: 0, + borderStyle: 'solid', + }, + }, /* Styles applied to the tooltip (label wrapper) element if the tooltip is opened by touch. */ touch: { padding: '8px 16px', @@ -84,6 +152,7 @@ export const styles = theme => ({ const Tooltip = React.forwardRef(function Tooltip(props, ref) { const { + arrow = false, children, classes, disableFocusListener = false, @@ -110,6 +179,7 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) { const [openState, setOpenState] = React.useState(false); const [, forceUpdate] = React.useState(0); const [childNode, setChildNode] = React.useState(); + const [arrowRef, setArrowRef] = React.useState(null); const ignoreNonTouchEvents = React.useRef(false); const { current: isControlled } = React.useRef(openProp != null); const defaultId = React.useRef(); @@ -395,12 +465,21 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) { @@ -415,11 +494,13 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) { classes.tooltip, { [classes.touch]: ignoreNonTouchEvents.current, + [classes.tooltipArrow]: arrow, }, classes[`tooltipPlacement${capitalize(placementInner.split('-')[0])}`], )} > {title} + {arrow ? : null} )} @@ -429,6 +510,10 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) { }); Tooltip.propTypes = { + /** + * If `true`, adds an arrow to the tooltip. + */ + arrow: PropTypes.bool, /** * Tooltip reference element. */