Skip to content

Commit

Permalink
Feat(web): Extend Tooltip Floating UI example, deprecate CSS modifier…
Browse files Browse the repository at this point in the history
…s and side corners names

 #DS-618
  • Loading branch information
crishpeen committed Nov 23, 2023
1 parent ac0fec6 commit de6953e
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 97 deletions.
54 changes: 37 additions & 17 deletions packages/web/src/scss/components/Tooltip/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@
Bare Tooltip HTML:

```html
<div class="Tooltip Tooltip--top">
<div class="Tooltip" data-spirit-placement="top">
Hello there!
<span class="Tooltip__arrow"></span>
</div>
```

## ⚠️ DEPRECATION NOTICE

CSS modifiers `Tooltip--top`, `Tooltip--rightTop`, `Tooltip--bottom`, etc. are deprecated and will be
removed in the next major release. Use `data-spirit-placement` attribute instead.

Also both axis side placements are renamed from `top-left`, `top-right`, `right-top`, `right-bottom`,
etc. to `top-start`, `top-end`, `right-start`, `right-end`, etc. Old names are deprecated and will be
removed in the next major release.

## Linking with Content

To display a Tooltip along your content, simply place it in the DOM next to it.
Expand All @@ -32,7 +41,7 @@ improved accessibility.
```html
<div class="TooltipWrapper d-inline-block">
<a href="#" aria-describedby="my-tooltip">I have a tooltip!</a>
<div id="my-tooltip" class="Tooltip Tooltip--top">
<div id="my-tooltip" class="Tooltip" data-spirit-placement="top">
Hello there!
<span class="Tooltip__arrow"></span>
</div>
Expand All @@ -41,14 +50,14 @@ improved accessibility.

## Placement

Tooltip implements the [Placement Dictionary][dictionary-placement] for placement. The dictionary values are used as CSS
modifiers in the camelCase format: `Tooltip--top`, `Tooltip--rightTop`, `Tooltip--leftBottom`, etc.
Tooltip implements the [Placement Dictionary][dictionary-placement] for placement. The dictionary values are used as
a value of data attribute `data-spirit-placement`, e.g. `data-spirit-placement="top"`, `data-spirit-placement="rightEnd"`, etc.

For JS-controlled positioning please use the `data-spirit-placement` attribute instead of CSS modifiers (more on that
[below](#advanced-positioning)).
For JS-controlled positioning please use the `data-spirit-tooltip-controlled` attribute instead of
`data-spirit-placement` modifiers (more on that [below](#advanced-positioning)).

```html
<div class="Tooltip Tooltip--rightTop">
<div class="Tooltip" data-spirit-placement="rightStart">
Tooltip on right
<span class="Tooltip__arrow"></span>
</div>
Expand All @@ -65,7 +74,7 @@ Tooltip HTML right after it.
```html
<div class="TooltipWrapper d-inline-block">
<a href="#" class="TooltipTarget" aria-describedby="my-tooltip-on-hover">I have a tooltip!</a>
<div id="my-tooltip-on-hover" class="Tooltip Tooltip--top">
<div id="my-tooltip-on-hover" class="Tooltip" data-spirit-placement="top">
Hello there!
<span class="Tooltip__arrow"></span>
</div>
Expand All @@ -78,7 +87,7 @@ Add `tabindex="0"` to non-focusable elements to ensure keyboard accessibility.
```html
<div class="TooltipWrapper d-inline-block">
<span class="TooltipTarget" aria-describedby="my-tooltip-on-focus" tabindex="0">I have a tooltip!</span>
<div id="my-tooltip-on-focus" class="Tooltip Tooltip--top">
<div id="my-tooltip-on-focus" class="Tooltip" data-spirit-placement="top">
Hello there!
<span class="Tooltip__arrow"></span>
</div>
Expand All @@ -95,7 +104,7 @@ Tooltip. As a workaround, you'll want to trigger the Tooltip from a wrapper
<div class="TooltipTarget" aria-describedby="my-tooltip-for-disabled-button" tabindex="0">
<button type="button" disabled>I have a tooltip though I'm disabled</button>
</div>
<div id="my-tooltip-for-disabled-button" class="Tooltip Tooltip--top">
<div id="my-tooltip-for-disabled-button" class="Tooltip" data-spirit-placement="top">
Hello there!
<span class="Tooltip__arrow"></span>
</div>
Expand All @@ -114,7 +123,7 @@ kind of task Tooltip responds to interaction classes `is-hidden` and
<button type="button" id="tooltip-trigger" data-spirit-target="#my-js-controlled-tooltip">Toggle tooltip</button>
<div class="TooltipWrapper d-inline-block">
<div aria-describedby="my-js-controlled-tooltip">I have an externally-triggered tooltip</div>
<div id="my-js-controlled-tooltip" class="Tooltip Tooltip--top is-hidden">
<div id="my-js-controlled-tooltip" class="Tooltip is-hidden" data-spirit-placement="top">
Hello there!
<span class="Tooltip__arrow"></span>
</div>
Expand All @@ -141,7 +150,7 @@ Tooltip can be made dismissible by following these steps:
attributes on the closing button.

