From 550bc70d0b97592ff8f1a62b4aca41fbbc319ef9 Mon Sep 17 00:00:00 2001 From: emyarod Date: Tue, 25 Feb 2020 16:57:22 -0600 Subject: [PATCH] feat(tooltip): add support for hoverable definition tooltip body (#5323) --- .../tooltip/tooltip--definition.hbs | 9 +++-- .../src/components/tooltip/tooltip--simple.js | 38 +++++++++++++++---- .../components/src/globals/scss/_tooltip.scss | 3 +- .../tests/spec/tooltip--simple_spec.js | 18 +++++++++ .../TooltipDefinition/TooltipDefinition.js | 15 +++++++- .../TooltipDefinition-test.js.snap | 3 ++ 6 files changed, 71 insertions(+), 15 deletions(-) diff --git a/packages/components/src/components/tooltip/tooltip--definition.hbs b/packages/components/src/components/tooltip/tooltip--definition.hbs index ff04e9c04fc7..46c36966a894 100644 --- a/packages/components/src/components/tooltip/tooltip--definition.hbs +++ b/packages/components/src/components/tooltip/tooltip--definition.hbs @@ -10,7 +10,8 @@ class="{{@root.prefix}}--tooltip__trigger {{@root.prefix}}--tooltip--a11y {{@root.prefix}}--tooltip__trigger--definition {{@root.prefix}}--tooltip--bottom {{@root.prefix}}--tooltip--align-start"> Definition Tooltip (start aligned) -
@@ -19,7 +20,8 @@ class="{{@root.prefix}}--tooltip__trigger {{@root.prefix}}--tooltip--a11y {{@root.prefix}}--tooltip__trigger--definition {{@root.prefix}}--tooltip--bottom {{@root.prefix}}--tooltip--align-center"> Definition Tooltip (center aligned) -
@@ -28,6 +30,7 @@ class="{{@root.prefix}}--tooltip__trigger {{@root.prefix}}--tooltip--a11y {{@root.prefix}}--tooltip__trigger--definition {{@root.prefix}}--tooltip--bottom {{@root.prefix}}--tooltip--align-end"> Definition Tooltip (end aligned) - diff --git a/packages/components/src/components/tooltip/tooltip--simple.js b/packages/components/src/components/tooltip/tooltip--simple.js index a834f9b0499c..175086520dd6 100644 --- a/packages/components/src/components/tooltip/tooltip--simple.js +++ b/packages/components/src/components/tooltip/tooltip--simple.js @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import debounce from 'lodash.debounce'; import settings from '../../globals/js/settings'; import mixin from '../../globals/js/misc/mixin'; import createComponent from '../../globals/js/mixins/create-component'; @@ -32,16 +33,28 @@ export default class TooltipSimple extends mixin( // ESC if (event.which === 27) { this.allowTooltipVisibility({ visible: false }); + const tooltipTriggerButton = this.getTooltipTriggerButton(); + if (tooltipTriggerButton) { + tooltipTriggerButton.classList.remove( + this.options.classTooltipVisible + ); + } } }) ); this.manage( - on(this.element, 'mouseenter', () => - this.allowTooltipVisibility({ visible: true }) - ) + on(this.element, 'mouseenter', () => { + this.tooltipFadeOut.cancel(); + this.allowTooltipVisibility({ visible: true }); + const tooltipTriggerButton = this.getTooltipTriggerButton(); + if (tooltipTriggerButton) { + tooltipTriggerButton.classList.add(this.options.classTooltipVisible); + } + }) ); + this.manage(on(this.element, 'mouseleave', this.tooltipFadeOut)); this.manage( - on(this.element, 'focus', event => { + on(this.element, 'focusin', event => { if (eventMatches(event, this.options.selectorTriggerButton)) { this.allowTooltipVisibility({ visible: true }); } @@ -49,13 +62,21 @@ export default class TooltipSimple extends mixin( ); } - allowTooltipVisibility = ({ visible }) => { - const tooltipTriggerButton = this.element.matches( - this.options.selectorTriggerButton - ) + tooltipFadeOut = debounce(() => { + const tooltipTriggerButton = this.getTooltipTriggerButton(); + if (tooltipTriggerButton) { + tooltipTriggerButton.classList.remove(this.options.classTooltipVisible); + } + }, 100); + + getTooltipTriggerButton = () => + this.element.matches(this.options.selectorTriggerButton) ? this.element : this.element.querySelector(this.options.selectorTriggerButton); + allowTooltipVisibility = ({ visible }) => { + const tooltipTriggerButton = this.getTooltipTriggerButton(); + if (!tooltipTriggerButton) { return; } @@ -83,6 +104,7 @@ export default class TooltipSimple extends mixin( selectorInit: '[data-tooltip-definition],[data-tooltip-icon]', selectorTriggerButton: `.${prefix}--tooltip__trigger.${prefix}--tooltip--a11y`, classTooltipHidden: `${prefix}--tooltip--hidden`, + classTooltipVisible: `${prefix}--tooltip--visible`, }; } diff --git a/packages/components/src/globals/scss/_tooltip.scss b/packages/components/src/globals/scss/_tooltip.scss index 6b491aeeabe4..d8a04afbe04f 100644 --- a/packages/components/src/globals/scss/_tooltip.scss +++ b/packages/components/src/globals/scss/_tooltip.scss @@ -40,7 +40,6 @@ font-weight: 400; text-align: left; transform: translateX(-50%); - pointer-events: none; background-color: $inverse-02; @include type-style('body-short-01'); @@ -97,7 +96,6 @@ display: flex; align-items: center; opacity: 0; - pointer-events: none; // IE media query @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { @@ -150,6 +148,7 @@ content: none; } + &.#{$prefix}--tooltip--visible, &:hover, &:focus { &::before, diff --git a/packages/components/tests/spec/tooltip--simple_spec.js b/packages/components/tests/spec/tooltip--simple_spec.js index 8f95871f9005..21698e188d31 100644 --- a/packages/components/tests/spec/tooltip--simple_spec.js +++ b/packages/components/tests/spec/tooltip--simple_spec.js @@ -1,3 +1,4 @@ +import Promise, { delay } from 'bluebird'; import Tooltip from '../../src/components/tooltip/tooltip--simple'; import TooltipDefinitionHTML from '../../html/tooltip/tooltip--definition.html'; import TooltipIconHTML from '../../html/tooltip/tooltip--icon.html'; @@ -88,6 +89,23 @@ describe('Test simple tooltip', function() { expect(element.classList.contains('bx--tooltip--hidden')).toBe(false); }); + it('Should have visible class after mouseenter', function() { + element.dispatchEvent(new CustomEvent('mouseenter', { bubbles: true })); + expect(element.classList.contains('bx--tooltip--visible')).toBe(true); + }); + + it('Should not have visible class after mouseleave', async function() { + await new Promise(resolve => { + resolve( + element.dispatchEvent( + new CustomEvent('mouseleave', { bubbles: true }) + ) + ); + }); + await delay(100); + expect(element.classList.contains('bx--tooltip--visible')).toBe(false); + }); + it('Should not have hidden class after focus', function() { element.dispatchEvent(new CustomEvent('focus', { bubbles: true })); expect(element.classList.contains('bx--tooltip--hidden')).toBe(false); diff --git a/packages/react/src/components/TooltipDefinition/TooltipDefinition.js b/packages/react/src/components/TooltipDefinition/TooltipDefinition.js index 000c1deeff4b..7d8655be810b 100644 --- a/packages/react/src/components/TooltipDefinition/TooltipDefinition.js +++ b/packages/react/src/components/TooltipDefinition/TooltipDefinition.js @@ -9,6 +9,7 @@ import cx from 'classnames'; import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { settings } from 'carbon-components'; +import debounce from 'lodash.debounce'; import setupGetInstanceId from '../../tools/setupGetInstanceId'; import { composeEventHandlers } from '../../tools/events'; import { keys, matches } from '../../internal/keyboard'; @@ -24,10 +25,12 @@ const TooltipDefinition = ({ align, onFocus, onMouseEnter, + onMouseLeave, tooltipText, ...rest }) => { const [allowTooltipVisibility, setAllowTooltipVisibility] = useState(true); + const [tooltipVisible, setTooltipVisible] = useState(false); const tooltipId = id || `definition-tooltip-${getInstanceId()}`; const tooltipClassName = cx( `${prefix}--tooltip--definition`, @@ -43,10 +46,17 @@ const TooltipDefinition = ({ [`${prefix}--tooltip--${direction}`]: direction, [`${prefix}--tooltip--align-${align}`]: align, [`${prefix}--tooltip--hidden`]: !allowTooltipVisibility, + [`${prefix}--tooltip--visible`]: tooltipVisible, } ); + const debounceTooltipVisible = debounce(() => setTooltipVisible(false), 100); const handleFocus = () => setAllowTooltipVisibility(true); - const handleMouseEnter = () => setAllowTooltipVisibility(true); + const handleMouseEnter = () => { + debounceTooltipVisible.cancel(); + setAllowTooltipVisibility(true); + setTooltipVisible(true); + }; + const handleMouseLeave = debounceTooltipVisible; useEffect(() => { const handleEscKeyDown = event => { if (matches(event, [keys.Escape])) { @@ -61,7 +71,8 @@ const TooltipDefinition = ({
+ onMouseEnter={composeEventHandlers([onMouseEnter, handleMouseEnter])} + onMouseLeave={composeEventHandlers([onMouseLeave, handleMouseLeave])}>