implement a tooltip which always floats correctly

This commit is contained in:
Kiril Videlov 2023-03-29 17:12:26 +02:00 committed by Kiril Videlov
parent af8cd41476
commit f948f0d24f
6 changed files with 199 additions and 2 deletions

View File

@ -82,6 +82,7 @@
"storybook": "^7.0.0-rc.8",
"svelte": "^3.55.1",
"svelte-check": "^3.0.1",
"svelte-floating-ui": "^1.5.2",
"tailwindcss": "^3.1.5",
"tslib": "^2.4.1",
"typescript": "^4.8.4",

View File

@ -63,6 +63,7 @@ specifiers:
storybook: ^7.0.0-rc.8
svelte: ^3.55.1
svelte-check: ^3.0.1
svelte-floating-ui: ^1.5.2
svelte-french-toast: ^1.0.3
tailwindcss: ^3.1.5
tauri-plugin-log-api: github:tauri-apps/tauri-plugin-log
@ -139,6 +140,7 @@ devDependencies:
storybook: 7.0.0-rc.8
svelte: 3.55.1
svelte-check: 3.0.3_gqx7lw3sljhsd4bstor5m2aa2u
svelte-floating-ui: 1.5.2
tailwindcss: 3.2.4_postcss@8.4.21
tslib: 2.5.0
typescript: 4.9.5
@ -2029,6 +2031,16 @@ packages:
resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==}
dev: true
/@floating-ui/core/1.2.5:
resolution: {integrity: sha512-qrcbyfnRVziRlB6IYwjCopYhO7Vud750JlJyuljruIXcPxr22y8zdckcJGsuOdnQ639uVD1tTXddrcH3t3QYIQ==}
dev: true
/@floating-ui/dom/1.2.5:
resolution: {integrity: sha512-+sAUfpQ3Frz+VCbPCqj+cZzvEESy3fjSeT/pDWkYCWOBXYNNKZfuVsHuv8/JO2zze8+Eb/Q7a6hZVgzS81fLbQ==}
dependencies:
'@floating-ui/core': 1.2.5
dev: true
/@humanwhocodes/config-array/0.11.8:
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
engines: {node: '>=10.10.0'}
@ -2759,6 +2771,17 @@ packages:
telejson: 7.0.4
dev: true
/@storybook/channel-postmessage/7.0.0-rc.9:
resolution: {integrity: sha512-iaEwRN4PvIzxzH8dGXKSKVkepC2+70O6MM4Z+zDOyN1Q6p4grYOddM2Olqdb9cf3TaYES5JENPe5ig62JLu2zg==}
dependencies:
'@storybook/channels': 7.0.0-rc.9
'@storybook/client-logger': 7.0.0-rc.9
'@storybook/core-events': 7.0.0-rc.9
'@storybook/global': 5.0.0
qs: 6.11.1
telejson: 7.0.4
dev: true
/@storybook/channel-websocket/7.0.0-rc.8:
resolution: {integrity: sha512-BcWlji7W98+pmn8Xhk7mC/oKVljipyOIqxzJFPDgLE9GFRshwoL3j5qBVhHS5aSGFyqNi8VhBp5FztFgsbLNJA==}
dependencies:
@ -2772,6 +2795,10 @@ packages:
resolution: {integrity: sha512-2tI/ECbQcXjncYGLVdrttNT8adIp6kV/bnQGJWmF5hBXZ7Izwyq1WRPTgPT++RihmOOTHvkRx4GCKfwluOrNpA==}
dev: true
/@storybook/channels/7.0.0-rc.9:
resolution: {integrity: sha512-XqgHzEqAlNG9lfNjYTLepXQNafoD7cUSDSFbZkpOAUlYRsBz0cyhCGyxAWDyH7er8ZJCCIfxsEwZVaMGg9bLFQ==}
dev: true
/@storybook/cli/7.0.0-rc.8:
resolution: {integrity: sha512-RUiiHCUEHPYMlw2Li3ubVKwy6aKvHPlrWvUhUcAtWK+iWo8oia+3A/vCo4aCQvpOIuhAIICz6ipg9vhm1Bw7zQ==}
hasBin: true
@ -2827,6 +2854,12 @@ packages:
'@storybook/global': 5.0.0
dev: true
/@storybook/client-logger/7.0.0-rc.9:
resolution: {integrity: sha512-ihBDLgfECGXaP54KXMcTLokOXdKryoKSF6UEErJpzb/Foq8g/OQSaklSjx0xdoEt01JT+cJEdQOLAewNK/4fYg==}
dependencies:
'@storybook/global': 5.0.0
dev: true
/@storybook/codemod/7.0.0-rc.8:
resolution: {integrity: sha512-3WtdAHlcWiOZhdq5JhQ+fYQbOglkg6IS5nd9ns4It/u/l7b6Zt3EpLwtqduav/QbaTngo4jzpV/97eW9VKXXXg==}
dependencies:
@ -2903,6 +2936,10 @@ packages:
resolution: {integrity: sha512-KsKf+Ob6zQ8+IJ9oDD5xqASwYGzcjT08azBjSt4yocHIJ3mY741h88YDS0wcwnM+JrV6iFYlY0hiK35lnBEddA==}
dev: true
/@storybook/core-events/7.0.0-rc.9:
resolution: {integrity: sha512-FDEDaaksKJFGeaME1lnxwQr7AQxBFZoPJD8jX9t5hniFtouJ7Pdn8lGDueJME2XNuklT4VZHAUwFVO3WcbxbzA==}
dev: true
/@storybook/core-server/7.0.0-rc.8:
resolution: {integrity: sha512-IpA5RY+xRG/gfzPoJMO3PBgZgu0bgevta8KGskQ01ljCP5JNRfbU3fEdY28Dai/xq1Jaj0WR1xyGHuCZgi5D+w==}
dependencies:
@ -3019,6 +3056,16 @@ packages:
'@storybook/preview-api': 7.0.0-rc.8
dev: true
/@storybook/instrumenter/7.0.0-rc.9:
resolution: {integrity: sha512-Uyq4ZjuEcG7ThZqq4L0YsGX5LnbP85yi25k+1ySkr677eft0VpTp5U37jmyChGkoI03kghVNvM16LTgVuQ83kw==}
dependencies:
'@storybook/channels': 7.0.0-rc.9
'@storybook/client-logger': 7.0.0-rc.9
'@storybook/core-events': 7.0.0-rc.9
'@storybook/global': 5.0.0
'@storybook/preview-api': 7.0.0-rc.9
dev: true
/@storybook/manager-api/7.0.0-rc.8_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-PV3N2tvUp6BTehgKt7JwwYH+ALoe7k2iKMeMKPNGwoRvbESmJfFEdAK95DyIZoLm8LUUtX1FpzcUek/7YWJ17w==}
peerDependencies:
@ -3086,6 +3133,26 @@ packages:
util-deprecate: 1.0.2
dev: true
/@storybook/preview-api/7.0.0-rc.9:
resolution: {integrity: sha512-KcbqKm4Ge0G8iZNzCnIzGKY7ZD4L6140BQexid0t6dDvBRGavkUXvg3IH+nzXFTHW4H9lW3yFY3zbPANjUexXw==}
dependencies:
'@storybook/channel-postmessage': 7.0.0-rc.9
'@storybook/channels': 7.0.0-rc.9
'@storybook/client-logger': 7.0.0-rc.9
'@storybook/core-events': 7.0.0-rc.9
'@storybook/csf': 0.0.2-next.10
'@storybook/global': 5.0.0
'@storybook/types': 7.0.0-rc.9
'@types/qs': 6.9.7
dequal: 2.0.3
lodash: 4.17.21
memoizerific: 1.11.3
qs: 6.11.1
synchronous-promise: 2.0.17
ts-dedent: 2.2.0
util-deprecate: 1.0.2
dev: true
/@storybook/preview/7.0.0-rc.8:
resolution: {integrity: sha512-ZWf/0MXR+6V19fXcNE9Sy0DCRuR1+P8S4Ai7FSy9iUhckk7QTmtgzV+Ob+2NIltZqWjbtDXHquBB6fcJqXqNmg==}
dev: true
@ -3201,8 +3268,8 @@ packages:
/@storybook/testing-library/0.0.14-next.1:
resolution: {integrity: sha512-1CAl40IKIhcPaCC4pYCG0b9IiYNymktfV/jTrX7ctquRY3akaN7f4A1SippVHosksft0M+rQTFE0ccfWW581fw==}
dependencies:
'@storybook/client-logger': 7.0.0-rc.8
'@storybook/instrumenter': 7.0.0-rc.8
'@storybook/client-logger': 7.0.0-rc.9
'@storybook/instrumenter': 7.0.0-rc.9
'@testing-library/dom': 8.20.0
'@testing-library/user-event': 13.5.0_yxlyej73nftwmh2fiao7paxmlm
ts-dedent: 2.2.0
@ -3231,6 +3298,15 @@ packages:
file-system-cache: 2.0.2
dev: true
/@storybook/types/7.0.0-rc.9:
resolution: {integrity: sha512-fnlEM+p1h8KAESKa0g+JK6FC5pwS6rci+vG1TexzMWPyk87bJeqZb7aFhhhd5t7UK5CQnZjka9t8RXD7atXk7A==}
dependencies:
'@storybook/channels': 7.0.0-rc.9
'@types/babel__core': 7.20.0
'@types/express': 4.17.17
file-system-cache: 2.0.2
dev: true
/@sveltejs/adapter-static/1.0.0-next.50_l5ueyfihz3gpzzvvyo2ean5u3e:
resolution: {integrity: sha512-xZKBmiwFGW8nrH8+eysUAAo9XrtApI81q0m67y1bexVw8IY7/x741b6VEklNM7BZ7js0Mi2x+yCkHpOee8UZKQ==}
peerDependencies:
@ -7862,6 +7938,13 @@ packages:
- sugarss
dev: true
/svelte-floating-ui/1.5.2:
resolution: {integrity: sha512-nV50eno74CEsFfFJ6iyN/oNYDEOck1TZjGV3lmJksVRbiiUAVF6bHspyAhR7GZ7c/4qbRWp9UyX24J+UXdEpag==}
dependencies:
'@floating-ui/core': 1.2.5
'@floating-ui/dom': 1.2.5
dev: true
/svelte-french-toast/1.0.3_svelte@3.55.1:
resolution: {integrity: sha512-HBdutlqUx5FroucvcmMJstmTJDedXayiOSXubBYW6iL5x1PdHp+bOflGx5NxsOa6+EQckJnnVv1vJYgGsg7ngQ==}
dependencies:

