fix: lock body scroll when any modal or drawer is open
Add useLockBodyScroll hook (sets overflow:hidden on body, restores on unmount) and apply it to ColorPicker, AdminColorFilter, WelcomeModal, and GuidedTour. CartDrawer uses an inline effect keyed on drawerOpen since it is always mounted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e95ec68931
commit
6865d2d437
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { BASE } from '@/lib/basepath'
|
import { BASE } from '@/lib/basepath'
|
||||||
|
import { useLockBodyScroll } from '@/lib/useLockBodyScroll'
|
||||||
|
|
||||||
interface ColorEntry {
|
interface ColorEntry {
|
||||||
name: string
|
name: string
|
||||||
@ -24,6 +25,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AdminColorFilter({ disabledColors, onSave, onClose }: Props) {
|
export default function AdminColorFilter({ disabledColors, onSave, onClose }: Props) {
|
||||||
|
useLockBodyScroll()
|
||||||
const [families, setFamilies] = useState<ColorFamily[]>([])
|
const [families, setFamilies] = useState<ColorFamily[]>([])
|
||||||
const [disabled, setDisabled] = useState<Set<string>>(() => new Set(disabledColors))
|
const [disabled, setDisabled] = useState<Set<string>>(() => new Set(disabledColors))
|
||||||
const [openFamily, setOpenFamily] = useState<string | null>(null)
|
const [openFamily, setOpenFamily] = useState<string | null>(null)
|
||||||
|
|||||||
@ -53,6 +53,13 @@ const STEP_ORDER: Step[] = ['cart', 'delivery', 'info', 'payment']
|
|||||||
export default function CartDrawer() {
|
export default function CartDrawer() {
|
||||||
const { entries, drawerOpen, closeDrawer, removeEntry, updateQuantity, clearCart, totalItems } = useCart()
|
const { entries, drawerOpen, closeDrawer, removeEntry, updateQuantity, clearCart, totalItems } = useCart()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!drawerOpen) return
|
||||||
|
const prev = document.body.style.overflow
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
return () => { document.body.style.overflow = prev }
|
||||||
|
}, [drawerOpen])
|
||||||
|
|
||||||
const [editingEntry, setEditingEntry] = useState<CartEntry | null>(null)
|
const [editingEntry, setEditingEntry] = useState<CartEntry | null>(null)
|
||||||
|
|
||||||
const [step, setStep] = useState<Step>('cart')
|
const [step, setStep] = useState<Step>('cart')
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useCart } from '@/context/CartContext'
|
|||||||
import { BASE } from '@/lib/basepath'
|
import { BASE } from '@/lib/basepath'
|
||||||
import type { CartEntry } from '@/context/CartContext'
|
import type { CartEntry } from '@/context/CartContext'
|
||||||
import { fmt } from '@/lib/format'
|
import { fmt } from '@/lib/format'
|
||||||
|
import { useLockBodyScroll } from '@/lib/useLockBodyScroll'
|
||||||
|
|
||||||
interface ColorEntry {
|
interface ColorEntry {
|
||||||
name: string
|
name: string
|
||||||
@ -29,6 +30,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ColorPicker({ product, maxColors, onClose, editingEntry }: Props) {
|
export default function ColorPicker({ product, maxColors, onClose, editingEntry }: Props) {
|
||||||
|
useLockBodyScroll()
|
||||||
const { addToCart, updateEntry } = useCart()
|
const { addToCart, updateEntry } = useCart()
|
||||||
const [families, setFamilies] = useState<ColorFamily[]>([])
|
const [families, setFamilies] = useState<ColorFamily[]>([])
|
||||||
const [openFamily, setOpenFamily] = useState<string | null>(null)
|
const [openFamily, setOpenFamily] = useState<string | null>(null)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { useLockBodyScroll } 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
|
||||||
@ -62,6 +63,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function GuidedTour({ onDone, onStart }: Props) {
|
export default function GuidedTour({ onDone, onStart }: Props) {
|
||||||
|
useLockBodyScroll()
|
||||||
const [step, setStep] = useState(0)
|
const [step, setStep] = useState(0)
|
||||||
const [targetRect, setTargetRect] = useState<DOMRect | null>(null)
|
const [targetRect, setTargetRect] = useState<DOMRect | null>(null)
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { useLockBodyScroll } from '@/lib/useLockBodyScroll'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onTour: () => void
|
onTour: () => void
|
||||||
onDismiss: () => void
|
onDismiss: () => void
|
||||||
@ -13,6 +15,7 @@ const HOW_IT_WORKS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export default function WelcomeModal({ onTour, onDismiss }: Props) {
|
export default function WelcomeModal({ onTour, onDismiss }: Props) {
|
||||||
|
useLockBodyScroll()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
|
|||||||
10
estore/src/lib/useLockBodyScroll.ts
Normal file
10
estore/src/lib/useLockBodyScroll.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
/** Locks body scroll while the calling component is mounted. */
|
||||||
|
export function useLockBodyScroll() {
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = document.body.style.overflow
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
return () => { document.body.style.overflow = prev }
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user