Skip to content

Commit

Permalink
Feat(web-twig): Introduce Full Floating UI demo to Tooltip, update in…
Browse files Browse the repository at this point in the history
…ternal implementation #DS-1084

Switch to data attribute placement instead of modification class based.
  • Loading branch information
crishpeen committed Dec 7, 2023
1 parent 02c3ddf commit ce54869
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,34 @@

{% block content %}

<DocsSection title="Placements" stackAlignment="stretch">
{% include '@components/Tooltip/stories/TooltipPlacements.twig' %}
</DocsSection>

<DocsSection title="Static Tooltip (No Interaction)" stackAlignment="stretch">
{% include '@components/Tooltip/stories/TooltipStatic.twig' %}
</DocsSection>

<DocsSection title="Tooltip on Hover (Pure CSS)" stackAlignment="stretch">
{% include '@components/Tooltip/stories/TooltipOnHover.twig' %}
</DocsSection>

<DocsSection title="Tooltip on Click (JavaScript)">
{% include '@components/Tooltip/stories/TooltipClickable.twig' %}
</DocsSection>

<DocsSection title="Dismissible Tooltip">
{% include '@components/Tooltip/stories/TooltipDismissible.twig' %}
</DocsSection>

<DocsSection title="Dismissible Tooltip via JS API">
{% include '@components/Tooltip/stories/TooltipDismissibleViaJS.twig' %}
</DocsSection>

<DocsSection title="Advanced Positioning">
{% include '@components/Tooltip/stories/TooltipFloatingUI.twig' %}
</DocsSection>
<div class="spirit-feature-tooltip-enable-data-placement">
<DocsSection title="Placements" stackAlignment="stretch">
{% include '@components/Tooltip/stories/TooltipPlacements.twig' %}
</DocsSection>

<DocsSection title="Static Tooltip (No Interaction)" stackAlignment="stretch">
{% include '@components/Tooltip/stories/TooltipStatic.twig' %}
</DocsSection>

<DocsSection title="Tooltip on Hover (Pure CSS)" stackAlignment="stretch">
{% include '@components/Tooltip/stories/TooltipOnHover.twig' %}
</DocsSection>

<DocsSection title="Tooltip on Click (JavaScript)">
{% include '@components/Tooltip/stories/TooltipClickable.twig' %}
</DocsSection>

<DocsSection title="Dismissible Tooltip">
{% include '@components/Tooltip/stories/TooltipDismissible.twig' %}
</DocsSection>

<DocsSection title="Dismissible Tooltip via JS API">
{% include '@components/Tooltip/stories/TooltipDismissibleViaJS.twig' %}
</DocsSection>

<DocsSection title="Full Floating UI Usage">
{% include '@components/Tooltip/stories/TooltipFloatingUI.twig' %}
</DocsSection>
</div>

{% endblock %}
36 changes: 6 additions & 30 deletions packages/web-twig/src/Resources/components/Tooltip/Tooltip.twig
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,24 @@
{%- set _closeClassName = _spiritClassPrefix ~ 'Tooltip__close' -%}
{%- set _rootClassName = _spiritClassPrefix ~ 'Tooltip' -%}
{%- set _rootDismissibleClassName = _isDismissible == 'true' ? _spiritClassPrefix ~ 'Tooltip--dismissible' : null -%}
{%- set _topClassName = _spiritClassPrefix ~ 'Tooltip--top' -%}
{%- set _topLeftClassName = _spiritClassPrefix ~ 'Tooltip--topLeft' -%}
{%- set _topRightClassName = _spiritClassPrefix ~ 'Tooltip--topRight' -%}
{%- set _bottomClassName = _spiritClassPrefix ~ 'Tooltip--bottom' -%}
{%- set _bottomLeftClassName = _spiritClassPrefix ~ 'Tooltip--bottomLeft' -%}
{%- set _bottomRightClassName = _spiritClassPrefix ~ 'Tooltip--bottomRight' -%}
{%- set _leftClassName = _spiritClassPrefix ~ 'Tooltip--left' -%}
{%- set _leftTopClassName = _spiritClassPrefix ~ 'Tooltip--leftTop' -%}
{%- set _leftBottomClassName = _spiritClassPrefix ~ 'Tooltip--leftBottom' -%}
{%- set _rightClassName = _spiritClassPrefix ~ 'Tooltip--right' -%}
{%- set _rightTopClassName = _spiritClassPrefix ~ 'Tooltip--rightTop' -%}
{%- set _rightBottomClassName = _spiritClassPrefix ~ 'Tooltip--rightBottom' -%}