View File

@ -0,0 +1,72 @@
<script lang="ts">
import { offset, flip, shift } from 'svelte-floating-ui/dom';
import { arrow } from 'svelte-floating-ui';
import { createFloatingActions } from 'svelte-floating-ui';
import { writable } from 'svelte/store';
let arrowRef = writable({} as HTMLElement);
export let label: string;
const [floatingRef, floatingContent, update] = createFloatingActions({
strategy: 'absolute',
placement: 'bottom',
middleware: [offset(6), flip(), shift(), arrow({ element: arrowRef })],
onComputed({ placement, middlewareData }) {
if (!middlewareData.arrow) return;
const { x, y } = middlewareData.arrow;
const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right'
}[placement.split('-')[0]] as any;
if (!$arrowRef) return;
Object.assign($arrowRef.style, {
left: x != null ? `${x}px` : '',
top: y != null ? `${y}px` : '',
[staticSide]: '-4px'
});
}
});
let showTooltip = false;
</script>
<div
class="h-fit w-fit"
on:mouseenter={() => (showTooltip = true)}
on:mouseleave={() => (showTooltip = false)}
use:floatingRef
>
<slot />
</div>
{#if showTooltip}
<div
role="tooltip"
class="
absolute
rounded-[5px]
bg-[#171717]
p-2
text-base
text-[#FAFAFA]
shadow-lg
"
use:floatingContent
>
{label}
<div
class="
absolute
h-3
w-3 rotate-45
rounded-sm
bg-[#171717]
"
bind:this={$arrowRef}
/>
</div>
{/if}

View File

@ -7,3 +7,4 @@ export { default as Modal } from './Modal.svelte';
export { default as Button } from './Button.svelte';
export { default as ButtonGroup } from './ButtonGroup.svelte';
export { default as Dialog } from './Dialog.svelte';
export { default as Tooltip } from './Tooltip.svelte';

View File

@ -0,0 +1,22 @@
import type { Meta, StoryObj } from '@storybook/svelte';
import TooltipOnText from './TooltipOnText.svelte';
// More on how to set up stories at: https://storybook.js.org/docs/7.0/svelte/writing-stories/introduction
const meta: Meta<TooltipOnText> = {
title: 'GitButler/Tooltip',
component: TooltipOnText,
tags: ['autodocs'],
argTypes: {
label: { control: 'text' }
}
};
export default meta;
type Story = StoryObj<TooltipOnText>;
export const TextWithTooltip: Story = {
args: {
label: 'This is a tooltip'
}
};

View File

@ -0,0 +1,18 @@
<script lang="ts">
import Tooltip from '$lib/components/Tooltip.svelte';
export let label: string;
</script>
<!--
@component
This exists only to facilitate testing of the `Tooltip` component in Storybook.
Do not use this in the app.
Do not style this.
Do not add any logic to this.
-->
<div class="bg-[#737373] p-16">
<Tooltip {label}>
<span>Hover me!</span>
</Tooltip>
</div>