Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tooltip): add support for hoverable definition tooltip body #5323

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
</button>
<div class="bx--assistive-text" id="example-start" role="tooltip">Brief description of the dotted, underlined word
<div class="{{@root.prefix}}--assistive-text" id="example-start" role="tooltip">Brief description of the dotted,
underlined word
above.</div>
</div>
<br>
Expand All @@ -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)
</button>
<div class="bx--assistive-text" id="example-center" role="tooltip">Brief description of the dotted, underlined word
<div class="{{@root.prefix}}--assistive-text" id="example-center" role="tooltip">Brief description of the dotted,
underlined word
above.</div>
</div>
<br>
Expand All @@ -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)
</button>
<div class="bx--assistive-text" id="example-end" role="tooltip">Brief description of the dotted, underlined word
<div class="{{@root.prefix}}--assistive-text" id="example-end" role="tooltip">Brief description of the dotted,
underlined word
above.</div>
</div>
38 changes: 30 additions & 8 deletions packages/components/src/components/tooltip/tooltip--simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -32,30 +33,50 @@ 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 });
}
})
);
}

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;
}
Expand Down Expand Up @@ -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`,
};
}

Expand Down
3 changes: 1 addition & 2 deletions packages/components/src/globals/scss/_tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -150,6 +148,7 @@
content: none;
}

&.#{$prefix}--tooltip--visible,
&:hover,
&:focus {
&::before,
Expand Down
18 changes: 18 additions & 0 deletions packages/components/tests/spec/tooltip--simple_spec.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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`,
Expand All @@ -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);
Copy link
Contributor

@joshblack joshblack Feb 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to debounce the exit? Are there any timing windows that we should be hitting for delays? Curious when we'd use 100/300/etc 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the 100ms delay value is arbitrary but debouncing the exit is what resolves the original issue. afaik there is no guidance on the delay since the guidelines are still WIP w3c/aria-practices#127 w3c/aria-practices#128

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])) {
Expand All @@ -61,7 +71,8 @@ const TooltipDefinition = ({
<div
{...rest}
className={tooltipClassName}
onMouseEnter={composeEventHandlers([onMouseEnter, handleMouseEnter])}>
onMouseEnter={composeEventHandlers([onMouseEnter, handleMouseEnter])}
onMouseLeave={composeEventHandlers([onMouseLeave, handleMouseLeave])}>
<button
className={tooltipTriggerClasses}
aria-describedby={tooltipId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exports[`TooltipDefinition should allow the user to specify the direction 1`] =
<div
className="bx--tooltip--definition bx--tooltip--a11y custom-class"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-describedby="definition-tooltip-3"
Expand Down Expand Up @@ -39,6 +40,7 @@ exports[`TooltipDefinition should render 1`] = `
<div
className="bx--tooltip--definition bx--tooltip--a11y custom-class"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-describedby="definition-tooltip-1"
Expand Down Expand Up @@ -69,6 +71,7 @@ exports[`TooltipDefinition should support a custom trigger element class 1`] = `
<div
className="bx--tooltip--definition bx--tooltip--a11y custom-class"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-describedby="definition-tooltip-2"
Expand Down