Skip to content

Commit d2d893e

Browse files
authored
fix: show info-tooltips on mobile (#1268)
1 parent ad0a645 commit d2d893e

File tree

6 files changed

+93
-25
lines changed

6 files changed

+93
-25
lines changed

app/components/Package/Dependencies.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,15 @@ const numberFormatter = useNumberFormatter()
9595
{{ dep }}
9696
</LinkBase>
9797
<span class="flex items-center gap-1 max-w-[40%]" dir="ltr">
98-
<span
98+
<TooltipApp
9999
v-if="outdatedDeps[dep]"
100-
class="shrink-0"
100+
class="shrink-0 p-2 -m-2"
101101
:class="getVersionClass(outdatedDeps[dep])"
102-
:title="getOutdatedTooltip(outdatedDeps[dep], $t)"
103102
aria-hidden="true"
103+
:text="getOutdatedTooltip(outdatedDeps[dep], $t)"
104104
>
105105
<span class="i-carbon:warning-alt w-3 h-3" />
106-
</span>
106+
</TooltipApp>
107107
<LinkBase
108108
v-if="getVulnerableDepInfo(dep)"
109109
:to="packageRoute(dep, getVulnerableDepInfo(dep)!.version)"

app/components/Package/InstallScripts.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,18 @@ const isExpanded = shallowRef(false)
115115
{{ dep }}
116116
</LinkBase>
117117
<span class="flex items-center gap-1">
118-
<span
118+
<TooltipApp
119119
v-if="
120120
outdatedNpxDeps[dep] &&
121121
outdatedNpxDeps[dep].resolved !== outdatedNpxDeps[dep].latest
122122
"
123-
class="shrink-0"
123+
class="shrink-0 p-2 -m-2"
124124
:class="getVersionClass(outdatedNpxDeps[dep])"
125-
:title="getOutdatedTooltip(outdatedNpxDeps[dep], $t)"
126125
aria-hidden="true"
126+
:text="getOutdatedTooltip(outdatedNpxDeps[dep], $t)"
127127
>
128128
<span class="i-carbon:warning-alt w-3 h-3" />
129-
</span>
129+
</TooltipApp>
130130
<span
131131
class="font-mono text-xs text-end truncate"
132132
:class="getVersionClass(outdatedNpxDeps[dep])"

app/components/Package/SkillsModal.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,15 @@ function getWarningTooltip(skill: SkillListItem): string | undefined {
164164
aria-hidden="true"
165165
/>
166166
<span class="font-mono text-sm text-fg-muted">{{ skill.name }}</span>
167-
<span
167+
<TooltipApp
168168
v-if="skill.warnings?.length"
169-
class="i-carbon:warning w-3.5 h-3.5 text-amber-500 shrink-0"
170-
:title="getWarningTooltip(skill)"
171-
/>
169+
class="shrink-0 p-2 -m-2"
170+
aria-hidden="true"
171+
:text="getWarningTooltip(skill)"
172+
to="#skills-modal"
173+
>
174+
<span class="i-carbon:warning w-3.5 h-3.5 text-amber-500" />
175+
</TooltipApp>
172176
</button>
173177

174178
<!-- Expandable details -->

app/components/Tooltip/App.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const props = defineProps<{
66
position?: 'top' | 'bottom' | 'left' | 'right'
77
/** Enable interactive tooltip (pointer events + hide delay for clickable content) */
88
interactive?: boolean
9+
/** Teleport target for the tooltip content (defaults to 'body') */
10+
to?: string | HTMLElement
911
}>()
1012
1113
const isVisible = shallowRef(false)
@@ -48,6 +50,7 @@ const tooltipAttrs = computed(() => {
4850
:position
4951
:interactive
5052
:tooltip-attr="tooltipAttrs"
53+
:to="props.to"
5154
@mouseenter="show"
5255
@mouseleave="hide"
5356
@focusin="show"

app/components/Tooltip/Base.vue

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@ import type { HTMLAttributes } from 'vue'
33
import type { Placement } from '@floating-ui/vue'
44
import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue'
55
6-
const props = defineProps<{
7-
/** Tooltip text (optional when using content slot) */
8-
text?: string
9-
/** Position: 'top' | 'bottom' | 'left' | 'right' */
10-
position?: 'top' | 'bottom' | 'left' | 'right'
11-
/** is tooltip visible */
12-
isVisible: boolean
13-
/** Allow pointer events on tooltip (for interactive content like links) */
14-
interactive?: boolean
15-
/** attributes for tooltip element */
16-
tooltipAttr?: HTMLAttributes
17-
}>()
6+
const 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+
}>(),
21+
{
22+
to: 'body',
23+
},
24+
)
1825
1926
const triggerRef = useTemplateRef('triggerRef')
2027
const tooltipRef = useTemplateRef('tooltipRef')
@@ -32,7 +39,7 @@ const { floatingStyles } = useFloating(triggerRef, tooltipRef, {
3239
<div ref="triggerRef" class="inline-flex">
3340
<slot />
3441

35-
<Teleport to="body">
42+
<Teleport :to="props.to">
3643
<Transition
3744
enter-active-class="transition-opacity duration-150 motion-reduce:transition-none"
3845
leave-active-class="transition-opacity duration-100 motion-reduce:transition-none"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { mountSuspended } from '@nuxt/test-utils/runtime'
3+
import TooltipBase from '~/components/Tooltip/Base.vue'
4+
5+
describe('TooltipBase to prop', () => {
6+
it('teleports to body by default', async () => {
7+
await mountSuspended(TooltipBase, {
8+
props: {
9+
text: 'Tooltip text',
10+
isVisible: true,
11+
tooltipAttr: { 'aria-label': 'tooltip' },
12+
},
13+
slots: {
14+
default: '<button>Trigger</button>',
15+
},
16+
})
17+
18+
const tooltip = document.querySelector<HTMLElement>('[aria-label="tooltip"]')
19+
expect(tooltip).not.toBeNull()
20+
expect(tooltip?.textContent).toContain('Tooltip text')
21+
22+
const currentContainer = tooltip?.parentElement?.parentElement
23+
expect(currentContainer).toBe(document.body)
24+
})
25+
26+
it('teleports into provided container when using selector string', async () => {
27+
const container = document.createElement('div')
28+
container.id = 'tooltip-container'
29+
document.body.appendChild(container)
30+
31+
try {
32+
await mountSuspended(TooltipBase, {
33+
props: {
34+
text: 'Tooltip text',
35+
isVisible: true,
36+
to: '#tooltip-container',
37+
tooltipAttr: { 'aria-label': 'tooltip' },
38+
},
39+
slots: {
40+
default: '<button>Trigger</button>',
41+
},
42+
})
43+
44+
const tooltip = container.querySelector<HTMLElement>('[aria-label="tooltip"]')
45+
expect(tooltip).not.toBeNull()
46+
expect(tooltip?.textContent).toContain('Tooltip text')
47+
48+
const currentContainer = tooltip?.parentElement?.parentElement
49+
expect(currentContainer).toBe(container)
50+
} finally {
51+
container.remove()
52+
}
53+
})
54+
})

0 commit comments

Comments
 (0)