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

RichTooltip/Popover component - Feature branch [MAIN] #2069

Merged
merged 128 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 127 commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
f110f9e
added `PopoverPrimitive` component
didoo Apr 10, 2024
07c5375
added `@oddbird/popover-polyfill` as dependency
didoo May 9, 2024
3e7cafb
added showcase for `PopoverPrimitive`
didoo Apr 9, 2024
bc16443
added integration tests for `PopoverPrimitive`
didoo Apr 9, 2024
9000485
generalized autoscroll to `Shw::Autoscrollable` showcase component so…
didoo Apr 11, 2024
58bd98e
changed default layout to β€œblock” instead of β€œinline”
didoo Apr 15, 2024
384b797
changed `eager` parameter for the modifiers from `false` to `true`
didoo Apr 15, 2024
466bf0d
updated showcase
didoo Apr 15, 2024
6a45f13
fixed integration tests for `block` (new default) vs `inline`
didoo Apr 15, 2024
0ebcec1
updated how `aria` atttributes are applied to the toggle and popover …
didoo Apr 16, 2024
7bd847e
updated integration tests for A11Y attributes
didoo Apr 16, 2024
f0edf0b
added support for `width/height` to the PopoverPrimitive
didoo Apr 16, 2024
9cdf98b
added integration tests for `width/height`
didoo Apr 16, 2024
2c5b139
split showcase for `enableCollisionDetection` in booleans vs strings
didoo Apr 16, 2024
740187a
small CSS update
didoo Apr 17, 2024
478fa73
added small clarification to the showcase
didoo Apr 17, 2024
34d3f97
made the `containsInteractive` check an explicit `@toggleContainsInte…
didoo Apr 18, 2024
247bcbe
updated integration tests
didoo Apr 17, 2024
76a38e2
removed extra method
didoo Apr 17, 2024
3b5056e
reordered β€œinteraction” section
didoo Apr 17, 2024
0f92b51
updates following engineering sync review
didoo Apr 17, 2024
8af1ee8
refactored/renamed APIs for `PopoverPrimitive`
didoo Apr 18, 2024
b08b0da
updated integration tests
didoo Apr 18, 2024
05d8b78
added percy test for `PopoverPrimitive`
didoo Apr 18, 2024
e5bab1a
small tweak to the showcase page
didoo Apr 18, 2024
b7f595a
small comment update (latest Firefox version supports Popover API)
didoo Apr 18, 2024
9043a77
removed `aria-details` attribute from β€œtoggle” element per code review
didoo Apr 23, 2024
ca9d815
refactored `PopoverPrimitive` to be a headless component and provide …
didoo Apr 29, 2024
7e31f97
updated showcase for `PopoverPrimitive` to account for API refactoring
didoo Apr 29, 2024
68bab1f
updated integration tests for `PopoverPrimitive`
didoo Apr 29, 2024
4756039
small CSS cleanups
didoo Apr 30, 2024
ad14ee6
updates following code review
didoo May 2, 2024
2773c5d
added `try/catch` statements in the `show/hide/togglePopover` actions…
didoo May 3, 2024
2173f9c
added β€œComponent API” documentation
didoo Apr 16, 2024
e38dfbb
added β€œHow to use” documentation
didoo Apr 8, 2024
197fcfb
added illustration for `PopoverPrimitive`
didoo Apr 15, 2024
23e5b16
added acceptance test for `PopoverPrimitive`
didoo Apr 17, 2024
ce3b937
Apply suggestions from code review
didoo Apr 18, 2024
47bbe1e
updated β€œComponent API” documentation for `PopoverPrimitive` followin…
didoo Apr 29, 2024
56529ab
updated β€œHow to use” documentation for `PopoverPrimitive` following t…
didoo Apr 29, 2024
87b69ba
Apply suggestions from code review
didoo May 1, 2024
25f8326
added the `RichTooltip` component + `RichTooltip::Toggle::InfoText` s…
didoo Apr 18, 2024
2e099fc
added the showcase page for the `RichTooltip`
didoo Apr 18, 2024
351a8eb
refactored component API to be more standard
didoo Apr 16, 2024
24a6676
updated showcase following component API refactoring
didoo Apr 16, 2024
43f865f
improved how we define defaults for `enableSoft/ClickEvents`
didoo Apr 16, 2024
f6227d8
changed how we handle width/height and the bubble styling
didoo Apr 16, 2024
52760ed
made `enableCollisionDetection` as `true` by default
didoo Apr 18, 2024
f41dbc6
exposed/forwarded the `@toggleContainsInteractive` argument
didoo Apr 18, 2024
ac59dc2
refactored/renamed APIs for `RichTooltip` + small cleanups
didoo Apr 18, 2024
f5fea08
added percy test for `RichTooltip`
didoo Apr 18, 2024
30e1769
added polyfilled styles for Firefox
didoo Apr 18, 2024
7707d89
added an `all: initial` reset to the popover content to avoid inherit…
didoo Apr 19, 2024
fa85019
added use cases to showcase to verify inheritance of typographic/colo…
didoo Apr 18, 2024
2b195af
fixed issue with inherited `font-weight` on β€œInfoText”
didoo Apr 18, 2024
9cbcadf
fixed issue with `clean-css` mangling the generated CSS code because …
didoo Apr 19, 2024
873542a
added extra tracked property (`isOpen`) to the `toggle` yielding
didoo Apr 19, 2024
b392e2c
small whitespace fix
didoo Apr 19, 2024
d9ac0e0
added integration tests for `RichTooltip` + `RichTooltip::Tooltip::In…
didoo Apr 22, 2024
183bbb9
added missing `…attributes` to `InfoText` subcomponent
didoo Apr 22, 2024
1535cd1
refactored `RichTooltip` to be compliant with accessibility requireme…
didoo Apr 29, 2024
e82f117
updated showcase for `RichTooltip` to account for API refactoring
didoo Apr 29, 2024
4808f77
updated integration tests for `RichTooltip` (and sub-components)
didoo Apr 29, 2024
a1b4822
fixed linting in test file
didoo Apr 30, 2024
5ad61b6
updated copy in showcase
didoo Apr 30, 2024
85cf5c9
removed redundant splattributes
didoo May 1, 2024
b3c7180
small update to showcase
didoo May 1, 2024
1d237df
added a bubble use case to the showcase
didoo May 1, 2024
547fd16
Apply suggestions from code review
didoo May 2, 2024
c6f7a3e
reverted committed suggestion
didoo May 2, 2024
e01734e
added missing styling to tooltip content
didoo May 2, 2024
f59a8b3
added use cases of text inline with other pre/post characters and inl…
didoo May 2, 2024
db4083b
improved vertical alignment when the toggle is icon-only and is inline
didoo May 2, 2024
d781cdf
removed extra whitespace in `RichTooltip` template
didoo May 2, 2024
1d2c0c0
fixed demo cases with inline text that has pre/post characters
didoo May 2, 2024
f209f19
changed default `iconPosition` to `trailing` for toggle (per discussi…
didoo May 7, 2024
d14bad0
updated showcase in relation to the change to default `iconPosition` …
didoo May 7, 2024
3009bb0
fix for layout bug in Safari
didoo May 9, 2024
f049985
fixed integration test
didoo May 9, 2024
2f9a8d8
changeset
didoo May 9, 2024
814efb3
Initial commit to open PR (to be removed later)
didoo Apr 30, 2024
2e86190
Starting to write documentation and guidelines
jorytindall Apr 5, 2024
edb6bd5
Adding a bunch of content to the guidelines, adding anatomy images
jorytindall Apr 5, 2024
3521c8e
Adding a bunch of image examples
jorytindall Apr 10, 2024
ebd284d
Streamlining trigger and interaction content
jorytindall Apr 15, 2024
f033d9b
Added many examples of different types of content, etc
jorytindall Apr 15, 2024
a916c3d
Added component illustration
jorytindall Apr 15, 2024
562a6cc
Update image path
jorytindall Apr 15, 2024
deec2bb
Editing, streamlining, and simplifying the guidelines content
jorytindall Apr 17, 2024
4707984
Added component illustration
jorytindall Apr 15, 2024
9989251
Editing, streamlining, and simplifying the guidelines content
jorytindall Apr 17, 2024
15f4fda
Further cleaning up the content, replacing trigger with toggle
jorytindall Apr 18, 2024
1892552
Added some information about the InfoText component, outlined variant…
jorytindall Apr 18, 2024
064dabd
Added component illustration
jorytindall Apr 15, 2024
ec6d1cb
Editing, streamlining, and simplifying the guidelines content
jorytindall Apr 17, 2024
51d45e9
added illustration for `RichTooltip`
didoo Apr 19, 2024
d9a1659
added β€œComponent API” documentation for `RichTooltip`
didoo Apr 19, 2024
5e60701
added acceptance test for `RichTooltip`
didoo Apr 19, 2024
b2e7561
updated front matter for `RichTooltip`
didoo Apr 22, 2024
a236677
Responding to feedback, updating images, adding more context in a few…
jorytindall Apr 22, 2024
e4871ba
added β€œHow to use” section for the `RichTooltip` component
didoo Apr 23, 2024
4d25ebc
Added a note about omitting the icon in blocks of text
jorytindall Apr 23, 2024
9bd08ba
Updating based on feedback from review
jorytindall Apr 24, 2024
add3212
Remove states file and include
jorytindall Apr 24, 2024
c7e0b30
Apply suggestions from code review
didoo Apr 24, 2024
1ae3386
updated β€œComponent API” documentation following the refactoring of th…
didoo Apr 29, 2024
0f04f2b
updated β€œHow to use” documentation following the refactoring of the c…
didoo Apr 29, 2024
c4a22c6
Apply suggestions from code review
MelSumner Apr 30, 2024
ddb8f7d
removed duplicate test file (wrong filename)
didoo May 1, 2024
846e413
Apply suggestions from code review
didoo May 1, 2024
760d2c4
added links to allow consumers to dig deeper in Floating UI APIs
didoo May 1, 2024
d248d28
added example of how to handle whitespace issues with inline text
didoo May 2, 2024
1576455
Updated the description and caption to more closely match the Tooltip…
jorytindall May 6, 2024
eea07cb
Working through feedback and updating imagery
jorytindall May 7, 2024
06614d0
Additional cleanup
jorytindall May 7, 2024
a990453
changed default `iconPosition` to `trailing` for toggle in documentat…
didoo May 7, 2024
45ff46c
jagged β†’ janky
didoo May 7, 2024
dfef302
Small addition to structured content
jorytindall May 7, 2024
989bde6
Apply suggestions from code review
didoo May 8, 2024
1785c8f
Updated the accessibility content and added an image to communicate t…
jorytindall May 8, 2024
674b387
Reverting change to the text input docs
jorytindall May 9, 2024
6217b0b
Responding to feedback and updating images to use correct styling
jorytindall May 9, 2024
a653da3
small tweak following review suggestion
didoo May 10, 2024
142114d
Update website/docs/components/rich-tooltip/partials/accessibility/ac…
MelSumner May 10, 2024
8762e6d
Merge pull request #2040 from hashicorp/04b-richtooltip-component-doc…
didoo May 10, 2024
ff0faa4
remove temporary changeset
didoo May 10, 2024
7bb5331
Removed duplicate illustration
jorytindall May 10, 2024
bc177ea
updated `oddbird/popover-polyfill` dependency version
didoo May 13, 2024
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
7 changes: 7 additions & 0 deletions .changeset/small-onions-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@hashicorp/design-system-components": minor
---

`PopoverPrimitive` - Added low-level (internal) headless component to provide anchoring, collision detection, and popover functionalities.

`RichTooltip` - Added component to provide tooltips that can contain more complex and structured content.
5 changes: 5 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@floating-ui/dom": "^1.6.3",
"@hashicorp/design-system-tokens": "^2.1.0",
"@hashicorp/ember-flight-icons": "^5.0.1",
"@oddbird/popover-polyfill": "^0.4.1",
didoo marked this conversation as resolved.
Show resolved Hide resolved
"decorator-transforms": "^1.1.0",
"ember-a11y-refocus": "^3.0.2",
"ember-cli-sass": "^11.0.1",
Expand Down Expand Up @@ -217,8 +218,12 @@
"./components/hds/pagination/nav/number.js": "./dist/_app_/components/hds/pagination/nav/number.js",
"./components/hds/pagination/numbered/index.js": "./dist/_app_/components/hds/pagination/numbered/index.js",
"./components/hds/pagination/size-selector/index.js": "./dist/_app_/components/hds/pagination/size-selector/index.js",
"./components/hds/popover-primitive/index.js": "./dist/_app_/components/hds/popover-primitive/index.js",
"./components/hds/reveal/index.js": "./dist/_app_/components/hds/reveal/index.js",
"./components/hds/reveal/toggle/button.js": "./dist/_app_/components/hds/reveal/toggle/button.js",
"./components/hds/rich-tooltip/bubble.js": "./dist/_app_/components/hds/rich-tooltip/bubble.js",
"./components/hds/rich-tooltip/index.js": "./dist/_app_/components/hds/rich-tooltip/index.js",
"./components/hds/rich-tooltip/toggle.js": "./dist/_app_/components/hds/rich-tooltip/toggle.js",
"./components/hds/segmented-group/index.js": "./dist/_app_/components/hds/segmented-group/index.js",
"./components/hds/separator/index.js": "./dist/_app_/components/hds/separator/index.js",
"./components/hds/side-nav/base.js": "./dist/_app_/components/hds/side-nav/base.js",
Expand Down
19 changes: 19 additions & 0 deletions packages/components/src/components/hds/popover-primitive/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{! @glint-nocheck: not typesafe yet }}
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
{{yield
(hash
setupPrimitiveContainer=this.setupPrimitiveContainer
setupPrimitiveToggle=this.setupPrimitiveToggle
setupPrimitivePopover=this.setupPrimitivePopover
toggleElement=this.toggleElement
arrowElement=this.arrowElement
popoverElement=this.popoverElement
isOpen=this.isOpen
showPopover=this.showPopover
hidePopover=this.hidePopover
togglePopover=this.togglePopover
)
}}
275 changes: 275 additions & 0 deletions packages/components/src/components/hds/popover-primitive/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { assert, warn } from '@ember/debug';
import { next } from '@ember/runloop';
import { guidFor } from '@ember/object/internals';
import { modifier } from 'ember-modifier';

