useAction.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { useCallback, useEffect, useRef, useState } from 'react';
  2. import { useNavigate } from 'react-router-dom';
  3. import type { NavMenuItem } from '@/utils/navUtils';
  4. const OVERFLOW_BUTTON_WIDTH = 80;
  5. const MENU_ITEM_GAP = 10;
  6. interface UseActionParams {
  7. menuItems: NavMenuItem[];
  8. isMobile: boolean;
  9. }
  10. /**
  11. * Topbar UI 交互响应逻辑 Hook
  12. * 处理 UI 状态、事件处理、DOM 交互等响应逻辑
  13. */
  14. export function useAction({ menuItems, isMobile }: UseActionParams) {
  15. const navigate = useNavigate();
  16. const menuContainerRef = useRef<HTMLDivElement>(null);
  17. const menuItemsRef = useRef<Map<string, HTMLButtonElement>>(new Map());
  18. const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
  19. const [isOverflowMenuOpen, setIsOverflowMenuOpen] = useState(false);
  20. const [visibleMenuItems, setVisibleMenuItems] = useState<NavMenuItem[]>([]);
  21. const [overflowMenuItems, setOverflowMenuItems] = useState<NavMenuItem[]>([]);
  22. const calculateVisibleItems = useCallback(() => {
  23. if (isMobile || !menuContainerRef.current || menuItems.length === 0) {
  24. setVisibleMenuItems(menuItems);
  25. setOverflowMenuItems([]);
  26. return;
  27. }
  28. const container = menuContainerRef.current;
  29. const containerWidth = container.offsetWidth;
  30. let totalWidth = 0;
  31. const visible: NavMenuItem[] = [];
  32. const overflow: NavMenuItem[] = [];
  33. for (let i = 0; i < menuItems.length; i++) {
  34. const item = menuItems[i];
  35. const itemElement = menuItemsRef.current.get(item.name);
  36. if (itemElement) {
  37. const itemWidth = itemElement.offsetWidth + MENU_ITEM_GAP;
  38. const needsOverflowButton = i < menuItems.length - 1;
  39. if (
  40. totalWidth + itemWidth + (needsOverflowButton ? OVERFLOW_BUTTON_WIDTH : 0) <=
  41. containerWidth
  42. ) {
  43. visible.push(item);
  44. totalWidth += itemWidth;
  45. } else {
  46. overflow.push(...menuItems.slice(i));
  47. break;
  48. }
  49. } else {
  50. visible.push(item);
  51. }
  52. }
  53. setVisibleMenuItems(visible);
  54. setOverflowMenuItems(overflow);
  55. }, [isMobile, menuItems]);
  56. useEffect(() => {
  57. if (!isMobile && menuItems.length > 0) {
  58. const timer = setTimeout(() => {
  59. calculateVisibleItems();
  60. }, 0);
  61. return () => clearTimeout(timer);
  62. }
  63. }, [isMobile, menuItems.length, calculateVisibleItems]);
  64. useEffect(() => {
  65. if (isMobile) {
  66. return;
  67. }
  68. const handleResize = () => {
  69. calculateVisibleItems();
  70. };
  71. window.addEventListener('resize', handleResize);
  72. return () => window.removeEventListener('resize', handleResize);
  73. }, [isMobile, calculateVisibleItems]);
  74. const handleMenuClick = useCallback(
  75. (path: string) => {
  76. navigate(path);
  77. setIsMobileMenuOpen(false);
  78. setIsOverflowMenuOpen(false);
  79. },
  80. [navigate]
  81. );
  82. const toggleMobileMenu = useCallback(() => {
  83. setIsMobileMenuOpen((prev) => !prev);
  84. }, []);
  85. const closeMobileMenu = useCallback(() => {
  86. setIsMobileMenuOpen(false);
  87. }, []);
  88. const toggleOverflowMenu = useCallback(() => {
  89. setIsOverflowMenuOpen((prev) => !prev);
  90. }, []);
  91. const setMenuItemRef = useCallback((itemName: string) => {
  92. return (el: HTMLButtonElement | null) => {
  93. if (el) {
  94. menuItemsRef.current.set(itemName, el);
  95. } else {
  96. menuItemsRef.current.delete(itemName);
  97. }
  98. };
  99. }, []);
  100. return {
  101. menuContainerRef,
  102. menuItemsRef,
  103. isMobileMenuOpen,
  104. isOverflowMenuOpen,
  105. visibleMenuItems,
  106. overflowMenuItems,
  107. handleMenuClick,
  108. toggleMobileMenu,
  109. closeMobileMenu,
  110. toggleOverflowMenu,
  111. setMenuItemRef,
  112. };
  113. }