| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- import { Fragment, memo, useMemo } from 'react';
- import { Icon } from '@iconify/react';
- import { Dropdown, type MenuProps } from 'antd';
- import { useTranslation } from 'react-i18next';
- import logoUnion from '@/assets/iconify/multi-color/logo-union.svg';
- import chevronDownIcon from '@/assets/iconify/single-color/chevron-down.svg';
- import closeIcon from '@/assets/iconify/single-color/close.svg';
- import menuIcon from '@/assets/iconify/single-color/menu.svg';
- import { LanguageSwitch } from '@/components/LanguageSwitch';
- import { useAuth } from '@/hooks/useAuth';
- import { useLoginDialog } from '@/hooks/useLoginDialog';
- import { useResponsive } from '@/hooks/useSize';
- import type { NavMenuItem } from '@/utils/navUtils';
- import { useAction } from './useAction';
- import { useService } from './useService';
- const Topbar = memo(() => {
- const { t } = useTranslation();
- const { isMobile } = useResponsive();
- const { menuItems, isActive, getMenuItemLabel } = useService();
- const { isLoggedIn } = useAuth();
- const openLoginDialog = useLoginDialog();
- const {
- menuContainerRef,
- loginButtonRef,
- languageButtonRef,
- isMobileMenuOpen,
- isMobileMenuClosing,
- isOverflowMenuOpen,
- visibleMenuItems,
- overflowMenuItems,
- handleMenuClick,
- toggleMobileMenu,
- closeMobileMenu,
- handleMenuAnimationEnd,
- setOverflowMenuOpen,
- setMenuItemRef,
- } = useAction({ menuItems, isMobile, isLoggedIn });
- const overflowMenuProps: MenuProps = useMemo(
- () => ({
- items: overflowMenuItems.map((item: NavMenuItem) => ({
- key: item.name,
- label: getMenuItemLabel(item),
- onClick: () => handleMenuClick(item.path),
- })),
- }),
- [overflowMenuItems, getMenuItemLabel, handleMenuClick]
- );
- return (
- <Fragment>
- <header className="fixed top-0 start-0 end-0 z-50 bg-black/90 border-b border-white/10 backdrop-blur-sm">
- <div className="flex items-center justify-between px-5 sm:px-6 lg:px-20 py-5 max-w-[1440px] mx-auto">
- {/* Logo */}
- <div className="flex-shrink-0 flex items-center gap-3">
- <Icon icon={logoUnion} className="w-8 h-8" />
- <h1 className="text-2xl font-bold italic text-white leading-none font-[REM] tracking-wide">
- {t('components.topbar.logo')}
- </h1>
- </div>
- {/* Desktop Menu */}
- {!isMobile && (
- <nav
- ref={menuContainerRef}
- className="flex-1 flex items-center justify-end gap-2.5 ms-8 min-w-0"
- >
- <div className="flex items-center gap-2.5 min-w-0">
- {visibleMenuItems.map((item: NavMenuItem) => {
- const active = isActive(item.path);
- return (
- <button
- key={item.name}
- ref={setMenuItemRef(item.name)}
- onClick={() => handleMenuClick(item.path)}
- className={`px-2.5 h-10 flex items-center justify-center transition-colors text-base leading-6 border-none bg-transparent whitespace-nowrap ${
- active
- ? 'font-bold text-[#0FA4E9] border-b-2 border-[#0FA4E9]'
- : 'font-normal text-white/80 hover:text-white'
- }`}
- >
- {getMenuItemLabel(item)}
- </button>
- );
- })}
- {overflowMenuItems.length > 0 && (
- <Dropdown
- menu={overflowMenuProps}
- open={isOverflowMenuOpen}
- onOpenChange={setOverflowMenuOpen}
- overlayClassName="topbar-overflow-menu"
- trigger={['click']}
- placement="bottomRight"
- >
- <button
- type="button"
- className="px-2.5 h-10 flex items-center justify-center transition-colors text-base leading-6 border-none bg-transparent text-white/80 hover:text-white gap-1 whitespace-nowrap"
- >
- <span>...</span>
- <Icon
- icon={chevronDownIcon}
- className={`w-4 h-4 transition-transform ${
- isOverflowMenuOpen ? 'rotate-180' : ''
- }`}
- />
- </button>
- </Dropdown>
- )}
- </div>
- <div ref={languageButtonRef}>
- <LanguageSwitch className="px-2.5 h-10 flex items-center justify-center transition-colors text-base leading-6 border-none bg-transparent font-normal text-white/80 hover:text-white whitespace-nowrap cursor-pointer" />
- </div>
- {!isLoggedIn && (
- <button
- ref={loginButtonRef}
- type="button"
- onClick={() => openLoginDialog()}
- className="ms-2 px-4 h-10 rounded-full bg-[#0FA4E9] text-white text-base font-normal border-none hover:bg-[#0d93d1] transition-colors whitespace-nowrap"
- >
- {t('components.topbar.login')}
- </button>
- )}
- </nav>
- )}
- {/* Mobile Menu Button */}
- {isMobile && (
- <button
- type="button"
- onClick={toggleMobileMenu}
- className="p-2 rounded-xl bg-white/20 border-none text-white outline-none focus:outline-none [-webkit-tap-highlight-color:transparent] transition-colors"
- aria-label={isMobileMenuOpen ? 'Close menu' : 'Open menu'}
- >
- <Icon
- icon={isMobileMenuOpen ? closeIcon : menuIcon}
- className="w-6 h-6"
- />
- </button>
- )}
- </div>
- </header>
- {/* Mobile Expanded Menu */}
- {isMobile && (isMobileMenuOpen || isMobileMenuClosing) && (
- <>
- <div
- className="fixed inset-0 bg-black/40 backdrop-blur-sm z-40 top-[81px]"
- onClick={closeMobileMenu}
- />
- <nav
- className={`fixed end-5 top-[81px] z-50 border border-white/[0.35] bg-black/85 rounded-lg origin-top ${
- isMobileMenuClosing ? 'animate-collapse-up' : 'animate-expand-down'
- }`}
- onAnimationEnd={handleMenuAnimationEnd}
- >
- <div className="flex flex-col items-end gap-4 p-4">
- {menuItems.map((item: NavMenuItem) => {
- const active = isActive(item.path);
- return (
- <button
- key={item.name}
- onClick={() => handleMenuClick(item.path)}
- className={`text-base leading-[1.5] transition-colors border-none bg-transparent whitespace-nowrap ${
- active
- ? 'font-bold text-white'
- : 'font-normal text-[#999] hover:text-white'
- }`}
- >
- {getMenuItemLabel(item)}
- </button>
- );
- })}
- <LanguageSwitch className="text-base leading-[1.5] font-normal text-[#999] hover:text-white transition-colors border-none bg-transparent whitespace-nowrap cursor-pointer" />
- {!isLoggedIn && (
- <button
- type="button"
- onClick={() => {
- closeMobileMenu();
- openLoginDialog();
- }}
- className="text-base leading-[1.5] font-normal text-[#999] hover:text-white transition-colors border-none bg-transparent whitespace-nowrap"
- >
- {t('components.topbar.login')}
- </button>
- )}
- </div>
- </nav>
- </>
- )}
- </Fragment>
- );
- });
- Topbar.displayName = 'Topbar';
- export default Topbar;
|