Skip to content

Commit

Permalink
feat(ui-library): split tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel.seemann committed Nov 22, 2023
1 parent a9c973a commit 0ba0fa1
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 130 deletions.
40 changes: 20 additions & 20 deletions packages/ui-library/src/components/feedback/tooltip/index.css.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,61 @@ export const { tokenizedLight: tooltipLight, tokenizedDark: tooltipDark } = rend
const { UI } = semanticTokens;

return typeSafeNestedCss`
.tooltip {
:host {
background-color: ${Tooltip.SurfaceFill};
border-radius: ${Tooltip.ContentCol.BorderRadius};
left: 0;
max-width: ${Tooltip.MaxWidth};
min-width: ${Tooltip.MinWidth};
opacity: 0;
opacity: 1;
position: absolute;
top: 0;
visibility: hidden;
visibility: visible;
width: max-content;
.content {
color: ${Tooltip.Content};
padding: ${Tooltip.ContentCol.Padding};
}
&.elevation {
.elevation {
filter: drop-shadow(0 0 1px ${Tooltip.SurfaceFill});
}
&.sm .content {
.sm .content {
font-family: ${UI.Caption.SM.fontFamily}, sans-serif;
font-size: ${UI.Caption.SM.fontSize};
font-weight: ${UI.Caption.SM.fontWeight};
line-height: ${UI.Caption.SM.lineHeight};
}
&.md .content {
.md .content {
font-family: ${UI.Caption.MD.fontFamily}, sans-serif;
font-size: ${UI.Caption.MD.fontSize};
font-weight: ${UI.Caption.MD.fontWeight};
line-height: ${UI.Caption.MD.lineHeight};
}
&.lg .content {
.lg .content {
font-family: ${UI.Caption.LG.fontFamily}, sans-serif;
font-size: ${UI.Caption.LG.fontSize};
font-weight: ${UI.Caption.LG.fontWeight};
line-height: ${UI.Caption.LG.lineHeight};
}
}
.arrow {
align-items: flex-end;
display: flex;
height: 12px;
justify-content: center;
position: absolute;
width: 12px;
z-index: 1;
}
.arrow path {
fill: ${Tooltip.SurfaceFill};
.arrow {
align-items: flex-end;
display: flex;
height: 12px;
justify-content: center;
position: absolute;
width: 12px;
z-index: 1;
> svg > path {
fill: ${Tooltip.SurfaceFill};
}
}
}
`;
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { html } from 'lit';
import { LitElement, html } from 'lit';

import { BlrTooltipRenderFunction, BlrTooltipType } from './index';
import { Themes } from '../../../foundation/_tokens-generated/index.themes';
import { ThemeType, Themes } from '../../../foundation/_tokens-generated/index.themes';
import { TooltipPlacement } from '../../../globals/constants';
import { BlrTooltipElementRenderFunction } from './tooltipElement';
import { setupTooltip } from './setupTooltip';
import { customElement, property, query } from 'lit/decorators.js';
import { FormSizesType } from '../../../globals/types';
import { Placement as PlacementType } from '@floating-ui/dom';

export default {
title: 'Design System/Web Components/Feedback/Tooltip',
Expand All @@ -18,23 +23,36 @@ export default {
},
};

export const BlrTooltipLargeReference = (params: BlrTooltipType) => html`<div
style="height:500px; display: flex; justify-content: center; flex-direction: row; align-content: center; align-items: center;"
export const BlrTooltip = (params: BlrTooltipType) => html`<div
style="height: 500px; display: flex; justify-content: center; flex-direction: row; align-content: center; align-items: center;"
>
${BlrTooltipRenderFunction(
params,
html`<div style="height: 300px; width: 300px; background-color: lightblue"></div>`
html`<div style="height: 200px; width: 200px; background-color: lightblue"></div>`
)}
</div>`;

export const BlrTooltip2SmallReference = (params: BlrTooltipType) => html`<div
style="height:500px; display: flex; justify-content: center; flex-direction: row; align-content: center; align-items: center;"
export const BlrTooltipSmallReference = (params: BlrTooltipType) => html`<div
style="height: 500px; display: flex; justify-content: center; flex-direction: row; align-content: center; align-items: center;"
>
${BlrTooltipRenderFunction(params, html`<div style="height: 20px; width: 20px; background-color: lightblue"></div>`)}
</div>`;

