import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import type { NavMenuItem } from '@/utils/navUtils'; const OVERFLOW_BUTTON_WIDTH = 80; const MENU_ITEM_GAP = 10; interface UseActionParams { menuItems: NavMenuItem[]; isMobile: boolean; } /** * Topbar UI 交互响应逻辑 Hook * 处理 UI 状态、事件处理、DOM 交互等响应逻辑 */ export function useAction({ menuItems, isMobile }: UseActionParams) { const navigate = useNavigate(); const menuContainerRef = useRef(null); const menuItemsRef = useRef>(new Map()); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuClosing, setIsMobileMenuClosing] = useState(false); const [isOverflowMenuOpen, setIsOverflowMenuOpen] = useState(false); const [visibleMenuItems, setVisibleMenuItems] = useState([]); const [overflowMenuItems, setOverflowMenuItems] = useState([]); const calculateVisibleItems = useCallback(() => { if (isMobile || !menuContainerRef.current || menuItems.length === 0) { setVisibleMenuItems(menuItems); setOverflowMenuItems([]); return; } const container = menuContainerRef.current; const containerWidth = container.offsetWidth; let totalWidth = 0; const visible: NavMenuItem[] = []; const overflow: NavMenuItem[] = []; for (let i = 0; i < menuItems.length; i++) { const item = menuItems[i]; const itemElement = menuItemsRef.current.get(item.name); if (itemElement) { const itemWidth = itemElement.offsetWidth + MENU_ITEM_GAP; const needsOverflowButton = i < menuItems.length - 1; if ( totalWidth + itemWidth + (needsOverflowButton ? OVERFLOW_BUTTON_WIDTH : 0) <= containerWidth ) { visible.push(item); totalWidth += itemWidth; } else { overflow.push(...menuItems.slice(i)); break; } } else { visible.push(item); } } setVisibleMenuItems(visible); setOverflowMenuItems(overflow); }, [isMobile, menuItems]); useEffect(() => { if (!isMobile && menuItems.length > 0) { const timer = setTimeout(() => { calculateVisibleItems(); }, 0); return () => clearTimeout(timer); } }, [isMobile, menuItems.length, calculateVisibleItems]); useEffect(() => { if (isMobile) { return; } const handleResize = () => { calculateVisibleItems(); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [isMobile, calculateVisibleItems]); const handleMenuClick = useCallback( (path: string) => { navigate(path); setIsMobileMenuOpen(false); setIsOverflowMenuOpen(false); }, [navigate] ); const toggleMobileMenu = useCallback(() => { if (isMobileMenuOpen) { setIsMobileMenuClosing(true); } else { setIsMobileMenuOpen(true); setIsMobileMenuClosing(false); } }, [isMobileMenuOpen]); const closeMobileMenu = useCallback(() => { setIsMobileMenuClosing(true); }, []); const handleMenuAnimationEnd = useCallback(() => { if (isMobileMenuClosing) { setIsMobileMenuOpen(false); setIsMobileMenuClosing(false); } }, [isMobileMenuClosing]); const toggleOverflowMenu = useCallback(() => { setIsOverflowMenuOpen((prev) => !prev); }, []); const setMenuItemRef = useCallback((itemName: string) => { return (el: HTMLButtonElement | null) => { if (el) { menuItemsRef.current.set(itemName, el); } else { menuItemsRef.current.delete(itemName); } }; }, []); return { menuContainerRef, menuItemsRef, isMobileMenuOpen, isMobileMenuClosing, isOverflowMenuOpen, visibleMenuItems, overflowMenuItems, handleMenuClick, toggleMobileMenu, closeMobileMenu, handleMenuAnimationEnd, toggleOverflowMenu, setMenuItemRef, }; }