// src/hooks/useDelayedMenu.js // 导航菜单延迟关闭 Hook - 优化 hover 和 click 交互体验 import { useState, useRef, useCallback } from 'react'; /** * 自定义 Hook:提供带延迟关闭功能的菜单控制 * * 解决问题: * 1. 用户快速移动鼠标导致菜单意外关闭 * 2. Hover 和 Click 状态冲突 * 3. 从 MenuButton 移动到 MenuList 时菜单闪烁 * * 功能特性: * - ✅ Hover 进入:立即打开菜单 * - ✅ Hover 离开:延迟关闭(默认 200ms) * - ✅ Click 切换:支持点击切换打开/关闭状态 * - ✅ 智能取消:再次 hover 进入时取消关闭定时器 * * @param {Object} options - 配置选项 * @param {number} options.closeDelay - 延迟关闭时间(毫秒),默认 200ms * @returns {Object} 菜单控制对象 */ export function useDelayedMenu({ closeDelay = 200 } = {}) { const [isOpen, setIsOpen] = useState(false); const closeTimerRef = useRef(null); const isClickedRef = useRef(false); // 追踪是否通过点击打开 /** * 打开菜单 * - 立即打开,无延迟 * - 清除任何待执行的关闭定时器 */ const onOpen = useCallback(() => { // 清除待执行的关闭定时器 if (closeTimerRef.current) { clearTimeout(closeTimerRef.current); closeTimerRef.current = null; } setIsOpen(true); }, []); /** * 延迟关闭菜单 * - 设置定时器,延迟后关闭 * - 如果在延迟期间再次 hover 进入,会被 onOpen 取消 */ const onDelayedClose = useCallback(() => { // 如果是点击打开的,hover 离开时不自动关闭 if (isClickedRef.current) { return; } // 清除之前的定时器(防止重复设置) if (closeTimerRef.current) { clearTimeout(closeTimerRef.current); } // 设置延迟关闭定时器 closeTimerRef.current = setTimeout(() => { setIsOpen(false); closeTimerRef.current = null; }, closeDelay); }, [closeDelay]); /** * 立即关闭菜单 * - 无延迟,立即关闭 * - 清除所有定时器和状态标记 */ const onClose = useCallback(() => { // 清除定时器 if (closeTimerRef.current) { clearTimeout(closeTimerRef.current); closeTimerRef.current = null; } setIsOpen(false); isClickedRef.current = false; }, []); /** * 切换菜单状态(用于点击) * - 如果关闭 → 打开,并标记为点击打开 * - 如果打开 → 关闭,并清除点击标记 */ const onToggle = useCallback(() => { if (isOpen) { // 当前已打开 → 关闭 onClose(); } else { // 当前已关闭 → 打开 onOpen(); isClickedRef.current = true; // 标记为点击打开 } }, [isOpen, onOpen, onClose]); /** * Hover 进入处理 * - 打开菜单 * - 清除点击标记(允许 hover 离开时自动关闭) */ const handleMouseEnter = useCallback(() => { onOpen(); isClickedRef.current = false; // 清除点击标记,允许 hover 控制 }, [onOpen]); /** * Hover 离开处理 * - 延迟关闭菜单 */ const handleMouseLeave = useCallback(() => { onDelayedClose(); }, [onDelayedClose]); /** * 点击处理 * - 切换菜单状态 */ const handleClick = useCallback(() => { onToggle(); }, [onToggle]); // 组件卸载时清理定时器 const cleanup = useCallback(() => { if (closeTimerRef.current) { clearTimeout(closeTimerRef.current); closeTimerRef.current = null; } }, []); return { isOpen, onOpen, onClose, onDelayedClose, onToggle, handleMouseEnter, handleMouseLeave, handleClick, cleanup }; }