Skip to content
Open
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
97 changes: 97 additions & 0 deletions packages/unity-bootstrap-theme/src/js/tooltips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { EventHandler } from "./bootstrap-helper";

function initTooltips() {
/* this value must stay in sync, found in files: */
/* packages/unity-bootstrap-theme/src/scss/extends/_tooltips.scss */
/* packages/unity-bootstrap-theme/src/js/tooltips.js */
const TOOLTIP_MAX_WIDTH = 288;
const CONTAINER_CLASS = "uds-tooltip-container";
const CONTAINER_SELECTOR = `.${CONTAINER_CLASS}`;
const TRIGGER_ATTR = "aria-describedby";
const TRIGGER_SELECTOR = `[${TRIGGER_ATTR}]`;
const CONTENT_ATTR = "role=tooltip";
const CONTENT_SELECTOR = `[${CONTENT_ATTR}]`;

// This query selector is not just creating a List,
// it's also checking to ensure all 3 elements are present
// (container, trigger, content) and in the correct order
// (trigger immediately followed by content)
const tooltipContentList = document.querySelectorAll(
`${CONTAINER_SELECTOR} > ${TRIGGER_SELECTOR} + ${CONTENT_SELECTOR}`
);

function closeActiveTooltips() {
const activeTooltips = document.querySelectorAll(
`${TRIGGER_SELECTOR}[aria-expanded="true"]`
);
activeTooltips.forEach(activeTooltip => {
activeTooltip.setAttribute("aria-expanded", "false");
});
}

function show(e) {
// container or trigger
let trigger =
e.target.querySelector(`${CONTAINER_SELECTOR} ${TRIGGER_SELECTOR}`) ||
e.target;
let content = trigger.nextSibling;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think nextElementSibling might be better here just in case nextSibling picks up any whitespace elements or line breaks


if (e.type === "keypress") {
if (e.charCode !== 32) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should use e.key since keypress and charCode is deprecated

return;
}
}

closeActiveTooltips();

if (
trigger.getBoundingClientRect().right + TOOLTIP_MAX_WIDTH >
window.innerWidth
) {
content.classList.add("bottom-placement");
} else {
content.classList.remove("bottom-placement");
}
trigger.setAttribute("aria-expanded", "true");
}

function hide(e) {
// container or trigger
let trigger =
e.target.querySelector(`${CONTAINER_SELECTOR} ${TRIGGER_SELECTOR}`) ||
e.target;

if (e.type === "mouseleave") {
if (trigger === document.activeElement) {
return;
}
}
trigger.setAttribute("aria-expanded", "false");
}

function keyboardHide(e) {
if (e.key === "Escape") {
hide(e);
}
}

[...tooltipContentList].map(contentEl => {
const controller = new AbortController();
const { signal } = controller;
const triggerEl = contentEl.previousElementSibling;
const containerEl = triggerEl.parentElement;

triggerEl.addEventListener("mouseenter", show, { signal });
triggerEl.addEventListener("focus", show, { signal });
triggerEl.addEventListener("keypress", show, { signal });
triggerEl.addEventListener("blur", hide, { signal });
triggerEl.addEventListener("keydown", keyboardHide, { signal });
containerEl.addEventListener("mouseleave", hide, { signal });

return controller;
});
}

EventHandler.on(window, "load.uds.tooltips", initTooltips);

export { initTooltips };
2 changes: 2 additions & 0 deletions packages/unity-bootstrap-theme/src/js/unity-bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { initImageParallax } from "./image-parallax.js";
import { initModals } from "./modals.js";
import { initTabbedPanels } from "./tabbed-panels.js";
import { initFixedTable } from "./tables.js";
import { initTooltips } from "./tooltips.js";
import { initVideo } from "./video.js";