import registerEvent from '../../../modifiers/hds-register-event';
import anchoredPositionModifier from '../../../modifiers/hds-anchored-position';

// https://github.com/oddbird/popover-polyfill?tab=readme-ov-file#with-npm
// this is needed until Firefox releases the version 126 (up to 125 didn't support the Popover API)
import {
// this call polyfills some of the browser methods to emulate the Popover API
apply as applyPopoverApiPolyfill,
// we'll use these two flags to overwrite the popover positioning strategy
// this is specifically done for Firefox (we need Firefox 126 to be released, to support the last 2 versions)
// see: https://whattrainisitnow.com/release/?version=126
isSupported as isPopoverApiSupported,
isPolyfilled as isPopoverApiPolyfilled,
} from '@oddbird/popover-polyfill/fn';

// we use this to re-export the values
export {
PLACEMENTS,
DEFAULT_PLACEMENT,
} from '../../../modifiers/hds-anchored-position';

export default class HdsPopoverPrimitiveComponent extends Component {
@tracked isOpen = this.args.isOpen ?? false;
@tracked isClosing = false;
// this will enable "soft" events for the toggle ("hover" and "focus")
enableSoftEvents = this.args.enableSoftEvents ?? false;
// this will enable "click" events for the toggle
enableClickEvents = this.args.enableClickEvents ?? false;

constructor() {
super(...arguments);

// if the Popover API is not supported we need to polyfill it
if (!isPopoverApiSupported()) {
warn(
"The browser used does not support the Popover API so it's been emulated and some functionalities may not work as expected.",
{
id: 'hds-popover.no-popover-api-support.polyfill-applied',
}
);
// this function polyfills quite a few DOM methods and adds emulation for the Popover API
// see: https://github.com/oddbird/popover-polyfill/blob/main/src/popover.ts#L123
applyPopoverApiPolyfill();
}
}

setupPrimitiveContainer = modifier(
(element) => {
this.containerElement = element;

// we register the "soft" events
if (this.enableSoftEvents) {
registerEvent(this.containerElement, ['mouseenter', this.onMouseEnter]);
registerEvent(this.containerElement, ['mouseleave', this.onMouseLeave]);
registerEvent(this.containerElement, ['focusin', this.onFocusIn]);
}
// we always want the focusOut event
registerEvent(this.containerElement, ['focusout', this.onFocusOut]);
},
{ eager: true }
);

setupPrimitiveToggle = modifier(
(element) => {
this.toggleElement = element;

assert(
`The toggle element of "Hds::PopoverPrimitive" must be a <button>; element received: <${element.tagName.toLowerCase()}>`,
element instanceof HTMLButtonElement
);
},
{ eager: true }
);

setupPrimitivePopover = modifier(
(element, _positional, named = {}) => {
this.popoverElement = element;

// for the click events we don't use `onclick` event listeners, but we rely on the `popovertarget` attribute
// provided by the Popover API which does all the magic for us without needing JS code
// (important: to work it needs to be applied to a button)
if (this.enableClickEvents) {
let popoverId;
if (this.popoverElement.id) {
popoverId = this.popoverElement.id;
} else {
// we need a DOM id for the `popovertarget` attribute
popoverId = guidFor(this);
this.popoverElement.id = popoverId;
}
this.toggleElement.setAttribute('popovertarget', popoverId);
}

// this should be an extremely edge case, but in the case the popover needs to be initially forced to be open
// we need to use the "manual" state to support the case of multiple "menus" opened at the same time
// IMPORTANT! if a "popover" is set to "open" with a "manual" state, then it can't be closed via `esc` and `click outside`
if (this.args.isOpen) {
this.popoverElement.popover = 'manual';
this.popoverElement.showPopover();
} else {
this.popoverElement.popover = 'auto';
}

// Register "onBeforeToggle" + "onToggle" callback functions to be called when a native 'toggle' event is dispatched
registerEvent(this.popoverElement, [
'beforetoggle',
this.onBeforeTogglePopover,
]);
registerEvent(this.popoverElement, ['toggle', this.onTogglePopover]);

// we need to spread the argument because if it's set via `{{ hash … }}` Ember complains when we overwrite one of its values
const anchoredPositionOptions = { ...named.anchoredPositionOptions };

// we overwrite the "strategy" if the Popover API is not supported (polyfill applied for the first time) of it's already been polyfilled (see above)
// this is specifically done for Firefox: currently it doesn't support it, but will soon (we need Firefox 127 to support the last 2 versions)
// see: https://wiki.mozilla.org/Release_Management/Release_owners
if (!isPopoverApiSupported() || isPopoverApiPolyfilled()) {
// when using the "absolute" strategy, the presence of a parent with "relative" position leads to wrong layout rendering (known issue in the polyfill library)
// see: https://github.com/oddbird/popover-polyfill/tree/main?tab=readme-ov-file#caveats
anchoredPositionOptions.strategy = 'fixed';
}

// Apply the `hds-anchored-position` modifier to the "popover" element
// (notice: this function runs the first time when the element the modifier was applied to is inserted into the DOM, and it autotracks while running.
// Any tracked values that it accesses will be tracked, including the arguments it receives, and if any of them changes, the function will run again)
// This modifiers uses the Floating UI library to provide:
// - positioning of the "popover" in relation to the "toggle"
// - collision detection (optional)
next(() => {
anchoredPositionModifier(
this.popoverElement, // element the modifier is attached to
[this.toggleElement], // positional arguments
anchoredPositionOptions // named arguments
);
});
},
{ eager: true }
);

@action
showPopover() {
try {
this.popoverElement.showPopover();
} catch (error) {
warn(
'The invocation of `showPopover` for the popover element caused an unexpected error.',
{
id: 'hds-popover.show-popover-action.invocation-failed',
error: error,
}
);
}
}

@action
hidePopover() {
try {
this.popoverElement.hidePopover();
} catch (error) {
warn(
'The invocation of `hidePopover` for the popover element caused an unexpected error.',
{
id: 'hds-popover.hide-popover-action.invocation-failed',
error: error,
}
);
}
}

@action
togglePopover() {
try {
this.popoverElement.togglePopover();
} catch (error) {
warn(
'The invocation of `togglePopover` for the popover element caused an unexpected error.',
{
id: 'hds-popover.toggle-popover-action.invocation-failed',
error: error,
}
);
}
}

// fired just _before_ the "popover" is shown or hidden
@action
onBeforeTogglePopover(event) {
if (event.newState === 'closed') {
// we need this flag to check if it's in the "closing" process,
// because the browser automatically returns the focus to the "trigger" button
// and this would re-open immediately the popover because of the `focusin` event
this.isClosing = true;
}
}

// fired just _after_ the "popover" is shown or hidden
@action
onTogglePopover(event) {
if (event.newState === 'open') {
this.isOpen = true;

// we call the "onOpen" callback if it exists (and is a function)
let { onOpen } = this.args;
if (typeof onOpen === 'function') {
onOpen();
}
} else {
this.isOpen = false;

// reset the "isClosing" flag (the `toggle` event is fired _after_ the popover is closed)
this.isClosing = false;

// if the popover was initially forced to be open (using the "manual" state) then revert its status to `auto` once the user interacts with it
if (this.args.isOpen) {
this.popoverElement.popover = 'auto';
}

// we call the "onClose" callback if it exists (and is a function)
let { onClose } = this.args;
if (typeof onClose === 'function') {
onClose();
}
}
}

@action
onMouseEnter() {
if (this.timer) {
clearTimeout(this.timer);
}
this.showPopover();
}

@action
onFocusIn() {
// don't re-open the popover if the focus is returned because the closing
if (!this.isClosing) {
if (this.timer) {
clearTimeout(this.timer);
}
this.showPopover();
}
}

@action
onMouseLeave() {
this.timer = setTimeout(() => this.hidePopover(), 500);
}

@action
onFocusOut(event) {
// due to inconsistent implementation of relatedTarget across browsers we use the activeElement as a fallback
// if the related target is not part of the disclosed content we close the disclosed container
if (
!this.containerElement.contains(
event.relatedTarget || document.activeElement
)
) {
this.hidePopover();
}
}
}
22 changes: 22 additions & 0 deletions packages/components/src/components/hds/rich-tooltip/bubble.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{{! @glint-nocheck: not typesafe yet }}
{{!
Copyright (c) HashiCorp, InPP.
SPDX-License-Identifier: MPL-2.0
}}
<div
class="hds-rich-tooltip__bubble"
...attributes
id={{@popoverId}}
tabindex="-1"
role="tooltip"
aria-hidden={{(unless @isOpen true)}}
{{style this.sizingStyles}}
{{@setupPrimitivePopover anchoredPositionOptions=this.anchoredPositionOptions}}
>
<div class="hds-rich-tooltip__bubble-arrow" id={{@arrowId}} />
{{#if @isOpen}}
<div class="hds-rich-tooltip__bubble-inner-content">
{{yield}}
</div>
{{/if}}
</div>
Loading