forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1<script setup lang="ts">
2import type { HTMLAttributes } from 'vue'
3import type { Placement, Strategy } from '@floating-ui/vue'
4import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue'
5
6const props = withDefaults(
7 defineProps<{
8 /** Tooltip text (optional when using content slot) */
9 text?: string
10 /** Position: 'top' | 'bottom' | 'left' | 'right' */
11 position?: 'top' | 'bottom' | 'left' | 'right'
12 /** is tooltip visible */
13 isVisible: boolean
14 /** Allow pointer events on tooltip (for interactive content like links) */
15 interactive?: boolean
16 /** attributes for tooltip element */
17 tooltipAttr?: HTMLAttributes
18 /** Teleport target for the tooltip content (defaults to 'body') */
19 to?: string | HTMLElement
20 /** Whether to defer teleport rendering until after the component is mounted */
21 defer?: boolean
22 /** Offset distance in pixels (default: 4) */
23 offset?: number
24 /** Strategy for the tooltip - prefer fixed for sticky containers (defaults to 'absolute') */
25 strategy?: Strategy
26 }>(),
27 {
28 to: 'body',
29 offset: 4,
30 strategy: 'absolute',
31 },
32)
33
34const triggerRef = useTemplateRef('triggerRef')
35const tooltipRef = useTemplateRef('tooltipRef')
36
37const placement = computed<Placement>(() => props.position || 'bottom')
38
39const { floatingStyles } = useFloating(triggerRef, tooltipRef, {
40 placement,
41 whileElementsMounted: autoUpdate,
42 strategy: props.strategy,
43 middleware: [offset(props.offset), flip(), shift({ padding: 8 })],
44})
45</script>
46
47<template>
48 <div ref="triggerRef" class="inline-flex">
49 <slot />
50
51 <Teleport :to="props.to" :defer>
52 <Transition
53 enter-active-class="transition-opacity duration-150 motion-reduce:transition-none"
54 leave-active-class="transition-opacity duration-100 motion-reduce:transition-none"
55 enter-from-class="opacity-0"
56 leave-to-class="opacity-0"
57 >
58 <div
59 v-if="props.isVisible"
60 ref="tooltipRef"
61 class="px-2 py-1 font-mono text-xs text-fg bg-bg-elevated border border-border rounded shadow-lg whitespace-pre-line break-words max-w-xs z-[100]"
62 :class="{ 'pointer-events-none': !interactive }"
63 :style="floatingStyles"
64 v-bind="tooltipAttr"
65 >
66 <slot name="content">{{ text }}</slot>
67 </div>
68 </Transition>
69 </Teleport>
70 </div>
71</template>