Completely revise the animations for the context menu to be compatible with Firefox for Android (worked everywhere else amusingly)

This commit is contained in:
Gigiaj 2025-06-22 03:35:58 -05:00
parent 4963142ed4
commit a4c5d1a6f9
2 changed files with 67 additions and 39 deletions

View file

@ -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 (
<>
<animated.div
<div
className="bottom-sheet-backdrop"
onClick={onClose}
style={{ opacity: y.to([0, innerHeight], [1, 0], 'clamp') }}
style={{ opacity: Math.max(0, backdropOpacity) }}
/>
<animated.div
className="bottom-sheet-container"
<div
className={containerClasses}
{...bind()}
style={{
y,
transform: `translate3d(0, ${y}px, 0)`,
touchAction: 'none',
}}
>
@ -66,7 +79,7 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
<div className="bottom-sheet-content" style={{ overflow: 'visible' }}>
{children}
</div>
</animated.div>
</div>
</>
);
}

View file

@ -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;
}