```html
<div id="my-dismissible-tooltip" class="Tooltip Tooltip--right Tooltip--dismissible">
<div id="my-dismissible-tooltip" class="Tooltip Tooltip--dismissible" data-spirit-placement="right">
Close me
<button
type="button"
Expand Down Expand Up @@ -170,20 +179,31 @@ purpose.

### Placement

When controlling Tooltip position with JavaScript, use `data-spirit-placement`
attribute instead of CSS modifiers (`Tooltip--top` etc.) to set Tooltip
placement. All [Placement Dictionary][dictionary-placement] values are supported.
When controlling Tooltip position with JavaScript, set `data-spirit-tooltip-controlled`
on the `.Tooltip` to control it and prevent conflicts with the default CSS positioning.

```html
<div id="my-advanced-tooltip" class="Tooltip" data-spirit-placement="top">
<div id="my-advanced-tooltip" class="Tooltip" data-spirit-tooltip-controlled>
Hello there!
<span class="Tooltip__arrow"></span>
</div>
```

### Arrow

When controlling Tooltip arrow with JavaScript, set `data-spirit-element="arrow"`
on the `.Tooltip__arrow` to control it and prevent conflicts with the default CSS positioning.

```html
<div id="my-advanced-tooltip" class="Tooltip" data-spirit-tooltip-controlled>
Hello there!
<span class="Tooltip__arrow" data-spirit-element="arrow"></span>
</div>
```

### Example

💻 Check our minimum [example] that uses external library
💻 Check our [example] that uses external library
[Floating UI][floating-ui] (see the [JS source](./floating-ui-example.mjs)).

👉 Please consult [Floating UI][floating-ui] documentation to understand how it
Expand Down
9 changes: 8 additions & 1 deletion packages/web/src/scss/components/Tooltip/_Tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,17 @@
}

// Controlled placement
.Tooltip[data-spirit-tooltip-controlled],
// @deprecated This is deprecated and will be removed in the next major version, use the above instead
.Tooltip[data-spirit-placement] {
@include placement.child-controlled($prefix: 'tooltip');
@include placement.child-controlled($prefix: 'tooltip', $offset: theme.$arrow-height);
}

.Tooltip[data-spirit-tooltip-controlled] .Tooltip__arrow {
@include placement.arrow-controlled();
}

// @deprecated This is deprecated and will be removed in the next major version
@include dictionaries.generate-controlled-placements(
$class-name: 'Tooltip',
$dictionary-values: theme.$placement-dictionary,
Expand Down
83 changes: 78 additions & 5 deletions packages/web/src/scss/components/Tooltip/floating-ui-example.mjs
Original file line number Diff line number Diff line change
@@ -1,23 +1,96 @@
// To fully understand Floating UI and its options, please consult Floating UI docs:
// @see https://floating-ui.com

import { autoUpdate, computePosition, flip } from 'https://cdn.skypack.dev/@floating-ui/[email protected]';
import {
arrow,
autoUpdate,
computePosition,
flip,
offset,
limitShift,
shift,
size,
} from 'https://cdn.skypack.dev/@floating-ui/[email protected]';

const button = document.getElementById('my-button');
const tooltip = document.getElementById('my-advanced-tooltip');
const result = document.getElementById('my-advanced-result');
const select = document.getElementById('my-advanced-select');
const viewport = document.getElementById('my-advanced-viewport');
const content = document.getElementById('my-advanced-content');
const arrowEl = tooltip.querySelector('[data-spirit-element="arrow"]');

const cleanup = autoUpdate(button, tooltip, () => {
const tooltipComputedStyle = window.getComputedStyle(tooltip);
const tooltipMaxWidth = parseInt(tooltipComputedStyle.maxWidth, 10);
const tooltipOffset = parseInt(tooltipComputedStyle.getPropertyValue('--tooltip-offset'), 10);
const arrowCornerOffset = parseInt(
window.getComputedStyle(arrowEl).getPropertyValue('--tooltip-arrow-corner-offset'),
10,
);

function updateTooltipPosition() {
computePosition(button, tooltip, {
placement: 'top',
middleware: [flip()],
}).then(({ x, y, placement }) => {
placement: tooltip.dataset.spiritPlacement,
middleware: [
offset(tooltipOffset),
flip({
crossAxis: false,
}),
shift({
limiter: limitShift({
offset: ({ rects }) => ({
mainAxis: rects.reference.height,
}),
}),
}),
size({
apply({ availableWidth }) {
Object.assign(tooltip.style, {
maxWidth: `${tooltipMaxWidth < availableWidth ? tooltipMaxWidth : availableWidth}px`,
});
},
}),
arrow({ element: arrowEl, padding: arrowCornerOffset }), // arrow() should be placed at the end
],
}).then(({ x, y, middlewareData, placement }) => {
Object.assign(tooltip.style, {
top: `${y}px`,
left: `${x}px`,
});

const side = placement.split('-')[0];

const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right',
}[side];

if (middlewareData.arrow) {
const divider = staticSide === 'top' || staticSide === 'bottom' ? 2 : 4;
const { x, y } = middlewareData.arrow;
Object.assign(arrowEl.style, {
left: x != null ? `${x}px` : '',
top: y != null ? `${y}px` : '',
[staticSide]: `${-arrowEl.offsetWidth + arrowCornerOffset / divider}px`,
});
}

tooltip.dataset.spiritPlacement = placement;
result.value = placement;
});
}

window.onload = () => {
viewport.scrollLeft = (content.offsetWidth - viewport.offsetWidth) / 2;
};

const cleanup = autoUpdate(button, tooltip, updateTooltipPosition);

select.addEventListener('change', () => {
tooltip.dataset.spiritPlacement = select.value;
updateTooltipPosition();
});

// Call cleanup function when tooltip is removed from DOM.
Expand Down
Loading

0 comments on commit de6953e

Please sign in to comment.