{# Attributes #}
{%- set _idAttr = _id ? 'id="' ~ _id | escape('html_attr') ~ '"' : null -%}
{%- set _ariaControlsAttr = _id ? 'aria-controls="' ~ _id | escape('html_attr') ~ '"' : null -%}
{%- set _dataPlacementAttr = _placement ? 'data-spirit-placement="' ~ _placement | escape('html_attr') ~ '"' : null -%}
{%- set _dataTargetAttr = _id ? 'data-spirit-target="#' ~ _id | escape('html_attr') ~ '"' : null -%}

{# Miscellaneous #}
{%- set _styleProps = useStyleProps(props) -%}
{%- set _placementClassNames = {
'top': _topClassName,
'top-left': _topLeftClassName,
'top-right': _topRightClassName,
'bottom': _bottomClassName,
'bottom-left': _bottomLeftClassName,
'bottom-right': _bottomRightClassName,
'left': _leftClassName,
'left-top': _leftTopClassName,
'left-bottom': _leftBottomClassName,
'right': _rightClassName,
'right-top': _rightTopClassName,
'right-bottom': _rightBottomClassName,
} -%}
{%- set _classNames = [ _rootClassName, _rootDismissibleClassName, _placementClassNames[_placement], _styleProps.className ] -%}
{%- set _mainPropsWithoutId = props | filter((value, prop) => prop is not same as('id')) -%}
{%- set _classNames = [ _rootClassName, _rootDismissibleClassName, _styleProps.className ] -%}
{%- set _mainPropsWithoutReservedAttributes = props | filter((value, prop) => prop not in ['id', 'data-spirit-placement']) -%}

<div
{{ mainProps(_mainPropsWithoutId) }}
{{ mainProps(_mainPropsWithoutReservedAttributes) }}
{{ styleProp(_styleProps) }}
{{ classProp(_classNames) }}
{{ _idAttr | raw }}
{{ _dataPlacementAttr | raw }}
>
{% block content %}{% endblock %}
{% if _isDismissible == 'true' %}
Expand All @@ -67,5 +43,5 @@
<VisuallyHidden>{{ _closeLabel }}</VisuallyHidden>
</button>
{% endif %}
<span class="{{ _arrowClassName }}"></span>
<span class="{{ _arrowClassName }}" data-spirit-element="arrow"></span>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
closeLabel="Close tooltip"
id="my-tooltip"
isDismissible
placement="right-top"
placement="right-start"
>
Hello there!
</Tooltip>
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
</title>
</head>
<body>
<div class="Tooltip Tooltip--bottom">
<div class="Tooltip" data-spirit-placement="bottom">
Hello there!
</div>
<!-- Render with all props -->

<div class="Tooltip Tooltip--dismissible Tooltip--rightTop" id="my-tooltip">
<div class="Tooltip Tooltip--dismissible" id="my-tooltip" data-spirit-placement="right-start">
Hello there! <button type="button" class="Tooltip__close" data-spirit-dismiss="tooltip" aria-controls="my-tooltip" data-spirit-target="#my-tooltip" aria-expanded="true"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24" fill="none" id="a79dff7a255f69bbe6e39d594aa2275b" aria-hidden="true">
<path d="M18.3 5.70997C17.91 5.31997 17.28 5.31997 16.89 5.70997L12 10.59L7.11 5.69997C6.72 5.30997 6.09 5.30997 5.7 5.69997C5.31 6.08997 5.31 6.71997 5.7 7.10997L10.59 12L5.7 16.89C5.31 17.28 5.31 17.91 5.7 18.3C6.09 18.69 6.72 18.69 7.11 18.3L12 13.41L16.89 18.3C17.28 18.69 17.91 18.69 18.3 18.3C18.69 17.91 18.69 17.28 18.3 16.89L13.41 12L18.3 7.10997C18.68 6.72997 18.68 6.08997 18.3 5.70997Z" fill="currentColor">
</path></svg> <span class="accessibility-hidden">Close tooltip</span></button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,160 @@
<p class="mb-0">
The following example is using external library <a href="https://floating-ui.com">Floating UI</a>.
Try scrolling the frame or resizing the window to see how the Tooltip behaves. The Floating UI
library is trying to keep the Tooltip in the viewport and it is also flipping, shifting and
resizing the Tooltip when it is not possible to keep it in the viewport.
</p>

<Alert color="informative">
Please note that the Floating UI library is trying to point the arrow to the center
of the trigger element. This is not possible to achieve in CSS only so our behavior
is slightly different for tooltips not using Floating UI.
</Alert>

<Select id="my-advanced-select" label="Suggested placement">
<option value="top-start" selected>top-start</option>
<option value="top">top</option>
<option value="top-end">top-end</option>
<option value="right-start">right-start</option>
<option value="right">right</option>
<option value="right-end">right-end</option>
<option value="bottom-start">bottom-start</option>
<option value="bottom">bottom</option>
<option value="bottom-end">bottom-end</option>
<option value="left-start">left-start</option>
<option value="left">left</option>
<option value="left-end">left-end</option>
</Select>

<style>
.example-viewport {
width: 40rem;
max-width: 100%;
height: 10rem;
overflow: auto;
}
.example-viewport {
width: 100%;
max-width: 100%;
height: 30rem;
overflow: auto;
}
.example-content {
position: relative;
width: 100%;
height: 20rem;
padding-block: 7rem;
text-align: center;
}
.example-content {
position: relative;
width: 300%;
height: 60rem;
padding-block: 12rem;
text-align: center;
}
</style>

<p>
The following example is using external library <a href="https://floating-ui.com">Floating UI</a>.
</p>
<p>🖱 Try scrolling the example to see how Tooltip placement is updated.</p>

<div class="example-viewport bg-cover">
<div class="example-content">
<Button
type="button"
id="my-button"
UNSAFE_className="Button Button--primary Button--medium"
aria-describedby="my-advanced-tooltip"
>
I have a flipping tooltip!
</Button>
<Tooltip id="my-advanced-tooltip" UNSAFE_className="Tooltip" data-spirit-placement="top">
Hello there!
<span class="Tooltip__arrow"></span>
</Tooltip>
</div>
<div class="example-viewport bg-cover" id="my-advanced-viewport">
<div class="example-content" id="my-advanced-content">
<Button
id="my-button"
aria-describedby="my-advanced-tooltip"
>
I have a Floating tooltip!
</Button>
<Tooltip id="my-advanced-tooltip" placement="top-start" data-spirit-placement-controlled>
This long tooltip is flipping, resizing and shifting to stay in the viewport.
Also its arrow is always trying to point to the center of the trigger.
</Tooltip>
</div>
</div>

<script type="module">
// 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 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 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,
);
const cleanup = autoUpdate(button, tooltip, () => {
computePosition(button, tooltip, {
placement: 'top',
middleware: [flip()],
}).then(({ x, y, placement }) => {
Object.assign(tooltip.style, {
top: `${y}px`,
left: `${x}px`,
function updateTooltipPosition() {
computePosition(button, tooltip, {
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 offset =
staticSide === 'top' || staticSide === 'bottom'
? arrowEl.offsetHeight
: (arrowEl.offsetHeight + arrowEl.offsetWidth) / 2;
const { x, y } = middlewareData.arrow;
Object.assign(arrowEl.style, {
left: x != null ? `${x}px` : '',
top: y != null ? `${y}px` : '',
bottom: '',
right: '',
[staticSide]: `-${offset}px`,
});
}
tooltip.dataset.spiritPlacement = placement;
});
tooltip.dataset.spiritPlacement = 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 ce54869

Please sign in to comment.