const unityBootstrap = {
Expand All @@ -30,6 +31,7 @@ const unityBootstrap = {
initModals,
initRankingCard,
initTabbedPanels,
initTooltips,
initVideo,
initCardBodies,
};
Expand Down
63 changes: 39 additions & 24 deletions packages/unity-bootstrap-theme/src/scss/extends/_tooltips.scss
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
@mixin focusState {
+ div[role='tooltip'].uds-tooltip-description {
visibility: visible;
}
.fa-circle {
color: $uds-color-font-light-info;
}
}

.uds-tooltip-container {
/* this value must stay in sync, found in files: */
/* packages/unity-bootstrap-theme/src/scss/extends/_tooltips.scss */
/* packages/unity-bootstrap-theme/src/js/tooltips.js */
--tooltip-max-width: 288px;

--tooltip-offset: .5rem;
display: inline-block;
position: relative;

&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: calc(-1 * var(--tooltip-offset));
bottom: calc(-1 * var(--tooltip-offset));
}

[aria-describedby] {
& {
position: relative;
}

+ [role="tooltip"] {
visibility: hidden;
display: none;
z-index: 2;
}
}

[aria-describedby]:focus,
[aria-describedby]:hover {
[aria-describedby][aria-expanded='true'] {
+ [role="tooltip"] {
visibility: visible;
display: block;
}
}
}

button.uds-tooltip {
background: none;
background-color: transparent;
color: inherit;
border: none;
padding: 0;
Expand All @@ -50,12 +61,9 @@ button.uds-tooltip {
vertical-align: middle;
}

&:focus {
@include focusState();
}
@include media-breakpoint-up(sm) {
&:hover {
@include focusState();
&[aria-expanded='true'] {
.fa-circle {
color: $uds-color-font-light-info;
}
}
}
Expand Down Expand Up @@ -95,16 +103,23 @@ div[role='tooltip'].uds-tooltip-description {
color: $asu-gray-7;
font: normal normal normal $uds-size-spacing-2 Arial;
line-height: $uds-size-spacing-3;
margin: 0px 5px;
max-width: 353px;
min-width: 300px;
max-width: var(--tooltip-max-width);
min-width: min(100vw, var(--min-width));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I dont see --min-width defined up top

width: -webkit-max-content;
padding: $uds-size-spacing-4;
position: absolute;
left: 40px;
left: calc(100% + var(--tooltip-offset));
top: 0;
visibility: hidden;
justify-self: start;
align-self: end;
z-index: 1;

&.bottom-placement {
left: unset;
top: calc(100% + var(--tooltip-offset));
right: 0;
}

& > span.uds-tooltip-heading {
color: $asu-gray-7;
display: block;
Expand Down
3 changes: 3 additions & 0 deletions packages/unity-react-core/.storybook/decorators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const withContainer: Decorator = (
// custom events created by eventSpy.js to allow storybook to dispatch load events after the page is loaded
document.dispatchEvent(new Event("sb_DOMContentLoaded"));
window.dispatchEvent(new Event('sb_load'));
} else {
window.dispatchEvent(new Event("DOMContentLoaded"));
window.dispatchEvent(new Event("load"));
}

emit("HTML/CodeUpdated", { code: root.current.innerHTML });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { Tooltip } from "./Tooltip";
import { ButtonIconOnly } from "../ButtonIconOnly/ButtonIconOnly";
import { Image } from "../Image/Image";
import { img01 } from "@asu/shared";
/**
* TODO
* https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tooltip_role
Expand All @@ -10,38 +11,68 @@ import { ButtonIconOnly } from "../ButtonIconOnly/ButtonIconOnly";
*
* probably limit the triggers to something with a visual inidicator (like button or link)
*/

const defaultProps = {
title: "Header",
content: "Content goes here, this is a tooltip. It can be long or short.",
};
export default {
title: "Components/Tooltip",
component: Tooltip,
decorators: [
story => (
<>
<div style={{ margin: "100px 10px" }}>
Lorem ipsum dolor <button tabIndex={0}>Focus</button>sit amet,
consectetur adipiscing elit.
{story()} Sed Sed do eiusmod tempor incididunt
<button tabIndex={0}>Focus</button>
ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo <button tabIndex={0}>Focus</button>consequat. Duis aute irure dolor in
{story()} Sed Sed do eiusmod tempor incididunt
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
ut aliquip ex ea commodo <button tabIndex={0}>Focus</button>consequat. Duis aute irure dolor in
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</div>
</>
),
],
render: args => <Tooltip {...args} />,
args: {
...defaultProps,
},
};

const defaultProps = {
title: "Header",
content: "Content",
}
export const NoChildrenDefaultIcon = {};

const tooltipTemplate = args => <Tooltip {...args} />;
export const Link = {
render: args => (
<Tooltip {...args}>
<a href="https://example.com">Tooltiptrigger</a>
</Tooltip>
),
};

export const Icon = {
render: tooltipTemplate.bind({}),
args: {
...defaultProps,
triggerElement: <ButtonIconOnly icon={["fas","info"]} />,
}
export const Text = {
render: args => <Tooltip {...args}>just a plain string</Tooltip>,
};

export const link = {
render: args => <div>This is a <Tooltip {...args} /> sentence.</div>,
args: {
...defaultProps,
triggerElement: <a href="javascript:void(0);">Tooltiptrigger</a>,
}
export const JsxSpanContainingText = {
render: args => (
<Tooltip {...args}>
<span> html string Tooltiptrigger</span>
</Tooltip>
),
};

export const text = {
render: tooltipTemplate.bind({}),
args: {
...defaultProps,
triggerElement: <span>Tooltiptrigger</span>,
}
export const ImageOnly = {
render: args => (
<Tooltip {...args}>
<a style={{ display: "inline-block", maxWidth: "550px" }}>
<Image src={img01} alt={""} />
</a>
</Tooltip>
),
};
Loading