Fix mobile action sheet close on long press release

This commit is contained in:
ShiGuangAlex
2026-04-16 20:34:03 +08:00
parent 7ba91a0293
commit add552e2a8
+24 -2
View File
@@ -1,6 +1,6 @@
import { Radio, X } from 'lucide-react'; import { Radio, X } from 'lucide-react';
import Image from 'next/image'; import Image from 'next/image';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
interface ActionItem { interface ActionItem {
@@ -46,6 +46,7 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
const [isAnimating, setIsAnimating] = useState(false); const [isAnimating, setIsAnimating] = useState(false);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const backdropPressStarted = useRef(false);
// 确保组件在客户端挂载后才渲染 Portal // 确保组件在客户端挂载后才渲染 Portal
useEffect(() => { useEffect(() => {
@@ -58,6 +59,7 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
let timer: NodeJS.Timeout; let timer: NodeJS.Timeout;
if (isOpen) { if (isOpen) {
backdropPressStarted.current = false;
setIsVisible(true); setIsVisible(true);
// 使用双重 requestAnimationFrame 确保DOM完全渲染 // 使用双重 requestAnimationFrame 确保DOM完全渲染
animationId = requestAnimationFrame(() => { animationId = requestAnimationFrame(() => {
@@ -66,6 +68,7 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
}); });
}); });
} else { } else {
backdropPressStarted.current = false;
setIsAnimating(false); setIsAnimating(false);
// 等待动画完成后隐藏组件 // 等待动画完成后隐藏组件
timer = setTimeout(() => { timer = setTimeout(() => {
@@ -171,6 +174,23 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
} }
}; };
const armBackdropClose = () => {
backdropPressStarted.current = true;
};
const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
// 菜单打开前那次长按的松手会产生一个“悬空 click”,
// 这次 click 并不是从遮罩开始按下的,所以不能拿来关闭菜单。
if (!backdropPressStarted.current) {
e.preventDefault();
e.stopPropagation();
return;
}
backdropPressStarted.current = false;
onClose();
};
const content = ( const content = (
<div <div
className="fixed inset-0 z-[9999] flex items-end justify-center" className="fixed inset-0 z-[9999] flex items-end justify-center"
@@ -187,7 +207,9 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
<div <div
className={`absolute inset-0 bg-black/50 transition-opacity duration-200 ease-out ${isAnimating ? 'opacity-100' : 'opacity-0' className={`absolute inset-0 bg-black/50 transition-opacity duration-200 ease-out ${isAnimating ? 'opacity-100' : 'opacity-0'
}`} }`}
onClick={onClose} onPointerDown={armBackdropClose}
onTouchStart={armBackdropClose}
onClick={handleBackdropClick}
onTouchMove={(e) => { onTouchMove={(e) => {
// 只阻止滚动,允许其他触摸事件(包括点击) // 只阻止滚动,允许其他触摸事件(包括点击)
e.preventDefault(); e.preventDefault();