diff --git a/packages/design-system-docs/src/pages/components/Tooltip/Tooltip.example.jsx b/packages/design-system-docs/src/pages/components/Tooltip/Tooltip.example.jsx
index 5f1a03f662..523f2ce437 100644
--- a/packages/design-system-docs/src/pages/components/Tooltip/Tooltip.example.jsx
+++ b/packages/design-system-docs/src/pages/components/Tooltip/Tooltip.example.jsx
@@ -12,25 +12,19 @@ ReactDOM.render(
triggerClassName="ds-c-tooltip__trigger-icon"
triggerActiveClassName="ds-c-tooltip-icon--active"
>
-
for the trigger content'}
Tooltip using a
- Tooltip trigger is styled with dashed underline
+ Tooltip trigger is styled with dashed underline
Tooltip with
-
-
+
+ <>
{
'Tooltip remains active when the mouse hovers over the tooltip body. Tooltip can contain '
}
@@ -38,7 +32,7 @@ ReactDOM.render(
links
{' and other interactive content'}
-
+ >
@@ -48,7 +42,7 @@ ReactDOM.render(
triggerContent="placement"
triggerClassName="ds-c-tooltip__trigger-link"
>
-
Tooltip positioned on the right
+ Tooltip positioned on the right
@@ -58,17 +52,12 @@ ReactDOM.render(
triggerContent="offset"
triggerClassName="ds-c-tooltip__trigger-link"
>
-
Tooltip positioned with custom offset
+ Tooltip positioned with custom offset
Tooltip dialog activated
-
+
<>
{
@@ -93,13 +82,12 @@ ReactDOM.render(
}
triggerClassName="ds-c-tooltip__trigger-icon"
triggerActiveClassName="ds-c-tooltip-icon--active"
>
-
Inverse tooltip styles applied
+ Inverse tooltip styles applied
>,
diff --git a/packages/design-system/src/components/Tooltip/Tooltip.jsx b/packages/design-system/src/components/Tooltip/Tooltip.jsx
index 2354497428..64c1e33aae 100644
--- a/packages/design-system/src/components/Tooltip/Tooltip.jsx
+++ b/packages/design-system/src/components/Tooltip/Tooltip.jsx
@@ -26,7 +26,11 @@ export class Tooltip extends React.Component {
this.tooltipElement = elem;
};
- this.state = { active: false };
+ this.state = {
+ active: false,
+ isHover: false,
+ isMobile: false,
+ };
}
componentDidMount() {
@@ -50,10 +54,11 @@ export class Tooltip extends React.Component {
}
handleClickOutside(event) {
- // Closes click only tooltips when mouse clicks outside of tooltip container element
- if (this.state.active && this.props.dialog) {
+ // Closes dialog and mobile tooltips when mouse clicks outside of tooltip element
+ if (this.state.active && (this.props.dialog || this.state.isMobile)) {
+ const clickedTrigger = this.triggerElement && this.triggerElement.contains(event.target);
const clickedTooltip = this.tooltipElement && this.tooltipElement.contains(event.target);
- if (!clickedTooltip) {
+ if (!clickedTooltip && !clickedTrigger) {
this.setTooltipActive(false);
}
}
@@ -68,24 +73,40 @@ export class Tooltip extends React.Component {
}
setTooltipActive(active) {
- this.setState({ active }, () => {
- this.popper.forceUpdate();
- });
+ if (active !== this.state.active) {
+ this.setState({ active }, () => {
+ this.popper.forceUpdate();
+ if (active) {
+ this.props.onOpen && this.props.onOpen();
+ } else {
+ this.props.onClose && this.props.onClose();
+ }
+ });
+ }
}
handleBlur() {
// Hide tooltips when blurring away from the trigger or tooltip body
+ // and when the mouse is not hovering over the tooltip
setTimeout(() => {
const focusedInsideTrigger =
this.triggerElement && this.triggerElement.contains(document.activeElement);
const focusedInsideTooltip =
this.tooltipElement && this.tooltipElement.contains(document.activeElement);
- if (!focusedInsideTrigger && !focusedInsideTooltip) {
+ if (!focusedInsideTrigger && !focusedInsideTooltip && !this.state.isHover) {
this.setTooltipActive(false);
}
}, 10);
}
+ handleTouch() {
+ // On mobile, touch -> mouseenter -> click events can all be fired simultaneously
+ // `isMobile` flag is used inside onClick and onMouseEnter handlers, so touch events can be used in isolation on mobile
+ // https://stackoverflow.com/a/65055198
+ this.setState({ isMobile: true });
+ this.setTooltipActive(!this.state.active);
+ }
+
triggerComponentType() {
let component = this.props.triggerComponent;
if (component === 'button' && this.props.triggerHref) {
@@ -97,8 +118,8 @@ export class Tooltip extends React.Component {
renderTrigger() {
const {
- dialog,
ariaLabel,
+ dialog,
triggerActiveClassName,
triggerClassName,
triggerContent,
@@ -108,30 +129,37 @@ export class Tooltip extends React.Component {
const TriggerComponent = this.triggerComponentType();
const triggerClasses = classNames('ds-base', 'ds-c-tooltip__trigger', triggerClassName, {
[triggerActiveClassName]: this.state.active,
- 'ds-c-tooltip__trigger--click-only-active': this.props.dialog && this.state.active,
});
const eventHandlers = dialog
? {
- onClick: () => this.setTooltipActive(!this.state.active),
+ onTouchStart: () => this.handleTouch(),
+ onClick: () => {
+ if (!this.state.isMobile) {
+ this.setTooltipActive(!this.state.active);
+ }
+ },
}
: {
- onTouchStart: () => this.setTooltipActive(!this.state.active),
+ onTouchStart: () => this.handleTouch(),
+ onClick: () => {
+ if (!this.state.isMobile) {
+ this.setTooltipActive(!this.state.active);
+ }
+ },
onFocus: () => this.setTooltipActive(true),
onBlur: () => this.handleBlur(),
- onMouseEnter: () => this.setTooltipActive(true),
- onMouseLeave: () => this.setTooltipActive(false),
};
return (
{triggerContent}
@@ -143,7 +171,6 @@ export class Tooltip extends React.Component {
dialog,
children,
inversed,
- interactive,
interactiveBorder,
placement,
className,
@@ -160,6 +187,12 @@ export class Tooltip extends React.Component {
zIndex: '-999', // ensures interactive border doesnt cover tooltip content
};
+ const eventHandlers = dialog
+ ? {}
+ : {
+ onBlur: () => this.handleBlur(),
+ };
+
const tooltipContent = () => (
(interactive ? this.setTooltipActive(true) : null)}
- onMouseLeave={() => (dialog ? null : this.setTooltipActive(false))}
- onBlur={() => this.handleBlur()}
data-placement={placement}
aria-hidden={!this.state.active}
role={dialog ? 'dialog' : 'tooltip'}
+ {...eventHandlers}
>
{children}
- {interactive && (
+ {!dialog && (
)}
@@ -195,6 +226,7 @@ export class Tooltip extends React.Component {
focusTrapOptions={{
// Set initialFocus to the tooltip container element in case it contains no focusable elements
initialFocus: `#${this.id}`,
+ clickOutsideDeactivates: true,
}}
>
{tooltipContent()}
@@ -207,11 +239,28 @@ export class Tooltip extends React.Component {
}
render() {
+ const eventHandlers = this.props.dialog
+ ? {}
+ : {
+ onMouseEnter: () => {
+ if (!this.state.isMobile) {
+ this.setState({ isHover: true });
+ this.setTooltipActive(true);
+ }
+ },
+ onMouseLeave: () => {
+ if (!this.state.isMobile) {
+ this.setState({ isHover: false });
+ this.setTooltipActive(false);
+ }
+ },
+ };
+
return (
-
+
{this.renderTrigger()}
{this.renderContent()}
-
+
);
}
}
@@ -246,10 +295,6 @@ Tooltip.propTypes = {
* `id` applied to tooltip body container element. If not provided, a unique id will be automatically generated and used.
*/
id: PropTypes.string,
- /**
- * Set to `true` if the tooltip content contains tabbable, interactive elements like links or buttons. This prop expands the activation area to include the tooltip itself, allowing the content to interact with mouse events.
- */
- interactive: PropTypes.bool,
/**
* Sets the size of the invisible border around interactive tooltips that prevents it from immediately hiding when the cursor leaves the tooltip.
*/
@@ -259,6 +304,14 @@ Tooltip.propTypes = {
* Applies `skidding` and `distance` offsets to the tooltip relative to the trigger. See the [`popperjs` docs](https://popper.js.org/docs/v2/modifiers/popper-offsets/) for more info.
*/
offset: PropTypes.arrayOf(PropTypes.number),
+ /**
+ * Called when the tooltip is hidden
+ */
+ onClose: PropTypes.func,
+ /**
+ * Called when the tooltip is shown
+ */
+ onOpen: PropTypes.func,
/**
* Placement of the tooltip body relative to the trigger. See the [`popperjs` docs](https://popper.js.org/docs/v2/constructors/#options) for more info.
*/
diff --git a/packages/design-system/src/components/Tooltip/__snapshots__/Tooltip.test.jsx.snap b/packages/design-system/src/components/Tooltip/__snapshots__/Tooltip.test.jsx.snap
index fd698c7969..536b096578 100644
--- a/packages/design-system/src/components/Tooltip/__snapshots__/Tooltip.test.jsx.snap
+++ b/packages/design-system/src/components/Tooltip/__snapshots__/Tooltip.test.jsx.snap
@@ -1,16 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Tooltip renders custom trigger component 1`] = `
-
+
-
+
`;
exports[`Tooltip renders default trigger icon 1`] = `
-
+
@@ -81,8 +94,6 @@ exports[`Tooltip renders default trigger icon 1`] = `
data-placement="top"
id="trigger_1"
onBlur={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
role="tooltip"
style={
Object {
@@ -105,18 +116,30 @@ exports[`Tooltip renders default trigger icon 1`] = `
Tooltip body content
+
-
+
`;
exports[`Tooltip renders dialog tooltip 1`] = `
-
+
@@ -131,6 +154,7 @@ exports[`Tooltip renders dialog tooltip 1`] = `
active={false}
focusTrapOptions={
Object {
+ "clickOutsideDeactivates": true,
"initialFocus": "#trigger_5",
}
}
@@ -141,9 +165,6 @@ exports[`Tooltip renders dialog tooltip 1`] = `
className="ds-c-tooltip"
data-placement="top"
id="trigger_5"
- onBlur={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
role="dialog"
style={
Object {
@@ -169,19 +190,21 @@ exports[`Tooltip renders dialog tooltip 1`] = `
-
+
`;
exports[`Tooltip renders interactive tooltip 1`] = `
-
+
@@ -198,8 +221,6 @@ exports[`Tooltip renders interactive tooltip 1`] = `
data-placement="top"
id="trigger_3"
onBlur={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
role="tooltip"
style={
Object {
@@ -235,19 +256,21 @@ exports[`Tooltip renders interactive tooltip 1`] = `
/>
-
+
`;
exports[`Tooltip renders inverse tooltip 1`] = `
-
+
@@ -264,8 +287,6 @@ exports[`Tooltip renders inverse tooltip 1`] = `
data-placement="top"
id="trigger_2"
onBlur={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
role="tooltip"
style={
Object {
@@ -288,7 +309,18 @@ exports[`Tooltip renders inverse tooltip 1`] = `
Tooltip body content
+
-
+
`;
diff --git a/packages/design-system/src/styles/components/_Tooltip.scss b/packages/design-system/src/styles/components/_Tooltip.scss
index 007436fb36..47cc43216a 100644
--- a/packages/design-system/src/styles/components/_Tooltip.scss
+++ b/packages/design-system/src/styles/components/_Tooltip.scss
@@ -22,18 +22,13 @@ $tooltip-background-color-inverse: $color-background !default;
// Tooltip trigger style
.ds-c-tooltip__trigger {
cursor: pointer;
-}
-
-// Prevent the trigger from reactivating the tooltip when clicking outside of tooltipDialog tooltips
-.ds-c-tooltip__trigger--click-only-active {
- pointer-events: none;
+ display: inline;
+ font-size: inherit;
+ font-weight: inherit;
}
.ds-c-tooltip__trigger-icon {
@extend %trigger-reset-styles;
- align-items: center;
- display: flex;
- justify-content: center;
padding: 4px;
}
@@ -68,6 +63,14 @@ $tooltip-background-color-inverse: $color-background !default;
font-size: $small-font-size;
font-weight: 400;
padding: $spacer-1;
+
+ // Remove tooltip content padding by default
+ &:first-child {
+ margin-top: 0;
+ }
+ &:last-child {
+ margin-bottom: 0;
+ }
}
// The invisible area around the tooltip container that keeps the tooltip visible on hover
diff --git a/packages/design-system/src/styles/components/_TooltipIcon.scss b/packages/design-system/src/styles/components/_TooltipIcon.scss
index b2303e8a03..09d3ec891f 100644
--- a/packages/design-system/src/styles/components/_TooltipIcon.scss
+++ b/packages/design-system/src/styles/components/_TooltipIcon.scss
@@ -6,6 +6,7 @@
display: inline-block;
height: $tooltip-plus-border-size;
position: relative;
+ vertical-align: middle;
width: $tooltip-plus-border-size;
&:hover {