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 React, { useState, useEffect } from 'react';
import { useSpring, animated } from '@react-spring/web';
import { useDrag } from 'react-use-gesture'; import { useDrag } from 'react-use-gesture';
import './MobileContextMenu.scss'; import './MobileContextMenu.scss';
export function MobileContextMenu({ isOpen, onClose, children }) { 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(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
document.body.style.overscrollBehavior = 'contain'; document.body.style.overscrollBehavior = 'contain';
@ -14,51 +25,53 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
}; };
}, [isOpen]); }, [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( 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 (last) {
if (my > innerHeight / 4) { setIsDragging(false);
event.preventDefault(); if (my > getInnerHeight() / 4) {
event.stopPropagation();
onClose(); onClose();
} else { } 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, filterTaps: true,
bounds: { top: 0 }, bounds: { top: 0 },
rubberband: true, 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 ( return (
<> <>
<animated.div <div
className="bottom-sheet-backdrop" className="bottom-sheet-backdrop"
onClick={onClose} onClick={onClose}
style={{ opacity: y.to([0, innerHeight], [1, 0], 'clamp') }} style={{ opacity: Math.max(0, backdropOpacity) }}
/> />
<animated.div <div
className="bottom-sheet-container" className={containerClasses}
{...bind()} {...bind()}
style={{ style={{
y, transform: `translate3d(0, ${y}px, 0)`,
touchAction: 'none', touchAction: 'none',
}} }}
> >
@ -66,7 +79,7 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
<div className="bottom-sheet-content" style={{ overflow: 'visible' }}> <div className="bottom-sheet-content" style={{ overflow: 'visible' }}>
{children} {children}
</div> </div>
</animated.div> </div>
</> </>
); );
} }

View file

@ -1,34 +1,49 @@
.bottom-sheet-backdrop { .bottom-sheet-backdrop {
position: fixed; position: fixed;
inset: 0; top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
z-index: 1000; // The backdrop fade will also be smoother with a transition
touch-action: none; transition: opacity 300ms ease-out;
z-index: 999;
} }
.bottom-sheet-container { .bottom-sheet-container {
position: fixed; position: fixed;
left: 0;
right: 0;
bottom: 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; display: flex;
flex-direction: column; flex-direction: column;
max-height: 90vh;
border-radius: 16px 16px 0 0; // This is the magic: apply a transition only when this class is present
box-shadow: 0px -4px B16px rgba(0, 0, 0, 0.15); &.is-transitioning {
transition: transform 300ms ease-out;
}
} }
// Your other styles remain the same
.bottom-sheet-grabber { .bottom-sheet-grabber {
flex-shrink: 0;
width: 40px; width: 40px;
height: 5px; height: 5px;
border-radius: 2.5px;
background-color: #ccc; background-color: #ccc;
border-radius: 2.5px;
margin: 8px auto; margin: 8px auto;
cursor: grab;
} }
.bottom-sheet-content { .bottom-sheet-content {
padding: 16px;
overflow-y: auto; overflow-y: auto;
padding: 0 1rem 1rem 1rem;
} }