|
|
@@ -16,16 +16,19 @@ import { useService } from './useService';
|
|
|
const Topbar = memo(() => {
|
|
|
const { t } = useTranslation();
|
|
|
const { isMobile } = useResponsive();
|
|
|
+ const isRtl = t('DIR') === 'rtl';
|
|
|
const { menuItems, isActive, getMenuItemLabel } = useService();
|
|
|
const {
|
|
|
menuContainerRef,
|
|
|
isMobileMenuOpen,
|
|
|
+ isMobileMenuClosing,
|
|
|
isOverflowMenuOpen,
|
|
|
visibleMenuItems,
|
|
|
overflowMenuItems,
|
|
|
handleMenuClick,
|
|
|
toggleMobileMenu,
|
|
|
closeMobileMenu,
|
|
|
+ handleMenuAnimationEnd,
|
|
|
toggleOverflowMenu,
|
|
|
setMenuItemRef,
|
|
|
} = useAction({ menuItems, isMobile });
|
|
|
@@ -109,8 +112,9 @@ const Topbar = memo(() => {
|
|
|
{/* Mobile Menu Button */}
|
|
|
{isMobile && (
|
|
|
<button
|
|
|
+ type="button"
|
|
|
onClick={toggleMobileMenu}
|
|
|
- className="p-2 rounded-lg bg-transparent border-none text-white"
|
|
|
+ className="p-2 rounded-lg bg-transparent border-none text-white outline-none focus:outline-none focus:bg-transparent active:bg-transparent hover:text-[#0EA5E9]/80 active:text-[#0EA5E9]/60 [-webkit-tap-highlight-color:transparent] transition-colors"
|
|
|
aria-label={isMobileMenuOpen ? 'Close menu' : 'Open menu'}
|
|
|
>
|
|
|
<Icon
|
|
|
@@ -123,15 +127,26 @@ const Topbar = memo(() => {
|
|
|
</header>
|
|
|
|
|
|
{/* Mobile Expanded Menu (Sidebar) - 移到 header 外部 */}
|
|
|
- {isMobile && isMobileMenuOpen && (
|
|
|
+ {isMobile && (isMobileMenuOpen || isMobileMenuClosing) && (
|
|
|
<>
|
|
|
- {/* 遮罩层 */}
|
|
|
+ {/* 遮罩层(毛玻璃) */}
|
|
|
<div
|
|
|
- className="fixed inset-0 bg-black/50 z-40 top-[81px]"
|
|
|
+ className="fixed inset-0 bg-black/40 backdrop-blur-sm z-40 top-[81px]"
|
|
|
onClick={closeMobileMenu}
|
|
|
/>
|
|
|
- {/* 侧边栏菜单 */}
|
|
|
- <nav className="fixed end-0 top-[81px] bottom-0 w-[250px] bg-black/80 backdrop-blur-[4px] z-50">
|
|
|
+ {/* 侧边栏菜单:LTR 自右向左进入/向右退出,RTL 自左向右进入/向左退出 */}
|
|
|
+ <nav
|
|
|
+ className={`fixed end-0 top-[81px] bottom-0 w-[250px] bg-black/80 backdrop-blur-[4px] z-50 ${
|
|
|
+ isMobileMenuClosing
|
|
|
+ ? isRtl
|
|
|
+ ? 'animate-slide-out-to-start'
|
|
|
+ : 'animate-slide-out-to-end'
|
|
|
+ : isRtl
|
|
|
+ ? 'animate-slide-in-from-start'
|
|
|
+ : 'animate-slide-in-from-end'
|
|
|
+ }`}
|
|
|
+ onAnimationEnd={handleMenuAnimationEnd}
|
|
|
+ >
|
|
|
<div className="h-full px-[30px] pt-[30px] flex flex-col gap-[14px]">
|
|
|
{menuItems.map((item: NavMenuItem) => {
|
|
|
const active = isActive(item.path);
|