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'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useLockBodyScroll } from '@/lib/useLockBodyScroll'
|
||||
import { useLockBodyScroll, scrollLockedBodyTo } from '@/lib/useLockBodyScroll'
|
||||
|
||||
interface TourStep {
|
||||
target: string | null // CSS selector, or null = centered modal
|
||||
@ -54,8 +54,9 @@ const STEPS: TourStep[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const PAD = 10 // px padding around spotlight
|
||||
const TIP_WIDTH = 300 // tooltip width in px
|
||||
const PAD = 10 // px padding around spotlight
|
||||
const TIP_WIDTH = 300 // tooltip width in px
|
||||
const TIP_MAX_HEIGHT = 260 // conservative estimate, used to keep the tooltip on-screen
|
||||
|
||||
interface Props {
|
||||
onDone: () => void
|
||||
@ -103,7 +104,11 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
||||
const elRect = el.getBoundingClientRect()
|
||||
scrollParent.scrollTop = elRect.top - parentRect.top + scrollParent.scrollTop - 20
|
||||
} 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)
|
||||
requestAnimationFrame(() => {
|
||||
@ -179,7 +184,7 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
||||
)
|
||||
tooltipStyle = {
|
||||
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,
|
||||
width: Math.min(TIP_WIDTH, vw - 16),
|
||||
zIndex: 10002,
|
||||
@ -191,7 +196,7 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
||||
)
|
||||
tooltipStyle = {
|
||||
position: 'fixed',
|
||||
bottom: vh - spot.top + 12,
|
||||
bottom: Math.max(8, Math.min(vh - spot.top + 12, vh - TIP_MAX_HEIGHT - 8)),
|
||||
left,
|
||||
width: Math.min(TIP_WIDTH, vw - 16),
|
||||
zIndex: 10002,
|
||||
@ -200,7 +205,7 @@ export default function GuidedTour({ onDone, onStart }: Props) {
|
||||
// right
|
||||
tooltipStyle = {
|
||||
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),
|
||||
width: Math.min(TIP_WIDTH, vw - 16),
|
||||
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