Fix guided tour tooltip going off-screen on mobile
Body scroll is locked for the tour's duration, so scrollIntoView on the step-2 target had nothing to scroll and its position was measured way off-screen. Scroll the frozen body's offset directly instead, and clamp tooltip vertical position to the viewport as a safety net. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f6960fd3ec
commit
219256be55
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import { useLockBodyScroll } from '@/lib/useLockBodyScroll'
|
import { useLockBodyScroll, scrollLockedBodyTo } from '@/lib/useLockBodyScroll'
|
||||||
|
|
||||||
interface TourStep {
|
interface TourStep {
|
||||||
target: string | null // CSS selector, or null = centered modal
|
target: string | null // CSS selector, or null = centered modal
|
||||||
@ -54,8 +54,9 @@ const STEPS: TourStep[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const PAD = 10 // px padding around spotlight
|
const PAD = 10 // px padding around spotlight
|
||||||
const TIP_WIDTH = 300 // tooltip width in px
|
const TIP_WIDTH = 300 // tooltip width in px
|
||||||
|
const TIP_MAX_HEIGHT = 260 // conservative estimate, used to keep the tooltip on-screen
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onDone: () => void
|
onDone: () => void
|
||||||
@ -103,7 +104,11 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
|||||||
const elRect = el.getBoundingClientRect()
|
const elRect = el.getBoundingClientRect()
|
||||||
scrollParent.scrollTop = elRect.top - parentRect.top + scrollParent.scrollTop - 20
|
scrollParent.scrollTop = elRect.top - parentRect.top + scrollParent.scrollTop - 20
|
||||||
} else {
|
} else {
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
// Page scroll is locked (useLockBodyScroll), so scrollIntoView has nothing
|
||||||
|
// to scroll — shift the frozen body's offset instead so the target actually moves into view.
|
||||||
|
const lockedY = Math.abs(parseFloat(document.body.style.top || '0')) || 0
|
||||||
|
const elRect = el.getBoundingClientRect()
|
||||||
|
scrollLockedBodyTo(lockedY + elRect.top - 100)
|
||||||
}
|
}
|
||||||
// Measure after layout settles (double-rAF ensures paint is done)
|
// Measure after layout settles (double-rAF ensures paint is done)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@ -179,7 +184,7 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
|||||||
)
|
)
|
||||||
tooltipStyle = {
|
tooltipStyle = {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: spot.top + spot.height + 12,
|
top: Math.max(8, Math.min(spot.top + spot.height + 12, vh - TIP_MAX_HEIGHT - 8)),
|
||||||
left,
|
left,
|
||||||
width: Math.min(TIP_WIDTH, vw - 16),
|
width: Math.min(TIP_WIDTH, vw - 16),
|
||||||
zIndex: 10002,
|
zIndex: 10002,
|
||||||
@ -191,7 +196,7 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
|||||||
)
|
)
|
||||||
tooltipStyle = {
|
tooltipStyle = {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: vh - spot.top + 12,
|
bottom: Math.max(8, Math.min(vh - spot.top + 12, vh - TIP_MAX_HEIGHT - 8)),
|
||||||
left,
|
left,
|
||||||
width: Math.min(TIP_WIDTH, vw - 16),
|
width: Math.min(TIP_WIDTH, vw - 16),
|
||||||
zIndex: 10002,
|
zIndex: 10002,
|
||||||
@ -200,7 +205,7 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
|||||||
// right
|
// right
|
||||||
tooltipStyle = {
|
tooltipStyle = {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: Math.max(8, spot.top + spot.height / 2 - 80),
|
top: Math.max(8, Math.min(spot.top + spot.height / 2 - 80, vh - TIP_MAX_HEIGHT - 8)),
|
||||||
left: Math.min(spot.left + spot.width + 12, vw - TIP_WIDTH - 8),
|
left: Math.min(spot.left + spot.width + 12, vw - TIP_WIDTH - 8),
|
||||||
width: Math.min(TIP_WIDTH, vw - 16),
|
width: Math.min(TIP_WIDTH, vw - 16),
|
||||||
zIndex: 10002,
|
zIndex: 10002,
|
||||||
|
|||||||
@ -25,3 +25,9 @@ export function useLockBodyScroll() {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** While the page is frozen by useLockBodyScroll, there's no real scroll to move —
|
||||||
|
* "scrolling" means shifting the fixed body's negative `top` offset instead. */
|
||||||
|
export function scrollLockedBodyTo(y: number) {
|
||||||
|
document.body.style.top = `-${Math.max(0, y)}px`
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user