Skip to content

Commit

Permalink
Merge pull request #5919 from uswds/al-tooltip-hoverable
Browse files Browse the repository at this point in the history
USWDS - Tooltip: Make tooltip content hoverable
  • Loading branch information
thisisdano committed May 22, 2024
2 parents e9bd88e + 1cbd67f commit 114f6e5
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 7 deletions.
10 changes: 9 additions & 1 deletion packages/usa-tooltip/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ const tooltip = behavior(
showToolTip(body, trigger, trigger.dataset.position);
},
},
"mouseout focusout": {
focusout: {
[TOOLTIP_TRIGGER](e) {
const { body } = getTooltipElements(e.target);

Expand All @@ -407,6 +407,14 @@ const tooltip = behavior(
init(root) {
selectOrMatches(TOOLTIP, root).forEach((tooltipTrigger) => {
setUpAttributes(tooltipTrigger);

const { body, wrapper } = getTooltipElements(tooltipTrigger);
wrapper.addEventListener("mouseleave", () => hideToolTip(body));
});
},
teardown(root) {
selectOrMatches(TOOLTIP, root).forEach((tooltipWrapper) => {
tooltipWrapper.removeEventListener("mouseleave", hideToolTip);
});
},
setup: setUpAttributes,
Expand Down
59 changes: 53 additions & 6 deletions packages/usa-tooltip/src/styles/_usa-tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@
// Variables
$triangle-size: 5px;

/// Create a spacer to increase target area for tooltip triangle.
///
/// @param {String} $direction - The direction of the tooltip; can be top, bottom, left, right.
///
/// @example
/// @include tooltip-spacer("top");
///
/// @output
/// .usa-tooltip__body--top::before {
/// top: 100%;
/// height: 5px;
/// left: 0;
/// right: 0;
/// }
@mixin tooltip-spacer($direction) {
&::before {
#{$direction}: 100%;

@if ($direction == "left") or ($direction == "right") {
bottom: 0;
top: 0;
width: $triangle-size;
} @else {
height: $triangle-size;
left: 0;
right: 0;
}
}
}

/* Tooltips */
.usa-tooltip {
display: inline-block;
Expand All @@ -28,19 +58,17 @@ $triangle-size: 5px;
font-size: size("ui", $theme-tooltip-font-size);
opacity: 0; // Required for recalculating position.
padding: units(1);
pointer-events: none;
width: auto;
white-space: pre;
z-index: 100000;
position: absolute;
/* positioning is completed with JS */

&:after {
&::after {
content: "";
display: block;
width: 0;
height: 0;
pointer-events: none;
border-left: $triangle-size solid transparent;
border-right: $triangle-size solid transparent;
border-top: $triangle-size solid color($theme-tooltip-background-color);
Expand All @@ -49,6 +77,15 @@ $triangle-size: 5px;
left: 50%;
margin-left: -$triangle-size;
}

// This pseudo element fills the gap between the tooltip trigger and body.
// Filling this gap allows the tooltip to stay open when the pointer moves
// from the tooltip trigger to the body.
&::before {
content: "";
display: block;
position: absolute;
}
}

.usa-tooltip__body--wrap {
Expand All @@ -66,8 +103,14 @@ $triangle-size: 5px;
opacity: 1;
}

.usa-tooltip__body--top {
@include tooltip-spacer("top");
}

.usa-tooltip__body--bottom {
&:after {
@include tooltip-spacer("bottom");

&::after {
border-left: $triangle-size solid transparent;
border-right: $triangle-size solid transparent;
border-bottom: $triangle-size solid color($theme-tooltip-background-color);
Expand All @@ -78,7 +121,9 @@ $triangle-size: 5px;
}

.usa-tooltip__body--right {
&:after {
@include tooltip-spacer("right");

&::after {
border-top: $triangle-size solid transparent;
border-bottom: $triangle-size solid transparent;
border-right: $triangle-size solid color($theme-tooltip-background-color);
Expand All @@ -92,7 +137,9 @@ $triangle-size: 5px;
}

.usa-tooltip__body--left {
&:after {
@include tooltip-spacer("left");

&::after {
border-top: $triangle-size solid transparent;
border-bottom: $triangle-size solid transparent;
border-left: $triangle-size solid color($theme-tooltip-background-color);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="usa-prose measure-5 padding-2 border-1px border-gray-20 margin-bottom-4">
<p class="font-body-lg text-bold margin-top-0">Test that mouse event listeners target the correct tooltip when no parent wrapper is present</p>
<p>To test this, confirm that mouseover/mouseleave events work as expected when there is no parent wrapper on `usa-tooltip` elements.</p>
</div>

<h2>Tooltip with no wrapper</h2>

<button type="button" class="usa-button usa-tooltip" data-position="left" title="Show on left">Show on left</button>

<h2>Another tooltip with no wrapper</h2>

<a href="#" class="usa-tooltip" data-position="bottom" title="Show on bottom">Show on bottom</button>
34 changes: 34 additions & 0 deletions packages/usa-tooltip/src/test/tooltips.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ const tests = [
];

const EVENTS = {
mouseover(el) {
const mouseoverEvent = new Event("mouseover", {
bubbles: true,
cancelable: true,
});

el.dispatchEvent(mouseoverEvent);
},
mouseleave(el) {
const mouseleaveEvent = new Event("mouseleave", {
cancelable: true,
});

el.dispatchEvent(mouseleaveEvent);
},
escape(el) {
const escapeKeyEvent = new KeyboardEvent("keydown", {
key: "Escape",
Expand All @@ -26,12 +41,14 @@ tests.forEach(({ name, selector: containerSelector }) => {
const { body } = document;
let tooltipBody;
let tooltipTrigger;
let tooltipWrapper;

beforeEach(() => {
body.innerHTML = TEMPLATE;
tooltip.on(containerSelector());
tooltipBody = body.querySelector(".usa-tooltip__body");
tooltipTrigger = body.querySelector(".usa-tooltip__trigger");
tooltipWrapper = body.querySelector(".usa-tooltip");
});

afterEach(() => {
Expand Down Expand Up @@ -64,6 +81,23 @@ tests.forEach(({ name, selector: containerSelector }) => {
assert.strictEqual(tooltipBody.classList.contains("is-set"), false);
});

it("tooltip is visible on mouseover", () => {
EVENTS.mouseover(tooltipTrigger);
assert.strictEqual(tooltipBody.classList.contains("is-set"), true);
});

it("tooltip is hidden on mouseleave", () => {
EVENTS.mouseover(tooltipTrigger);
EVENTS.mouseleave(tooltipWrapper);
assert.strictEqual(tooltipBody.classList.contains("is-set"), false);
});

it("tooltip content is hoverable", () => {
EVENTS.mouseover(tooltipTrigger);
EVENTS.mouseover(tooltipBody);
assert.strictEqual(tooltipBody.classList.contains("is-set"), true);
});

it("tooltip is hidden on escape keydown", () => {
tooltipTrigger.focus();
EVENTS.escape(tooltipTrigger);
Expand Down
3 changes: 3 additions & 0 deletions packages/usa-tooltip/src/usa-tooltip.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Component from "./usa-tooltip.twig";
import TestComponent from "./test/test-patterns/test-usa-tooltip-utilities.twig";
import TestNoWrapperComponent from "./test/test-patterns/test-usa-tooltip-no-wrapper.twig";
import UtilityComponent from "./usa-tooltip--utilities.twig";

export default {
Expand All @@ -8,8 +9,10 @@ export default {

const Template = (args) => Component(args);
const TestTemplate = (args) => TestComponent(args);
const TestNoWrapperTemplate = (args) => TestNoWrapperComponent(args);
const UtilityTemplate = (args) => UtilityComponent(args);

export const Tooltip = Template.bind({});
export const TooltipUtility = UtilityTemplate.bind({});
export const Test = TestTemplate.bind({});
export const TestNoWrapper = TestNoWrapperTemplate.bind({});

0 comments on commit 114f6e5

Please sign in to comment.