@ssaprt/tooltip 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENCE +5 -0
- package/README.md +1071 -0
- package/dist/index.css +344 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +117 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +2364 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2355 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,1071 @@
|
|
|
1
|
+
Tooltip by @ssaprt
|
|
2
|
+
|
|
3
|
+
A flexible, themeable, and interactive tooltip library for React and Next.js.
|
|
4
|
+
|
|
5
|
+
Web version: <a href="https://utility-lub.vercel.app/react/UI-Components/tooltip/">Scroll to Future</a>
|
|
6
|
+
|
|
7
|
+
tooltip supports simple text hints, rich React content, interactive controls, media, custom themes, built-in animations, automatic viewport collision handling, touch interaction, and optional global presets through TooltipProvider.
|
|
8
|
+
|
|
9
|
+
The provider is optional. Every Tooltip can work independently.
|
|
10
|
+
|
|
11
|
+
Features
|
|
12
|
+
|
|
13
|
+
Standalone tooltips without a provider
|
|
14
|
+
|
|
15
|
+
Optional global TooltipProvider
|
|
16
|
+
|
|
17
|
+
More than 50 built-in theme presets
|
|
18
|
+
|
|
19
|
+
Per-tooltip theme overrides
|
|
20
|
+
|
|
21
|
+
Fully custom themes
|
|
22
|
+
|
|
23
|
+
Four preferred placements: top, bottom, left, and right
|
|
24
|
+
|
|
25
|
+
Automatic opposite-side fallback
|
|
26
|
+
|
|
27
|
+
Automatic viewport clamping
|
|
28
|
+
|
|
29
|
+
Arrow position correction near rounded corners
|
|
30
|
+
|
|
31
|
+
Unified tooltip body and arrow surface
|
|
32
|
+
|
|
33
|
+
Solid colors, gradients, borders, textures, filters, and shadows
|
|
34
|
+
|
|
35
|
+
Separate show and hide animations
|
|
36
|
+
|
|
37
|
+
Configurable animation speed and easing
|
|
38
|
+
|
|
39
|
+
Interactive tooltip content
|
|
40
|
+
|
|
41
|
+
Configurable hide delay
|
|
42
|
+
|
|
43
|
+
Text, JSX, components, forms, buttons, links, images, video, and other media
|
|
44
|
+
|
|
45
|
+
Automatic repositioning while scrolling and resizing
|
|
46
|
+
|
|
47
|
+
Automatic updates when tooltip content changes size
|
|
48
|
+
|
|
49
|
+
Mouse, touch, focus, and keyboard handling
|
|
50
|
+
|
|
51
|
+
React portal rendering into document.body
|
|
52
|
+
|
|
53
|
+
TypeScript support
|
|
54
|
+
|
|
55
|
+
React and Next.js support
|
|
56
|
+
|
|
57
|
+
Adapted for modern Chrome, Safari, Firefox, Edge, iOS Safari, and Chromium-based mobile browsers
|
|
58
|
+
|
|
59
|
+
Installation
|
|
60
|
+
|
|
61
|
+
npm install @ssaprt/tooltip
|
|
62
|
+
|
|
63
|
+
yarn add @ssaprt/tooltip
|
|
64
|
+
|
|
65
|
+
pnpm add @ssaprt/tooltip
|
|
66
|
+
|
|
67
|
+
Import the stylesheet once in the application entry point:
|
|
68
|
+
|
|
69
|
+
import "@ssaprt/tooltip/style.css";
|
|
70
|
+
|
|
71
|
+
Basic usage
|
|
72
|
+
|
|
73
|
+
TooltipProvider is not required.
|
|
74
|
+
|
|
75
|
+
"use client";
|
|
76
|
+
|
|
77
|
+
import { Tooltip } from "@ssaprt/tooltip";
|
|
78
|
+
import "@ssaprt/tooltip/style.css";
|
|
79
|
+
|
|
80
|
+
export const Example = () => {
|
|
81
|
+
return (
|
|
82
|
+
<Tooltip content="Copy value">
|
|
83
|
+
<button type="button">Copy</button>
|
|
84
|
+
</Tooltip>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
The child element becomes the tooltip anchor.
|
|
89
|
+
|
|
90
|
+
Local configuration
|
|
91
|
+
|
|
92
|
+
Every tooltip can define its own position, theme, animation, delay, and behavior.
|
|
93
|
+
|
|
94
|
+
<Tooltip
|
|
95
|
+
content="Saved successfully"
|
|
96
|
+
position="bottom"
|
|
97
|
+
selectTheme="dark"
|
|
98
|
+
animation={{
|
|
99
|
+
show: "bounce",
|
|
100
|
+
hide: "fade",
|
|
101
|
+
speed: "180ms",
|
|
102
|
+
easing: "ease-out",
|
|
103
|
+
}}
|
|
104
|
+
hideDelay={200}
|
|
105
|
+
>
|
|
106
|
+
<button type="button">Save</button>
|
|
107
|
+
</Tooltip>
|
|
108
|
+
|
|
109
|
+
Tooltip inside an element
|
|
110
|
+
|
|
111
|
+
A tooltip can also be placed directly inside its target element.
|
|
112
|
+
|
|
113
|
+
<button type="button">
|
|
114
|
+
Delete
|
|
115
|
+
|
|
116
|
+
<Tooltip
|
|
117
|
+
content="Delete this item"
|
|
118
|
+
position="top"
|
|
119
|
+
selectTheme="red"
|
|
120
|
+
/>
|
|
121
|
+
</button>
|
|
122
|
+
|
|
123
|
+
When children is not passed to Tooltip, the immediate parent element becomes the anchor.
|
|
124
|
+
|
|
125
|
+
Optional global provider
|
|
126
|
+
|
|
127
|
+
Use TooltipProvider when many tooltips should share the same initial position, preset theme, custom theme overrides, or animations.
|
|
128
|
+
|
|
129
|
+
"use client";
|
|
130
|
+
|
|
131
|
+
import { TooltipProvider } from "@ssaprt/tooltip";
|
|
132
|
+
import "@ssaprt/tooltip/style.css";
|
|
133
|
+
|
|
134
|
+
export const AppProvider = ({
|
|
135
|
+
children,
|
|
136
|
+
}: {
|
|
137
|
+
children: React.ReactNode;
|
|
138
|
+
}) => {
|
|
139
|
+
return (
|
|
140
|
+
<TooltipProvider
|
|
141
|
+
defaultRenderPosition="top"
|
|
142
|
+
selectTheme="glass"
|
|
143
|
+
animation={{
|
|
144
|
+
show: "slide",
|
|
145
|
+
hide: "fade",
|
|
146
|
+
speed: "140ms",
|
|
147
|
+
easing: "ease-in-out",
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
{children}
|
|
151
|
+
</TooltipProvider>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
All descendant tooltips inherit these values unless they override them locally.
|
|
156
|
+
|
|
157
|
+
<Tooltip content="Uses provider defaults">
|
|
158
|
+
<button type="button">Default tooltip</button>
|
|
159
|
+
</Tooltip>
|
|
160
|
+
|
|
161
|
+
<Tooltip
|
|
162
|
+
content="Uses local settings"
|
|
163
|
+
position="right"
|
|
164
|
+
selectTheme="comic"
|
|
165
|
+
animation={{
|
|
166
|
+
show: "zoom",
|
|
167
|
+
hide: "scale",
|
|
168
|
+
speed: "220ms",
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<button type="button">Local tooltip</button>
|
|
172
|
+
</Tooltip>
|
|
173
|
+
|
|
174
|
+
Configuration priority
|
|
175
|
+
|
|
176
|
+
Local tooltip values have priority over provider defaults.
|
|
177
|
+
|
|
178
|
+
The effective configuration is resolved in this order:
|
|
179
|
+
|
|
180
|
+
Props passed directly to Tooltip
|
|
181
|
+
|
|
182
|
+
Local customTheme
|
|
183
|
+
|
|
184
|
+
Values from TooltipProvider
|
|
185
|
+
|
|
186
|
+
Animation settings from the selected preset theme
|
|
187
|
+
|
|
188
|
+
Built-in defaults
|
|
189
|
+
|
|
190
|
+
The default built-in values are:
|
|
191
|
+
|
|
192
|
+
const defaults = {
|
|
193
|
+
position: "top",
|
|
194
|
+
selectTheme: "primary",
|
|
195
|
+
showAnimation: "slide",
|
|
196
|
+
hideAnimation: "fade",
|
|
197
|
+
animationSpeed: "120ms",
|
|
198
|
+
animationEasing: "ease-in-out",
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
Text content
|
|
202
|
+
|
|
203
|
+
<Tooltip content="Simple text tooltip">
|
|
204
|
+
<span>Hover me</span>
|
|
205
|
+
</Tooltip>
|
|
206
|
+
|
|
207
|
+
The content prop accepts any valid ReactNode.
|
|
208
|
+
|
|
209
|
+
React content
|
|
210
|
+
|
|
211
|
+
<Tooltip
|
|
212
|
+
content={
|
|
213
|
+
<div>
|
|
214
|
+
<strong>Build completed</strong>
|
|
215
|
+
<span>All files were generated successfully.</span>
|
|
216
|
+
</div>
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
<button type="button">Build status</button>
|
|
220
|
+
</Tooltip>
|
|
221
|
+
|
|
222
|
+
Interactive content
|
|
223
|
+
|
|
224
|
+
Set interactive when the user must be able to move the pointer into the tooltip and interact with its content.
|
|
225
|
+
|
|
226
|
+
<Tooltip
|
|
227
|
+
interactive
|
|
228
|
+
hideDelay={300}
|
|
229
|
+
content={
|
|
230
|
+
<div>
|
|
231
|
+
<button type="button">Previous</button>
|
|
232
|
+
<input type="text" placeholder="Search" />
|
|
233
|
+
<button type="button">Next</button>
|
|
234
|
+
</div>
|
|
235
|
+
}
|
|
236
|
+
>
|
|
237
|
+
<button type="button">Open controls</button>
|
|
238
|
+
</Tooltip>
|
|
239
|
+
|
|
240
|
+
With interactive enabled:
|
|
241
|
+
|
|
242
|
+
The tooltip remains visible while the pointer is inside it
|
|
243
|
+
|
|
244
|
+
Buttons, links, inputs, selects, and other controls remain usable
|
|
245
|
+
|
|
246
|
+
Moving from the anchor to the tooltip does not close it
|
|
247
|
+
|
|
248
|
+
Moving away from both the anchor and tooltip starts the hide delay
|
|
249
|
+
|
|
250
|
+
Clicking outside closes the tooltip
|
|
251
|
+
|
|
252
|
+
Pressing Escape closes the tooltip
|
|
253
|
+
|
|
254
|
+
Focus can move between the anchor and tooltip content
|
|
255
|
+
|
|
256
|
+
The default hide delay is:
|
|
257
|
+
|
|
258
|
+
120
|
|
259
|
+
|
|
260
|
+
For interactive tooltips, the default is:
|
|
261
|
+
|
|
262
|
+
240
|
|
263
|
+
|
|
264
|
+
A custom hideDelay overrides both values.
|
|
265
|
+
|
|
266
|
+
Rich components and media
|
|
267
|
+
|
|
268
|
+
Tooltip content is not limited to a short string. It can contain complete React interfaces, including cards, media previews, forms, navigation, images, audio, and video.
|
|
269
|
+
|
|
270
|
+
<Tooltip
|
|
271
|
+
interactive
|
|
272
|
+
position="right"
|
|
273
|
+
selectTheme="glass"
|
|
274
|
+
content={
|
|
275
|
+
<article
|
|
276
|
+
style={{
|
|
277
|
+
display: "grid",
|
|
278
|
+
width: "320px",
|
|
279
|
+
gap: "12px",
|
|
280
|
+
textAlign: "left",
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
<video
|
|
284
|
+
controls
|
|
285
|
+
poster="/media/preview.jpg"
|
|
286
|
+
style={{
|
|
287
|
+
display: "block",
|
|
288
|
+
width: "100%",
|
|
289
|
+
borderRadius: "12px",
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
<source
|
|
293
|
+
src="/media/preview.mp4"
|
|
294
|
+
type="video/mp4"
|
|
295
|
+
/>
|
|
296
|
+
</video>
|
|
297
|
+
|
|
298
|
+
<div>
|
|
299
|
+
<strong>Media preview</strong>
|
|
300
|
+
<p>Interactive content remains available inside the tooltip.</p>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div
|
|
304
|
+
style={{
|
|
305
|
+
display: "flex",
|
|
306
|
+
gap: "8px",
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
<button type="button">Open</button>
|
|
310
|
+
<button type="button">Save</button>
|
|
311
|
+
</div>
|
|
312
|
+
</article>
|
|
313
|
+
}
|
|
314
|
+
>
|
|
315
|
+
<button type="button">Show preview</button>
|
|
316
|
+
</Tooltip>
|
|
317
|
+
|
|
318
|
+
The tooltip uses ResizeObserver, so its position is recalculated when dynamic content changes size.
|
|
319
|
+
|
|
320
|
+
Trigger elements
|
|
321
|
+
|
|
322
|
+
Wrapping a DOM element
|
|
323
|
+
|
|
324
|
+
<Tooltip content="Open settings">
|
|
325
|
+
<button type="button">Settings</button>
|
|
326
|
+
</Tooltip>
|
|
327
|
+
|
|
328
|
+
Wrapping a custom React component
|
|
329
|
+
|
|
330
|
+
The wrapped component must forward its ref to a DOM element.
|
|
331
|
+
|
|
332
|
+
import { forwardRef } from "react";
|
|
333
|
+
|
|
334
|
+
const ActionButton = forwardRef<
|
|
335
|
+
HTMLButtonElement,
|
|
336
|
+
React.ComponentProps<"button">
|
|
337
|
+
>((props, ref) => {
|
|
338
|
+
return <button ref={ref} {...props} />;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
ActionButton.displayName = "ActionButton";
|
|
342
|
+
|
|
343
|
+
export const Example = () => {
|
|
344
|
+
return (
|
|
345
|
+
<Tooltip content="Custom component tooltip">
|
|
346
|
+
<ActionButton type="button">Action</ActionButton>
|
|
347
|
+
</Tooltip>
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
Using a tooltip inside a custom component
|
|
352
|
+
|
|
353
|
+
When forwarding a ref is not convenient, place Tooltip inside the final DOM element.
|
|
354
|
+
|
|
355
|
+
export const ActionButton = () => {
|
|
356
|
+
return (
|
|
357
|
+
<button type="button">
|
|
358
|
+
Action
|
|
359
|
+
<Tooltip content="Custom component tooltip" />
|
|
360
|
+
</button>
|
|
361
|
+
);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
Placement
|
|
365
|
+
|
|
366
|
+
Available placements:
|
|
367
|
+
|
|
368
|
+
type TooltipPlacement =
|
|
369
|
+
| "top"
|
|
370
|
+
| "bottom"
|
|
371
|
+
| "left"
|
|
372
|
+
| "right";
|
|
373
|
+
|
|
374
|
+
Example:
|
|
375
|
+
|
|
376
|
+
<Tooltip content="Rendered on the left" position="left">
|
|
377
|
+
<button type="button">Left</button>
|
|
378
|
+
</Tooltip>
|
|
379
|
+
|
|
380
|
+
The requested placement is preferred, not forced.
|
|
381
|
+
|
|
382
|
+
When there is not enough space, the tooltip checks the opposite side:
|
|
383
|
+
|
|
384
|
+
top can fall back to bottom
|
|
385
|
+
|
|
386
|
+
bottom can fall back to top
|
|
387
|
+
|
|
388
|
+
left can fall back to right
|
|
389
|
+
|
|
390
|
+
right can fall back to left
|
|
391
|
+
|
|
392
|
+
The final position is clamped to the viewport so the tooltip does not render outside the visible area.
|
|
393
|
+
|
|
394
|
+
The arrow is repositioned automatically and constrained to the straight section of the edge so it does not overlap rounded corners.
|
|
395
|
+
|
|
396
|
+
Built-in themes
|
|
397
|
+
|
|
398
|
+
The package includes more than 50 ready-to-use presets with different visual styles, including clean, dark, comic, terminal, glass, neon, retro, cyberpunk, paper, metallic, natural, warning, and decorative themes.
|
|
399
|
+
|
|
400
|
+
Examples include:
|
|
401
|
+
|
|
402
|
+
"primary"
|
|
403
|
+
"secondary"
|
|
404
|
+
"dark"
|
|
405
|
+
"light"
|
|
406
|
+
"comic"
|
|
407
|
+
"manga"
|
|
408
|
+
"newspaper"
|
|
409
|
+
"stickyNote"
|
|
410
|
+
"blueprint"
|
|
411
|
+
"terminal"
|
|
412
|
+
"crt"
|
|
413
|
+
"pixel"
|
|
414
|
+
"arcade"
|
|
415
|
+
"cyberpunk"
|
|
416
|
+
"synthwave"
|
|
417
|
+
"vaporwave"
|
|
418
|
+
"hologram"
|
|
419
|
+
"glass"
|
|
420
|
+
"frost"
|
|
421
|
+
"clay"
|
|
422
|
+
"bubblegum"
|
|
423
|
+
"candy"
|
|
424
|
+
"watermelon"
|
|
425
|
+
"lemon"
|
|
426
|
+
"lava"
|
|
427
|
+
"ember"
|
|
428
|
+
"toxic"
|
|
429
|
+
"radioactive"
|
|
430
|
+
"hazard"
|
|
431
|
+
"policeTape"
|
|
432
|
+
"construction"
|
|
433
|
+
"parchment"
|
|
434
|
+
"pirateMap"
|
|
435
|
+
"royal"
|
|
436
|
+
"noir"
|
|
437
|
+
"detective"
|
|
438
|
+
"dossier"
|
|
439
|
+
"medical"
|
|
440
|
+
"laboratory"
|
|
441
|
+
"circuit"
|
|
442
|
+
"galaxy"
|
|
443
|
+
"aurora"
|
|
444
|
+
"oceanDepths"
|
|
445
|
+
"coralReef"
|
|
446
|
+
"forest"
|
|
447
|
+
"moss"
|
|
448
|
+
"desert"
|
|
449
|
+
"snow"
|
|
450
|
+
"chrome"
|
|
451
|
+
"goldFoil"
|
|
452
|
+
"bronze"
|
|
453
|
+
"brutalist"
|
|
454
|
+
"chalkboard"
|
|
455
|
+
|
|
456
|
+
Select a preset globally:
|
|
457
|
+
|
|
458
|
+
<TooltipProvider selectTheme="cyberpunk">
|
|
459
|
+
<App />
|
|
460
|
+
</TooltipProvider>
|
|
461
|
+
|
|
462
|
+
Or locally:
|
|
463
|
+
|
|
464
|
+
<Tooltip content="Local theme" selectTheme="terminal">
|
|
465
|
+
<button type="button">Terminal</button>
|
|
466
|
+
</Tooltip>
|
|
467
|
+
|
|
468
|
+
PresetsThemeType is derived from the actual preset map, so TypeScript only accepts available theme names.
|
|
469
|
+
|
|
470
|
+
Custom themes
|
|
471
|
+
|
|
472
|
+
A custom theme can be applied globally through TooltipProvider or locally through Tooltip.
|
|
473
|
+
|
|
474
|
+
<Tooltip
|
|
475
|
+
content="Custom theme"
|
|
476
|
+
customTheme={{
|
|
477
|
+
body: {
|
|
478
|
+
background:
|
|
479
|
+
"linear-gradient(135deg, #7c3aed, #db2777)",
|
|
480
|
+
filter:
|
|
481
|
+
"drop-shadow(0 12px 24px rgba(124, 58, 237, 0.4))",
|
|
482
|
+
style: {
|
|
483
|
+
color: "#ffffff",
|
|
484
|
+
padding: "12px 16px",
|
|
485
|
+
border: "2px solid #f0abfc",
|
|
486
|
+
borderRadius: "18px 6px",
|
|
487
|
+
fontSize: "14px",
|
|
488
|
+
fontWeight: 700,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
arrow: {
|
|
492
|
+
size: "10px",
|
|
493
|
+
width: "24px",
|
|
494
|
+
},
|
|
495
|
+
animation: {
|
|
496
|
+
show: "bounce",
|
|
497
|
+
hide: "scale",
|
|
498
|
+
speed: "220ms",
|
|
499
|
+
easing: "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
500
|
+
},
|
|
501
|
+
}}
|
|
502
|
+
>
|
|
503
|
+
<button type="button">Custom</button>
|
|
504
|
+
</Tooltip>
|
|
505
|
+
|
|
506
|
+
Custom theme values are merged with the selected preset. Only the values that must change need to be supplied.
|
|
507
|
+
|
|
508
|
+
Theme type
|
|
509
|
+
|
|
510
|
+
import type { CSSProperties } from "react";
|
|
511
|
+
|
|
512
|
+
type TooltipSize =
|
|
513
|
+
| `${number}px`
|
|
514
|
+
| `${number}rem`
|
|
515
|
+
| `${number}em`
|
|
516
|
+
| `calc(${string})`;
|
|
517
|
+
|
|
518
|
+
type ThemeType = {
|
|
519
|
+
body?: {
|
|
520
|
+
background?: CSSProperties["background"];
|
|
521
|
+
filter?: CSSProperties["filter"];
|
|
522
|
+
style?: CSSProperties;
|
|
523
|
+
className?: string;
|
|
524
|
+
};
|
|
525
|
+
arrow?: {
|
|
526
|
+
size?: TooltipSize;
|
|
527
|
+
width?: TooltipSize;
|
|
528
|
+
};
|
|
529
|
+
animation?: TooltipAnimationOptions;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
body.background
|
|
533
|
+
|
|
534
|
+
Controls the complete tooltip surface background, including the arrow.
|
|
535
|
+
|
|
536
|
+
Supported values include:
|
|
537
|
+
|
|
538
|
+
Solid colors
|
|
539
|
+
|
|
540
|
+
Linear gradients
|
|
541
|
+
|
|
542
|
+
Radial gradients
|
|
543
|
+
|
|
544
|
+
Conic gradients
|
|
545
|
+
|
|
546
|
+
Repeating gradients
|
|
547
|
+
|
|
548
|
+
CSS variables
|
|
549
|
+
|
|
550
|
+
Other valid CSS background values
|
|
551
|
+
|
|
552
|
+
<Tooltip
|
|
553
|
+
content="Gradient"
|
|
554
|
+
customTheme={{
|
|
555
|
+
body: {
|
|
556
|
+
background:
|
|
557
|
+
"linear-gradient(135deg, #22d3ee, #7c3aed)",
|
|
558
|
+
},
|
|
559
|
+
}}
|
|
560
|
+
>
|
|
561
|
+
<button type="button">Gradient</button>
|
|
562
|
+
</Tooltip>
|
|
563
|
+
|
|
564
|
+
body.style
|
|
565
|
+
|
|
566
|
+
Accepts regular React CSSProperties for the tooltip content and surface configuration.
|
|
567
|
+
|
|
568
|
+
Common options include:
|
|
569
|
+
|
|
570
|
+
{
|
|
571
|
+
color: "#ffffff",
|
|
572
|
+
padding: "10px 14px",
|
|
573
|
+
border: "2px solid #ffffff",
|
|
574
|
+
borderRadius: "14px",
|
|
575
|
+
fontSize: "13px",
|
|
576
|
+
fontWeight: 600,
|
|
577
|
+
lineHeight: 1.4,
|
|
578
|
+
textAlign: "left",
|
|
579
|
+
maxWidth: "320px",
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
Borders and corner radii are used to generate the unified body-and-arrow surface.
|
|
583
|
+
|
|
584
|
+
body.filter
|
|
585
|
+
|
|
586
|
+
Applies a filter to the complete tooltip surface, including the arrow.
|
|
587
|
+
|
|
588
|
+
<Tooltip
|
|
589
|
+
content="Shadow"
|
|
590
|
+
customTheme={{
|
|
591
|
+
body: {
|
|
592
|
+
filter:
|
|
593
|
+
"drop-shadow(0 12px 18px rgba(0, 0, 0, 0.35))",
|
|
594
|
+
},
|
|
595
|
+
}}
|
|
596
|
+
>
|
|
597
|
+
<button type="button">Shadow</button>
|
|
598
|
+
</Tooltip>
|
|
599
|
+
|
|
600
|
+
body.className
|
|
601
|
+
|
|
602
|
+
Adds a class name to the tooltip content container.
|
|
603
|
+
|
|
604
|
+
<Tooltip
|
|
605
|
+
content="Class name"
|
|
606
|
+
customTheme={{
|
|
607
|
+
body: {
|
|
608
|
+
className: "my-tooltip-content",
|
|
609
|
+
},
|
|
610
|
+
}}
|
|
611
|
+
>
|
|
612
|
+
<button type="button">Styled</button>
|
|
613
|
+
</Tooltip>
|
|
614
|
+
|
|
615
|
+
arrow.size
|
|
616
|
+
|
|
617
|
+
Controls how far the arrow extends from the tooltip body.
|
|
618
|
+
|
|
619
|
+
arrow: {
|
|
620
|
+
size: "10px",
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
arrow.width
|
|
624
|
+
|
|
625
|
+
Controls the width of the arrow base.
|
|
626
|
+
|
|
627
|
+
arrow: {
|
|
628
|
+
width: "24px",
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
Animations
|
|
632
|
+
|
|
633
|
+
Show and hide animations can be configured independently.
|
|
634
|
+
|
|
635
|
+
<Tooltip
|
|
636
|
+
content="Animated tooltip"
|
|
637
|
+
animation={{
|
|
638
|
+
show: "flip",
|
|
639
|
+
hide: "blur",
|
|
640
|
+
speed: "200ms",
|
|
641
|
+
easing: "ease-out",
|
|
642
|
+
}}
|
|
643
|
+
>
|
|
644
|
+
<button type="button">Animate</button>
|
|
645
|
+
</Tooltip>
|
|
646
|
+
|
|
647
|
+
Available animation types:
|
|
648
|
+
|
|
649
|
+
type TooltipAnimationType =
|
|
650
|
+
| "fade"
|
|
651
|
+
| "slide"
|
|
652
|
+
| "scale"
|
|
653
|
+
| "zoom"
|
|
654
|
+
| "blur"
|
|
655
|
+
| "flip"
|
|
656
|
+
| "bounce"
|
|
657
|
+
| "none";
|
|
658
|
+
|
|
659
|
+
Animation options:
|
|
660
|
+
|
|
661
|
+
type TooltipAnimationSpeed =
|
|
662
|
+
| `${number}ms`
|
|
663
|
+
| `${number}s`;
|
|
664
|
+
|
|
665
|
+
type TooltipAnimationOptions = {
|
|
666
|
+
show?: TooltipAnimationType;
|
|
667
|
+
hide?: TooltipAnimationType;
|
|
668
|
+
speed?: TooltipAnimationSpeed;
|
|
669
|
+
easing?: CSSProperties["animationTimingFunction"];
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
Disable animations:
|
|
673
|
+
|
|
674
|
+
<Tooltip
|
|
675
|
+
content="No animation"
|
|
676
|
+
animation={{
|
|
677
|
+
show: "none",
|
|
678
|
+
hide: "none",
|
|
679
|
+
speed: "1ms",
|
|
680
|
+
easing: "linear",
|
|
681
|
+
}}
|
|
682
|
+
>
|
|
683
|
+
<button type="button">Static</button>
|
|
684
|
+
</Tooltip>
|
|
685
|
+
|
|
686
|
+
The stylesheet also respects prefers-reduced-motion and reduces animation duration when the user requests reduced motion.
|
|
687
|
+
|
|
688
|
+
TooltipProvider API
|
|
689
|
+
|
|
690
|
+
TooltipProvider is optional and only supplies global defaults to descendant tooltips.
|
|
691
|
+
|
|
692
|
+
Property
|
|
693
|
+
|
|
694
|
+
Type
|
|
695
|
+
|
|
696
|
+
Default
|
|
697
|
+
|
|
698
|
+
Description
|
|
699
|
+
|
|
700
|
+
children
|
|
701
|
+
|
|
702
|
+
ReactNode
|
|
703
|
+
|
|
704
|
+
Required
|
|
705
|
+
|
|
706
|
+
Components that can consume the provider defaults
|
|
707
|
+
|
|
708
|
+
defaultRenderPosition
|
|
709
|
+
|
|
710
|
+
"top" | "bottom" | "left" | "right"
|
|
711
|
+
|
|
712
|
+
"top"
|
|
713
|
+
|
|
714
|
+
Default preferred placement
|
|
715
|
+
|
|
716
|
+
selectTheme
|
|
717
|
+
|
|
718
|
+
PresetsThemeType
|
|
719
|
+
|
|
720
|
+
"primary"
|
|
721
|
+
|
|
722
|
+
Default built-in theme preset
|
|
723
|
+
|
|
724
|
+
customTheme
|
|
725
|
+
|
|
726
|
+
ThemeType
|
|
727
|
+
|
|
728
|
+
undefined
|
|
729
|
+
|
|
730
|
+
Global theme overrides merged with the selected preset
|
|
731
|
+
|
|
732
|
+
animation
|
|
733
|
+
|
|
734
|
+
TooltipAnimationOptions
|
|
735
|
+
|
|
736
|
+
Theme settings
|
|
737
|
+
|
|
738
|
+
Global show and hide animation overrides
|
|
739
|
+
|
|
740
|
+
Complete provider example:
|
|
741
|
+
|
|
742
|
+
<TooltipProvider
|
|
743
|
+
defaultRenderPosition="bottom"
|
|
744
|
+
selectTheme="glass"
|
|
745
|
+
customTheme={{
|
|
746
|
+
body: {
|
|
747
|
+
style: {
|
|
748
|
+
color: "#ffffff",
|
|
749
|
+
padding: "10px 14px",
|
|
750
|
+
borderRadius: "14px",
|
|
751
|
+
maxWidth: "360px",
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
arrow: {
|
|
755
|
+
size: "8px",
|
|
756
|
+
width: "18px",
|
|
757
|
+
},
|
|
758
|
+
}}
|
|
759
|
+
animation={{
|
|
760
|
+
show: "blur",
|
|
761
|
+
hide: "fade",
|
|
762
|
+
speed: "180ms",
|
|
763
|
+
easing: "ease-in-out",
|
|
764
|
+
}}
|
|
765
|
+
>
|
|
766
|
+
<App />
|
|
767
|
+
</TooltipProvider>
|
|
768
|
+
|
|
769
|
+
Tooltip API
|
|
770
|
+
|
|
771
|
+
Property
|
|
772
|
+
|
|
773
|
+
Type
|
|
774
|
+
|
|
775
|
+
Default
|
|
776
|
+
|
|
777
|
+
Description
|
|
778
|
+
|
|
779
|
+
content
|
|
780
|
+
|
|
781
|
+
ReactNode
|
|
782
|
+
|
|
783
|
+
Required
|
|
784
|
+
|
|
785
|
+
Text, JSX, components, controls, media, or any other React content
|
|
786
|
+
|
|
787
|
+
children
|
|
788
|
+
|
|
789
|
+
ReactElement
|
|
790
|
+
|
|
791
|
+
Optional
|
|
792
|
+
|
|
793
|
+
Element used as the tooltip anchor; without it, the immediate parent is used
|
|
794
|
+
|
|
795
|
+
position
|
|
796
|
+
|
|
797
|
+
TooltipPlacement
|
|
798
|
+
|
|
799
|
+
Provider value or "top"
|
|
800
|
+
|
|
801
|
+
Preferred tooltip placement
|
|
802
|
+
|
|
803
|
+
selectTheme
|
|
804
|
+
|
|
805
|
+
PresetsThemeType
|
|
806
|
+
|
|
807
|
+
Provider value or "primary"
|
|
808
|
+
|
|
809
|
+
Local built-in theme preset
|
|
810
|
+
|
|
811
|
+
customTheme
|
|
812
|
+
|
|
813
|
+
ThemeType
|
|
814
|
+
|
|
815
|
+
undefined
|
|
816
|
+
|
|
817
|
+
Local theme overrides
|
|
818
|
+
|
|
819
|
+
animation
|
|
820
|
+
|
|
821
|
+
TooltipAnimationOptions
|
|
822
|
+
|
|
823
|
+
Provider or theme settings
|
|
824
|
+
|
|
825
|
+
Local show and hide animation settings
|
|
826
|
+
|
|
827
|
+
disabled
|
|
828
|
+
|
|
829
|
+
boolean
|
|
830
|
+
|
|
831
|
+
false
|
|
832
|
+
|
|
833
|
+
Prevents the tooltip from opening
|
|
834
|
+
|
|
835
|
+
interactive
|
|
836
|
+
|
|
837
|
+
boolean
|
|
838
|
+
|
|
839
|
+
false
|
|
840
|
+
|
|
841
|
+
Enables pointer and focus interaction inside the tooltip
|
|
842
|
+
|
|
843
|
+
hideDelay
|
|
844
|
+
|
|
845
|
+
number
|
|
846
|
+
|
|
847
|
+
120, or 240 when interactive
|
|
848
|
+
|
|
849
|
+
Delay in milliseconds before the tooltip begins hiding
|
|
850
|
+
|
|
851
|
+
Complete tooltip example:
|
|
852
|
+
|
|
853
|
+
<Tooltip
|
|
854
|
+
content={
|
|
855
|
+
<div>
|
|
856
|
+
<strong>Account</strong>
|
|
857
|
+
<button type="button">Open profile</button>
|
|
858
|
+
</div>
|
|
859
|
+
}
|
|
860
|
+
position="right"
|
|
861
|
+
selectTheme="terminal"
|
|
862
|
+
customTheme={{
|
|
863
|
+
body: {
|
|
864
|
+
style: {
|
|
865
|
+
minWidth: "220px",
|
|
866
|
+
textAlign: "left",
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
}}
|
|
870
|
+
animation={{
|
|
871
|
+
show: "zoom",
|
|
872
|
+
hide: "fade",
|
|
873
|
+
speed: "160ms",
|
|
874
|
+
}}
|
|
875
|
+
interactive
|
|
876
|
+
hideDelay={300}
|
|
877
|
+
>
|
|
878
|
+
<button type="button">Account</button>
|
|
879
|
+
</Tooltip>
|
|
880
|
+
|
|
881
|
+
Public interfaces
|
|
882
|
+
|
|
883
|
+
import type { ReactElement, ReactNode } from "react";
|
|
884
|
+
|
|
885
|
+
export type TooltipPlacement =
|
|
886
|
+
| "top"
|
|
887
|
+
| "bottom"
|
|
888
|
+
| "left"
|
|
889
|
+
| "right";
|
|
890
|
+
|
|
891
|
+
export interface TooltipProviderInterface {
|
|
892
|
+
defaultRenderPosition?: TooltipPlacement;
|
|
893
|
+
selectTheme?: PresetsThemeType;
|
|
894
|
+
customTheme?: ThemeType;
|
|
895
|
+
animation?: TooltipAnimationOptions;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
export interface TooltipInterface {
|
|
899
|
+
content: ReactNode;
|
|
900
|
+
children?: ReactElement;
|
|
901
|
+
position?: TooltipPlacement;
|
|
902
|
+
selectTheme?: PresetsThemeType;
|
|
903
|
+
customTheme?: ThemeType;
|
|
904
|
+
animation?: TooltipAnimationOptions;
|
|
905
|
+
disabled?: boolean;
|
|
906
|
+
interactive?: boolean;
|
|
907
|
+
hideDelay?: number;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
TypeScript exports
|
|
911
|
+
|
|
912
|
+
import {
|
|
913
|
+
Tooltip,
|
|
914
|
+
TooltipProvider,
|
|
915
|
+
} from "@ssaprt/tooltip";
|
|
916
|
+
|
|
917
|
+
import type {
|
|
918
|
+
PresetsThemeType,
|
|
919
|
+
ThemeType,
|
|
920
|
+
TooltipAnimationOptions,
|
|
921
|
+
TooltipAnimationSpeed,
|
|
922
|
+
TooltipAnimationType,
|
|
923
|
+
TooltipInterface,
|
|
924
|
+
TooltipPlacement,
|
|
925
|
+
TooltipProviderInterface,
|
|
926
|
+
TooltipSize,
|
|
927
|
+
} from "@ssaprt/tooltip";
|
|
928
|
+
|
|
929
|
+
Browser support
|
|
930
|
+
|
|
931
|
+
The package is designed for consistent behavior in modern desktop and mobile browsers:
|
|
932
|
+
|
|
933
|
+
Google Chrome
|
|
934
|
+
|
|
935
|
+
Apple Safari
|
|
936
|
+
|
|
937
|
+
Mozilla Firefox
|
|
938
|
+
|
|
939
|
+
Microsoft Edge
|
|
940
|
+
|
|
941
|
+
iOS Safari
|
|
942
|
+
|
|
943
|
+
Chromium-based Android browsers
|
|
944
|
+
|
|
945
|
+
The implementation uses browser-standard APIs and technologies:
|
|
946
|
+
|
|
947
|
+
React portals
|
|
948
|
+
|
|
949
|
+
Pointer events
|
|
950
|
+
|
|
951
|
+
Touch input
|
|
952
|
+
|
|
953
|
+
Focus events
|
|
954
|
+
|
|
955
|
+
ResizeObserver
|
|
956
|
+
|
|
957
|
+
requestAnimationFrame
|
|
958
|
+
|
|
959
|
+
SVG paths and masks
|
|
960
|
+
|
|
961
|
+
CSS custom properties
|
|
962
|
+
|
|
963
|
+
CSS gradients
|
|
964
|
+
|
|
965
|
+
CSS filters
|
|
966
|
+
|
|
967
|
+
Fixed viewport positioning
|
|
968
|
+
|
|
969
|
+
Tooltips are rendered into document.body, so they are not normally clipped by overflow: hidden or overflow: auto on application containers.
|
|
970
|
+
|
|
971
|
+
The position is recalculated when:
|
|
972
|
+
|
|
973
|
+
The window is resized
|
|
974
|
+
|
|
975
|
+
The page or an ancestor is scrolled
|
|
976
|
+
|
|
977
|
+
Tooltip content changes dimensions
|
|
978
|
+
|
|
979
|
+
Media or dynamic components change the tooltip size
|
|
980
|
+
|
|
981
|
+
Legacy Internet Explorer is not supported.
|
|
982
|
+
|
|
983
|
+
Mouse, keyboard, and touch behavior
|
|
984
|
+
|
|
985
|
+
Desktop pointer behavior:
|
|
986
|
+
|
|
987
|
+
Pointer enter opens the tooltip
|
|
988
|
+
|
|
989
|
+
Pointer leave starts the configured hide delay
|
|
990
|
+
|
|
991
|
+
Interactive content keeps the tooltip open while hovered
|
|
992
|
+
|
|
993
|
+
Keyboard behavior:
|
|
994
|
+
|
|
995
|
+
Focusing the anchor opens the tooltip
|
|
996
|
+
|
|
997
|
+
Focus can move into interactive content
|
|
998
|
+
|
|
999
|
+
Escape closes the tooltip
|
|
1000
|
+
|
|
1001
|
+
Touch behavior:
|
|
1002
|
+
|
|
1003
|
+
Tapping the anchor toggles the tooltip
|
|
1004
|
+
|
|
1005
|
+
Tapping outside closes it
|
|
1006
|
+
|
|
1007
|
+
React and Next.js
|
|
1008
|
+
|
|
1009
|
+
Tooltip and TooltipProvider use client-side browser APIs and React portals. In Next.js, render them from a Client Component.
|
|
1010
|
+
|
|
1011
|
+
"use client";
|
|
1012
|
+
|
|
1013
|
+
import { Tooltip, TooltipProvider } from "@ssaprt/tooltip";
|
|
1014
|
+
import "@ssaprt/tooltip/style.css";
|
|
1015
|
+
|
|
1016
|
+
export const TooltipClientProvider = ({
|
|
1017
|
+
children,
|
|
1018
|
+
}: {
|
|
1019
|
+
children: React.ReactNode;
|
|
1020
|
+
}) => {
|
|
1021
|
+
return (
|
|
1022
|
+
<TooltipProvider
|
|
1023
|
+
defaultRenderPosition="top"
|
|
1024
|
+
selectTheme="primary"
|
|
1025
|
+
>
|
|
1026
|
+
{children}
|
|
1027
|
+
</TooltipProvider>
|
|
1028
|
+
);
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
Use it in a layout:
|
|
1032
|
+
|
|
1033
|
+
import { TooltipClientProvider } from "./TooltipClientProvider";
|
|
1034
|
+
|
|
1035
|
+
export default function RootLayout({
|
|
1036
|
+
children,
|
|
1037
|
+
}: {
|
|
1038
|
+
children: React.ReactNode;
|
|
1039
|
+
}) {
|
|
1040
|
+
return (
|
|
1041
|
+
<html lang="en">
|
|
1042
|
+
<body>
|
|
1043
|
+
<TooltipClientProvider>
|
|
1044
|
+
{children}
|
|
1045
|
+
</TooltipClientProvider>
|
|
1046
|
+
</body>
|
|
1047
|
+
</html>
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
A provider is still optional. A standalone tooltip can be rendered directly from any Client Component.
|
|
1052
|
+
|
|
1053
|
+
Rendering and overflow
|
|
1054
|
+
|
|
1055
|
+
The visible tooltip is rendered through a React portal into document.body.
|
|
1056
|
+
|
|
1057
|
+
This provides several benefits:
|
|
1058
|
+
|
|
1059
|
+
Parent overflow normally does not clip the tooltip
|
|
1060
|
+
|
|
1061
|
+
The tooltip is positioned relative to the viewport
|
|
1062
|
+
|
|
1063
|
+
Scroll and resize updates are handled globally
|
|
1064
|
+
|
|
1065
|
+
Complex content is isolated from the anchor layout
|
|
1066
|
+
|
|
1067
|
+
A high stacking level can be used without depending on the anchor's stacking context
|
|
1068
|
+
|
|
1069
|
+
License
|
|
1070
|
+
|
|
1071
|
+
MIT
|