Skip to content
This repository was archived by the owner on Oct 14, 2025. It is now read-only.

Commit ec8d597

Browse files
authored
Implement Tooltip page and example (#47)
* Tooltip page updated, Preview comp added, tooltip example * Initial page copy * Polish and refine * Linting
1 parent 3ae279e commit ec8d597

File tree

10 files changed

+3259
-3727
lines changed

10 files changed

+3259
-3727
lines changed

pnpm-lock.yaml

Lines changed: 2982 additions & 3632 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/docs/components/Navigation/Navigation.svelte

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { page } from '$app/stores';
33
import { drawer } from '$docs/stores.svelte';
44
// Icons
5-
import IconStart from 'lucide-svelte/icons/rocket';
5+
import IconApi from 'lucide-svelte/icons/cog';
66
import IconTooltips from 'lucide-svelte/icons/message-square';
77
import IconPopovers from 'lucide-svelte/icons/square-mouse-pointer';
88
import IconModals from 'lucide-svelte/icons/layers-2';
@@ -14,19 +14,19 @@
1414
let { classes = '' } = $props();
1515
1616
// Navigation
17-
const navExternal = [
18-
{
19-
icon: IconStart,
20-
href: 'https://github.com/skeletonlabs/floating-ui-svelte?tab=readme-ov-file#floating-ui-svelte',
21-
label: 'Getting Started'
22-
}
23-
];
2417
const navHooks = [
2518
{ icon: IconTooltips, href: '/tooltips', label: 'Tooltips' },
2619
{ icon: IconPopovers, href: '/popovers', label: 'Popovers' },
2720
{ icon: IconModals, href: '/modals', label: 'Modals' },
2821
{ icon: IconContextMenus, href: '/context-menus', label: 'Context Menus' }
2922
];
23+
const navExternal = [
24+
{
25+
icon: IconApi,
26+
href: 'https://github.com/skeletonlabs/floating-ui-svelte?tab=readme-ov-file#floating-ui-svelte',
27+
label: 'API Reference'
28+
}
29+
];
3030
3131
// FIXME: Remove when Svelte 5 supports $page, see: https://github.com/sveltejs/eslint-plugin-svelte/issues/652
3232
// eslint-disable-next-line svelte/valid-compile
@@ -46,15 +46,14 @@
4646
</header>
4747
<!-- Nav List -->
4848
<nav class="space-y-8 p-4 py-8 pb-32">
49-
<!-- External Links -->
49+
<!-- Hooks -->
5050
<ul>
51-
{#each navExternal as link}
51+
{#each navHooks as link}
5252
<li>
5353
<a
5454
href={link.href}
55-
target="_blank"
5655
class="nav-link"
57-
class:nav-active={navActive(link.href)}
56+
class:nav-active={$page.route.id === link.href}
5857
onclick={() => drawer.close()}
5958
>
6059
<svelte:component this={link.icon} size={24} />
@@ -63,14 +62,15 @@
6362
</li>
6463
{/each}
6564
</ul>
66-
<!-- Hooks -->
65+
<!-- External Links -->
6766
<ul>
68-
{#each navHooks as link}
67+
{#each navExternal as link}
6968
<li>
7069
<a
7170
href={link.href}
71+
target="_blank"
7272
class="nav-link"
73-
class:nav-active={$page.route.id === link.href}
73+
class:nav-active={navActive(link.href)}
7474
onclick={() => drawer.close()}
7575
>
7676
<svelte:component this={link.icon} size={24} />
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script lang="ts">
2+
import type { Snippet } from 'svelte';
3+
4+
interface PreviewProps {
5+
preview: Snippet;
6+
code: Snippet;
7+
}
8+
9+
let { preview, code }: PreviewProps = $props();
10+
11+
let activeTab = $state('preview');
12+
const buttonBase = 'py-2 px-4 border-b-2 border-transparent hover:bg-surface-500/5';
13+
14+
function setTab(v: string) {
15+
activeTab = v;
16+
}
17+
</script>
18+
19+
<figure class="space-y-4">
20+
<!-- Tabs -->
21+
<nav class="flex border-b border-surface-500/50">
22+
<button
23+
class={buttonBase}
24+
onclick={() => setTab('preview')}
25+
class:!border-white={activeTab === 'preview'}
26+
>
27+
Preview
28+
</button>
29+
<button
30+
class={buttonBase}
31+
onclick={() => setTab('code')}
32+
class:!border-white={activeTab === 'code'}
33+
>
34+
Code
35+
</button>
36+
</nav>
37+
<!-- Panels -->
38+
<div>
39+
{#if activeTab === 'preview' && preview}<div class="preview">{@render preview()}</div>{/if}
40+
{#if activeTab === 'code' && code}{@render code()}{/if}
41+
</div>
42+
</figure>

src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from '@floating-ui/dom';
44
export * from '$lib/hooks/useFloating/index.svelte.js';
55
export * from '$lib/hooks/useInteractions/index.svelte.js';
66
export * from '$lib/hooks/useHover/index.svelte.js';
7+
export * from '$lib/hooks/useRole/index.svelte.js';
78

89
// Components
910
export { default as FloatingArrow } from '$lib/components/FloatingArrow/FloatingArrow.svelte';

src/routes/+page.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
>
1010
<Logo />
1111
<h1 class="h1 text-black dark:text-white">Floating UI Svelte</h1>
12-
<p>A Svelte library for position floating elements and create interactions for them.</p>
12+
<p class="max-w-md">
13+
A Svelte library for position floating elements and handling interaction. Inspired by <a
14+
class="anchor"
15+
href="https://floating-ui.com/"
16+
target="_blank">Floating UI</a
17+
>
18+
</p>
1319
<div class="flex gap-4">
1420
<a href="/tooltips" class="btn-cta">
1521
<span>Get Started</span>
Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,7 @@
1-
<script lang="ts">
2-
import { arrow, useFloating, FloatingArrow, autoUpdate, offset } from '$lib/index.js';
3-
4-
let arrowRef: HTMLElement | null = $state(null);
5-
6-
const elements: { reference: HTMLElement | null; floating: HTMLElement | null } = $state({
7-
reference: null,
8-
floating: null
9-
});
10-
11-
const floating = useFloating({
12-
whileElementsMounted: autoUpdate,
13-
open: true,
14-
placement: 'bottom',
15-
elements,
16-
get middleware() {
17-
return [offset(10), arrowRef && arrow({ element: arrowRef })];
18-
}
19-
});
20-
</script>
21-
221
<div class="space-y-10">
23-
<section class="preview">
24-
<div>
25-
<!-- Reference -->
26-
<button bind:this={elements.reference} class="btn-rose-sm">Reference</button>
27-
<!-- Floating -->
28-
<div bind:this={elements.floating} style={floating.floatingStyles} class="floating">
29-
{#if floating.isPositioned}
30-
<div class="bg-surface-500 text-white p-4 rounded shadow-xl">
31-
<p>This is the floating element</p>
32-
<FloatingArrow
33-
bind:ref={arrowRef}
34-
context={floating.context}
35-
class="fill-surface-500"
36-
/>
37-
</div>
38-
{/if}
39-
</div>
40-
</div>
41-
</section>
2+
<!-- Header -->
3+
<header class="card card-gradient space-y-8">
4+
<h1 class="h1"><span>Context Menus</span></h1>
5+
<p>Coming Soon!</p>
6+
</header>
427
</div>

src/routes/modals/+page.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
<p>Page: Modals</p>
1+
<div class="space-y-10">
2+
<!-- Header -->
3+
<header class="card card-gradient space-y-8">
4+
<h1 class="h1"><span>Modals</span></h1>
5+
<p>Coming Soon!</p>
6+
</header>
7+
</div>

src/routes/popovers/+page.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
<p>Page: Popovers</p>
1+
<div class="space-y-10">
2+
<!-- Header -->
3+
<header class="card card-gradient space-y-8">
4+
<h1 class="h1"><span>Popovers</span></h1>
5+
<p>Coming Soon!</p>
6+
</header>
7+
</div>

src/routes/tooltips/+page.svelte

Lines changed: 118 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
<script>
1+
<script lang="ts">
22
import CodeBlock from '$docs/components/CodeBlock/CodeBlock.svelte';
33
import Example from './Example.svelte';
44
import exampleCode from './Example.svelte?raw';
5+
import Preview from '$docs/components/Preview/Preview.svelte';
56
</script>
67

78
<div class="space-y-10">
9+
<!-- Header -->
810
<header class="card card-gradient space-y-8">
9-
<h1 class="h1">
10-
<span>Tooltips</span>
11-
</h1>
11+
<h1 class="h1"><span>Tooltips</span></h1>
1212
<p>
13-
A tooltip is a floating element that displays information related to an anchor element when it
14-
receives keyboard focus or the mouse hovers over it.
13+
A tooltip is a floating element that displays information related to a button or anchor
14+
element when it receives keyboard focus or the mouse hovers over it.
1515
</p>
16-
<CodeBlock
17-
code={`import { useFloating } from '@skeletonlabs/floatin-ui-svelte';`}
18-
lang="typescript"
19-
/>
2016
</header>
2117
<!-- Essentials -->
2218
<section class="space-y-8">
@@ -32,8 +28,8 @@
3228
when the reference element receives keyboard focus, the tooltip opens. When the mouse
3329
leaves, or the reference is blurred, the tooltip closes.
3430
</li>
35-
<li>
36-
<span class="highlight">Dismissal</span>: When the user presses the
31+
<li class="opacity-50 line-through">
32+
COMING SOON: <span class="highlight">Dismissal</span>: When the user presses the
3733
<kbd class="kbd">esc</kbd> key while the tooltip is open, it closes.
3834
</li>
3935
<li>
@@ -42,36 +38,122 @@
4238
</li>
4339
</ul>
4440
</section>
41+
<!-- Preview -->
4542
<section class="space-y-8">
46-
<h2 class="h2">Examples</h2>
47-
<p>Both of these examples have sections explaining them in-depth below.</p>
48-
<div>
49-
<Example />
50-
</div>
43+
<h2 class="h2">Example</h2>
44+
<p>
45+
This is a functional Tooltip that uses a combination of hooks and components, each of which is
46+
described in the sections below.
47+
</p>
48+
<Preview>
49+
{#snippet preview()}<Example />{/snippet}
50+
{#snippet code()}<CodeBlock code={exampleCode} lang="html" />{/snippet}
51+
</Preview>
52+
</section>
53+
<!-- Open State -->
54+
<section class="space-y-8">
55+
<h2 class="h2">Open State</h2>
56+
<CodeBlock code={`let open = $state(false);`} lang="ts" />
57+
<p>
58+
<code class="code">open</code> determines whether or not the tooltip is currently open on the screen.
59+
It is used for conditional rendering.
60+
</p>
61+
</section>
62+
<!-- useFloating Hook -->
63+
<section class="space-y-8">
64+
<h2 class="h2">useFloating Hook</h2>
65+
<p>
66+
The <code class="code">useFloating()</code> Hook provides positioning and context for our tooltip.
67+
We need to pass it some information:
68+
</p>
69+
<CodeBlock code={`const floating = useFloating({ /* ...settings... */ });`} lang="ts" />
70+
<ul class="list-disc list-outside translate-x-8 space-y-4">
71+
<li>
72+
<code class="code">open</code>: The open state from our <code class="code">useState()</code>
73+
Hook above.
74+
</li>
75+
<li>
76+
<code class="code">onOpenChange</code>: A callback function that will be called when the
77+
tooltip is opened or closed. We’ll use this to update our <code class="code">open</code> state.
78+
</li>
79+
<li>
80+
<code class="code">middleware</code>: Import and pass middleware to the array that ensure
81+
the tooltip remains on the screen, no matter where it ends up being positioned.
82+
</li>
83+
<li>
84+
<code class="code">whileElementsMounted</code>: Ensure the tooltip remains anchored to the
85+
reference element by updating the position when necessary, only while both the reference and
86+
floating elements are mounted for performance.
87+
</li>
88+
</ul>
5189
</section>
90+
<!-- Interaction Hooks -->
5291
<section class="space-y-8">
53-
<h2 class="h2">Open state</h2>
54-
<CodeBlock code={exampleCode} lang="html" />
92+
<h2 class="h2">Interaction Hooks</h2>
5593
<p>
56-
<code class="code">isOpen</code> determines whether or not the tooltip is currently open on the
57-
screen. It is used for conditional rendering.
94+
The <code class="code">useInteractions()</code> hooks returns an object containing keys of
95+
props that enable the tooltip to be opened, closed, or accessible to screen readers. Using the
96+
<code class="code">context</code> that was returned from the Hook, call the interaction Hooks.
5897
</p>
98+
<CodeBlock
99+
code={`
100+
const hover = useHover(floating.context);
101+
const role = useRole(floating.context, { role: 'tooltip' });
102+
const interactions = useInteractions([hover, role]);
103+
`}
104+
lang="ts"
105+
/>
106+
<ul class="list-disc list-outside translate-x-8 space-y-4">
107+
<li>
108+
<code class="code">useHover()</code>: adds the ability to toggle the tooltip open or closed
109+
when the reference element is hovered over. The <code class="code">move</code> option is set
110+
to false so that
111+
<code class="code">mousemove</code> events are ignored.
112+
</li>
113+
<li class="opacity-50 line-through">
114+
COMING SOON: <code class="code">useFocus()</code>: adds the ability to toggle the tooltip
115+
open or closed when the reference element is focused.
116+
</li>
117+
<li class="opacity-50 line-through">
118+
COMING SOON: <code class="code">useDismiss()</code>: adds the ability to dismiss the tooltip
119+
when the user presses the <kbd class="kbd">esc</kbd> key.
120+
</li>
121+
<li>
122+
<code class="code">useRole()</code>: adds the correct ARIA attributes for a
123+
<code class="code">tooltip</code> to the tooltip and reference elements.
124+
</li>
125+
</ul>
59126
</section>
60-
<!--
61-
<p>
62-
First, give the floating element initial CSS styles so that it becomes an
63-
absolutely-positioned element that floats on top of the UI with layout ready for being
64-
measured:
65-
</p>
66-
<p>
67-
The <code class="code">-start</code> and <code class="code">-end</code> alignments are
68-
<a href="/" class="anchor">logical</a> and will adapt to the writing direction (e.g. RTL) as expected.
69-
</p>
70-
<div class="alert">
71-
<h3 class="h3">Note</h3>
127+
<!-- Rendering -->
128+
<section class="space-y-8">
129+
<h2 class="h2">Rendering</h2>
130+
<p>Now we have all the variables and Hooks set up, we can render out our elements.</p>
131+
<CodeBlock
132+
lang="html"
133+
code={`
134+
<!-- Reference Element -->
135+
<button bind:this={elemReference} {...interactions.getReferenceProps()}>Hover Me</button>\n
136+
<!-- Floating Element -->
137+
<div
138+
bind:this={elemFloating}
139+
style={floating.floatingStyles}
140+
{...interactions.getFloatingProps()}
141+
class="floating"
142+
>
143+
{#if open}
144+
<div>
145+
<p>This is the floating element</p>
146+
<FloatingArrow bind:ref={elemArrow} context={floating.context} />
147+
</div>
148+
{/if}
149+
</div>
150+
`}
151+
/>
72152
<p>
73-
You aren’t limited to just these 12 placements though. <code class="code">offset</code> allows
74-
you to create any placement.
153+
<code class="code">{`{...getReferenceProps()}`}</code> and
154+
<code class="code">{`{...getFloatingProps()}`}</code> spreads the props from the interaction
155+
Hooks onto the relevant elements. They contain props like
156+
<code class="code">onMouseEnter</code>, <code class="code">aria-describedby</code>, etc.
75157
</p>
76-
</div> -->
158+
</section>
77159
</div>

0 commit comments

Comments
 (0)