BlrTooltipLargeReference.storyName = 'Tooltip Large Reference';
BlrTooltip2SmallReference.storyName = 'Tooltip Small Reference';
export const BlrTooltipVirtualReference = (params: BlrTooltipType) => {
return html` <virtual-reference
theme=${params.theme}
text=${params.text}
size=${params.size}
placement=${params.placement}
hasArrow=${params.hasArrow}
elevation=${params.elevation}
offset=${params.offset}
></virtual-reference>`;
};

BlrTooltip.storyName = 'Tooltip Large Reference';
BlrTooltipSmallReference.storyName = 'Tooltip Small Reference';
BlrTooltipVirtualReference.storyName = 'Tooltip Virtual Reference';

const args: BlrTooltipType = {
theme: 'Light',
Expand All @@ -46,5 +64,51 @@ const args: BlrTooltipType = {
offset: 4,
};

BlrTooltip2SmallReference.args = args;
BlrTooltipLargeReference.args = args;
BlrTooltip.args = args;
BlrTooltipSmallReference.args = args;
BlrTooltipVirtualReference.args = args;

@customElement('virtual-reference')
export class VirtualReference extends LitElement {
@property() theme: ThemeType = 'Light';
@property() size: FormSizesType = 'sm';
@property() text!: string;
@property() placement: PlacementType = 'top';
@property() hasArrow = true;
@property() elevation = true;
@property() offset = 4;

@query('blr-tooltip-element')
protected _tooltip!: HTMLElement;

protected firstUpdated() {
document.addEventListener('mousemove', ({ clientX, clientY }) => {
const virtualReference = {
getBoundingClientRect() {
return {
width: 0,
height: 0,
x: clientX,
y: clientY,
left: clientX,
right: clientX,
top: clientY,
bottom: clientY,
};
},
};

setupTooltip(virtualReference, this._tooltip, this.placement, 4);
});
}

render() {
return html`${BlrTooltipElementRenderFunction({
theme: this.theme,
text: this.text,
size: this.size,
hasArrow: this.hasArrow,
elevation: this.elevation,
})}`;
}
}
125 changes: 26 additions & 99 deletions packages/ui-library/src/components/feedback/tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -1,108 +1,47 @@
import { LitElement, TemplateResult, html, nothing } from 'lit';
import { LitElement, TemplateResult, html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { tooltipLight, tooltipDark } from './index.css';
import { computePosition, flip, offset, arrow, Placement, autoUpdate } from '@floating-ui/dom';
import type { VirtualElement } from '@floating-ui/core';

Check warning on line 3 in packages/ui-library/src/components/feedback/tooltip/index.ts

View workflow job for this annotation

GitHub Actions / eslint

'VirtualElement' is defined but never used
import { Placement as PlacementType } from '@floating-ui/dom';
import { FormSizesType } from '../../../globals/types';
import { ThemeType } from '../../../foundation/_tokens-generated/index.themes';
import { genericBlrComponentRenderer } from '../../../utils/typesafe-generic-component-renderer';
import { classMap } from 'lit/directives/class-map.js';
import { componentTokens } from '../../../foundation/_tokens-generated/__component-tokens.Light.generated.mjs';
import { setupTooltip } from './setupTooltip';
import { BlrTooltipElementRenderFunction } from './tooltipElement';

const TAG_NAME = 'blr-tooltip';

const enterEvents = ['pointerenter', 'focus'];
const leaveEvents = ['pointerleave', 'blur', 'keydown', 'click'];

const {
Feedback: { Tooltip },
} = componentTokens;

@customElement('blr-tooltip')
export class BlrTooltip extends LitElement {
@property() theme?: ThemeType = 'Light';
@property() theme: ThemeType = 'Light';
@property() size: FormSizesType = 'sm';
@property() text!: string;
@property() placement?: Placement = 'top';
@property() hasArrow? = true;
@property() placement: PlacementType = 'top';
@property() hasArrow = true;
@property() elevation = true;
@property() offset = 4;

@query('.tooltip')
@query('blr-tooltip-element')
protected _tooltip!: HTMLElement;

@query('.arrow')
protected _arrow!: HTMLElement;

protected arrowHeight = 4;

protected _slotTrigger: Element | undefined = undefined;

connectedCallback() {
super.connectedCallback();
}
protected _slotReference: Element | undefined = undefined;

protected firstUpdated() {
const slot = this?.shadowRoot?.querySelector('slot');
this._slotTrigger = slot?.assignedElements({ flatten: true })[0];
const slot = this.shadowRoot?.querySelector('slot');
this._slotReference = slot?.assignedElements({ flatten: true })[0];

enterEvents.forEach((event) => this._slotTrigger?.addEventListener(event, this.show));
leaveEvents.forEach((event) => this._slotTrigger?.addEventListener(event, this.hide));
enterEvents.forEach((event) => this._slotReference?.addEventListener(event, this.show));
leaveEvents.forEach((event) => this._slotReference?.addEventListener(event, this.hide));
}

protected setupTooltip = (trigger: Element, tooltip: HTMLElement) => {
autoUpdate(trigger, tooltip, () => {
computePosition(trigger, tooltip, {
placement: this.placement,
middleware: [
offset((this._arrow && this.arrowHeight) + this.offset),
flip(),
this._arrow && arrow({ element: this._arrow, padding: 8 }),
],
}).then(({ x, y, middlewareData, placement }) => {
Object.assign(this._tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});

if (middlewareData.arrow) {
const { x, y, centerOffset } = middlewareData.arrow;

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

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

const staticRotation = {
top: 'rotate(0)',
right: 'rotate(90deg)',
bottom: 'rotate(180deg)',
left: 'rotate(-90deg)',
}[side];

Object.assign(this._arrow.style, {
top: isCentered && y !== null ? `${y}px` : '',
right: !isCentered && x !== null ? `${x}px` : '',
bottom: !isCentered && y !== null ? `${y}px` : '',
left: isCentered && x !== null ? `${x}px` : '',
transform: staticRotation,
[staticSide]: `-${this.arrowHeight}px`,
});
}
});
});
};

protected show = () => {
if (!this._slotTrigger) {
if (!this._slotReference) {
return;
}

this.setupTooltip(this._slotTrigger, this._tooltip);
setupTooltip(this._slotReference, this._tooltip, this.placement, this.offset);

this._tooltip.style.visibility = 'visible';
this._tooltip.style.opacity = '1';
Expand All @@ -114,32 +53,20 @@ export class BlrTooltip extends LitElement {
};

protected render() {
const dynamicStyles = this.theme === 'Light' ? [tooltipLight] : [tooltipDark];

const classes = classMap({
tooltip: true,
[this.size]: this.size,
elevation: this.elevation,
});

return html` <style>
${dynamicStyles}
</style>
return html` <div class="container">
<slot></slot>
<div class="${classes}">
<div class="content">${this.text}</div>
${this.hasArrow
? html`<div class="arrow">
<svg width="12" height="4" fill="none" viewBox="0 0 12 4">
<path d="M6 4C3.738 4 3 0 0 0h12C9 0 8.262 4 6 4Z" />
</svg>
</div>`
: nothing}
</div>`;
${BlrTooltipElementRenderFunction({
theme: this.theme,
text: this.text,
size: this.size,
hasArrow: this.hasArrow,
elevation: this.elevation,
})}
</div>`;
}
}

export type BlrTooltipType = Omit<BlrTooltip, keyof LitElement>;

export const BlrTooltipRenderFunction = (params: BlrTooltipType, children?: TemplateResult<1>) =>
export const BlrTooltipRenderFunction = (params: BlrTooltipType, children: TemplateResult<1>) =>
genericBlrComponentRenderer<BlrTooltipType>(TAG_NAME, { ...params }, children);
10 changes: 10 additions & 0 deletions packages/ui-library/src/components/feedback/tooltip/indexReact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { createComponent } from '@lit-labs/react';

import { BlrTooltip } from '.';

export const BlrToolTipReact = createComponent({
tagName: 'blr-tooltip',
elementClass: BlrTooltip,
react: React,
});
Loading

0 comments on commit 0ba0fa1

Please sign in to comment.