diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx index fd809fa0..b9beda87 100644 --- a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx +++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx @@ -1,10 +1,21 @@ -import React, { useEffect } from 'react'; -import { useSpring, animated } from '@react-spring/web'; +import React, { useState, useEffect } from 'react'; import { useDrag } from 'react-use-gesture'; import './MobileContextMenu.scss'; export function MobileContextMenu({ isOpen, onClose, children }) { - const { innerHeight } = window; + const getInnerHeight = () => (typeof window !== 'undefined' ? window.innerHeight : 0); + const [y, setY] = useState(getInnerHeight()); + const [isDragging, setIsDragging] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => { + setY(isOpen ? 0 : getInnerHeight()); + }, 10); + + return () => clearTimeout(timer); + }, [isOpen]); + + useEffect(() => { if (isOpen) { document.body.style.overscrollBehavior = 'contain'; @@ -14,51 +25,53 @@ export function MobileContextMenu({ isOpen, onClose, children }) { }; }, [isOpen]); - const [{ y }, api] = useSpring(() => ({ - y: innerHeight, - config: { tension: 250, friction: 25 }, - })); - - useEffect(() => { - api.start({ y: isOpen ? 0 : innerHeight }); - }, [api, innerHeight, isOpen]); - const bind = useDrag( - ({ last, movement: [, my], event }) => { + ({ last, movement: [, my], down }) => { + if (down && !isDragging) { + setIsDragging(true); + } + + const newY = Math.max(my, 0); + setY(newY); + if (last) { - if (my > innerHeight / 4) { - event.preventDefault(); - event.stopPropagation(); + setIsDragging(false); + if (my > getInnerHeight() / 4) { onClose(); } else { - api.start({ y: 0 }); + setY(0); } - } else { - api.start({ y: Math.max(my, 0), immediate: true }); } }, { - from: () => [0, y.get()], + from: () => [0, y], filterTaps: true, bounds: { top: 0 }, rubberband: true, } ); - if (!isOpen) return null; + + if (!isOpen && y >= getInnerHeight()) return null; + const containerClasses = [ + 'bottom-sheet-container', + !isDragging ? 'is-transitioning' : '', + ].join(' '); + + const backdropOpacity = y > 0 ? 1 - y / getInnerHeight() : 1; return ( <> - - @@ -66,7 +79,7 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
{children}
-
+ ); } diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.scss b/src/app/molecules/mobile-context-menu/MobileContextMenu.scss index f42e1f75..326385da 100644 --- a/src/app/molecules/mobile-context-menu/MobileContextMenu.scss +++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.scss @@ -1,34 +1,49 @@ .bottom-sheet-backdrop { position: fixed; - inset: 0; + top: 0; + left: 0; + width: 100%; + height: 100%; background-color: rgba(0, 0, 0, 0.5); - z-index: 1000; - touch-action: none; + // The backdrop fade will also be smoother with a transition + transition: opacity 300ms ease-out; + z-index: 999; } .bottom-sheet-container { position: fixed; - left: 0; - right: 0; bottom: 0; - z-index: 1001; + left: 0; + width: 100%; + // Set transform from the component's style prop + // The 'will-change' property is a performance hint for the browser + will-change: transform; + z-index: 1000; + + // Your existing styles for the sheet itself + border-top-left-radius: 16px; + border-top-right-radius: 16px; + box-shadow: 0 -2px A10px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; - max-height: 90vh; - border-radius: 16px 16px 0 0; - box-shadow: 0px -4px B16px rgba(0, 0, 0, 0.15); + + // This is the magic: apply a transition only when this class is present + &.is-transitioning { + transition: transform 300ms ease-out; + } } +// Your other styles remain the same .bottom-sheet-grabber { - flex-shrink: 0; width: 40px; height: 5px; - border-radius: 2.5px; background-color: #ccc; + border-radius: 2.5px; margin: 8px auto; + cursor: grab; } .bottom-sheet-content { + padding: 16px; overflow-y: auto; - padding: 0 1rem 1rem 1rem; }