x
This commit is contained in:
1 parent
b8873f88b0
commit
807c43ece5
9 files changed
+624
-259
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { MegaMenuPayload } from '../store/menuStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,57 +15,97 @@ const getLocalizedText = (textObj: Record<string, string> | string | undefined,
|
||||
|
||||
export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const { i18n } = useTranslation();
|
||||
const lang = i18n.language ? i18n.language.substring(0, 2) : 'en';
|
||||
|
||||
// Open: cancel any pending close, then open this menu
|
||||
const handleOpen = (id: string) => {
|
||||
if (closeTimer.current) clearTimeout(closeTimer.current);
|
||||
setOpenId(id);
|
||||
};
|
||||
|
||||
// Close: wait 150ms — if mouse enters the dropdown in time, the close is cancelled
|
||||
const handleClose = () => {
|
||||
closeTimer.current = setTimeout(() => setOpenId(null), 150);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="hidden md:flex space-x-7 text-sm font-medium tracking-wide uppercase">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="relative group"
|
||||
onMouseEnter={() => setOpenId(item.id)}
|
||||
onMouseLeave={() => setOpenId(null)}
|
||||
>
|
||||
<Link
|
||||
to={item.url || '#'}
|
||||
className="flex items-center gap-1 text-spa-stone hover:text-spa-forest transition-colors py-2"
|
||||
{items.map((item) => {
|
||||
const uniqueId = item.nodeId || item.id || `menu-${item.url}`;
|
||||
const isOpen = openId === uniqueId;
|
||||
const hasColumns = item.columns && item.columns.length > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={uniqueId}
|
||||
className="relative"
|
||||
onMouseEnter={() => handleOpen(uniqueId)}
|
||||
onMouseLeave={handleClose}
|
||||
>
|
||||
{getLocalizedText(item.label, lang)}
|
||||
{item.columns && item.columns.length > 0 && (
|
||||
<svg className="w-3 h-3 opacity-50 transition-transform duration-300 group-hover:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
{item.columns && item.columns.length > 0 && openId === item.id && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 pt-4 w-auto min-w-[320px] opacity-100 pointer-events-auto transition-all duration-300 z-50">
|
||||
<div className="bg-white/95 backdrop-blur-xl border border-black/5 rounded-xl overflow-hidden shadow-2xl p-5 flex gap-8">
|
||||
{item.columns.map((col, idx) => (
|
||||
<div key={idx} className="flex-1 min-w-[140px]">
|
||||
<h4 className="text-[10px] uppercase tracking-widest text-spa-stone/60 font-semibold mb-3">
|
||||
{getLocalizedText(col.title, lang)}
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{col.links.map((link, lIdx) => (
|
||||
<li key={lIdx}>
|
||||
<Link
|
||||
to={link.url || '#'}
|
||||
className="text-xs uppercase tracking-widest text-spa-stone hover:text-spa-forest hover:bg-spa-sage px-3 py-2 rounded-lg block transition-colors"
|
||||
>
|
||||
{getLocalizedText(link.label, lang)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* Nav trigger */}
|
||||
<Link
|
||||
to={item.url || '#'}
|
||||
className={`flex items-center gap-1 transition-colors py-2 border-b ${
|
||||
isOpen
|
||||
? 'text-spa-forest border-spa-forest'
|
||||
: 'text-spa-stone border-transparent hover:text-spa-forest hover:border-spa-forest'
|
||||
}`}
|
||||
>
|
||||
{getLocalizedText(item.label, lang)}
|
||||
{hasColumns && (
|
||||
<svg
|
||||
className={`w-3 h-3 opacity-50 transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
{/* Dropdown panel — fixed, full-width.
|
||||
It handles its own onMouseEnter/Leave so the delay timer is
|
||||
cancelled the moment the mouse enters the panel. */}
|
||||
{hasColumns && (
|
||||
<div
|
||||
className={`fixed left-0 right-0 z-50 transition-opacity duration-200 ${
|
||||
isOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'
|
||||
}`}
|
||||
style={{ top: 'var(--navbar-bottom, 56px)' }}
|
||||
onMouseEnter={() => handleOpen(uniqueId)}
|
||||
onMouseLeave={handleClose}
|
||||
>
|
||||
<div className="w-full bg-[#F9F8F5] border-b border-black/5 shadow-sm cursor-default">
|
||||
<div className="flex justify-center gap-16 py-10 max-w-7xl mx-auto px-6">
|
||||
{item.columns!.map((col, idx) => (
|
||||
<div key={idx} className="flex flex-col min-w-[140px] text-left">
|
||||
<h4 className="text-[10px] uppercase tracking-widest text-spa-stone font-bold mb-4">
|
||||
{getLocalizedText(col.title, lang)}
|
||||
</h4>
|
||||
<ul className="space-y-3">
|
||||
{col.links.map((link, lIdx) => (
|
||||
<li key={lIdx}>
|
||||
<Link
|
||||
to={link.url || '#'}
|
||||
className="text-sm font-normal normal-case tracking-normal text-spa-stone hover:text-spa-forest hover:opacity-80 block transition-colors"
|
||||
>
|
||||
{getLocalizedText(link.label, lang)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+148
-116
@@ -1,5 +1,5 @@
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { type Category } from '../types'
|
||||
import { ShoppingBag, Menu as MenuIcon, X, Heart, Globe, Check } from 'lucide-react'
|
||||
import { useCartStore } from '../store/cartStore'
|
||||
@@ -35,6 +35,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
|
||||
const { t, i18n } = useTranslation()
|
||||
|
||||
const { megaMenu, fetchMegaMenu } = useMenuStore()
|
||||
const navRef = useRef<HTMLElement>(null)
|
||||
|
||||
const LANGUAGES = [
|
||||
{ code: 'th', label: 'ไทย', short: 'TH' },
|
||||
@@ -73,6 +74,23 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
|
||||
}
|
||||
}, [fetchMegaMenu])
|
||||
|
||||
// Keep --navbar-bottom in sync so the fixed mega-menu dropdown sits flush below the navbar
|
||||
useEffect(() => {
|
||||
const updateNavBottom = () => {
|
||||
if (navRef.current) {
|
||||
const bottom = navRef.current.getBoundingClientRect().bottom
|
||||
document.documentElement.style.setProperty('--navbar-bottom', `${bottom}px`)
|
||||
}
|
||||
}
|
||||
updateNavBottom()
|
||||
window.addEventListener('scroll', updateNavBottom, { passive: true })
|
||||
window.addEventListener('resize', updateNavBottom)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', updateNavBottom)
|
||||
window.removeEventListener('resize', updateNavBottom)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => { setMobileOpen(false) }, [navigate])
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = (searchOpen || mobileOpen || langSheetOpen) ? 'hidden' : ''
|
||||
@@ -98,131 +116,145 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
className={`fixed ${finalTopClassName} w-full z-50 transition-all duration-300 px-4 md:px-6 py-3.5 md:py-4 flex justify-between items-center bg-white/80 backdrop-blur-xl border-b border-black/5 ${
|
||||
ref={navRef}
|
||||
className={`fixed ${finalTopClassName} w-full z-50 transition-all duration-300 border-b border-black/5 ${
|
||||
scrolled ? 'shadow-lg shadow-black/5' : 'shadow-none'
|
||||
}`}
|
||||
} bg-white/80 backdrop-blur-xl md:bg-[#F9F8F5] md:backdrop-blur-none px-4 md:px-6 py-3.5 md:py-4`}
|
||||
>
|
||||
<Link to="/" onClick={() => setMobileOpen(false)} className="text-xl md:text-2xl font-bold tracking-widest text-spa-forest uppercase italic shrink-0">
|
||||
HomAroma <span className="hidden md:inline text-spa-stone text-xs md:text-sm not-italic font-light ml-1">Essential</span>
|
||||
</Link>
|
||||
{/* Single-row layout: Logo (left) | Menu (center, desktop) | Actions (right) */}
|
||||
<div className="w-full flex items-center justify-between gap-4">
|
||||
|
||||
{isDesktop && <DesktopMegaMenu items={megaMenu} />}
|
||||
|
||||
{/* ── ACTIONS ── */}
|
||||
<div className="flex items-center gap-1.5 md:gap-3">
|
||||
<button
|
||||
onClick={() => { setSearchOpen(true); setMobileOpen(false); }}
|
||||
className="p-1.5 md:p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
aria-label="Search"
|
||||
>
|
||||
<svg className="w-4 h-4 md:w-4 md:h-4 group-hover:text-spa-forest transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Link
|
||||
to="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="relative p-1.5 md:p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="w-4 h-4 group-hover:text-rose-500 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => { toggleCart(true); setMobileOpen(false); }}
|
||||
className="relative p-1.5 md:p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4 group-hover:text-spa-forest transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{/* Left: Logo */}
|
||||
<div className="flex items-center shrink-0">
|
||||
<Link to="/" onClick={() => setMobileOpen(false)} className="text-xl md:text-2xl font-bold tracking-widest text-spa-forest uppercase italic shrink-0">
|
||||
HomAroma <span className="hidden md:inline text-spa-stone text-xs md:text-sm not-italic font-light ml-1">Essential</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Center: Desktop mega menu */}
|
||||
{isDesktop && (
|
||||
<>
|
||||
<div className="relative group flex items-center">
|
||||
<button className="px-3 py-2 rounded-full text-[10px] font-bold transition-all text-spa-forest border border-spa-forest/30 hover:bg-spa-sage bg-white flex items-center gap-1.5 uppercase tracking-wider">
|
||||
<Globe className="w-3.5 h-3.5" strokeWidth={1.5} />
|
||||
{LANGUAGES.find(l => i18n.language.startsWith(l.code))?.short || 'TH'}
|
||||
</button>
|
||||
<div className="absolute top-full right-0 pt-2 w-36 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300">
|
||||
<div className="bg-white/95 backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-2xl p-1.5 space-y-0.5">
|
||||
{LANGUAGES.map(lang => {
|
||||
const isActive = i18n.language.startsWith(lang.code)
|
||||
return (
|
||||
<button
|
||||
key={lang.code}
|
||||
onClick={() => changeLanguage(lang.code)}
|
||||
className={`w-full flex items-center justify-between px-3 py-2.5 text-xs rounded-lg transition-colors font-medium ${isActive ? 'text-spa-forest bg-spa-sage/60 font-bold' : 'text-spa-stone hover:bg-spa-light hover:text-spa-forest'}`}
|
||||
>
|
||||
<span>{lang.label}</span>
|
||||
{isActive && <Check className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex justify-center">
|
||||
<DesktopMegaMenu items={megaMenu} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{customer ? (
|
||||
<div className="relative group flex items-center ml-1">
|
||||
<Link to="/profile">
|
||||
<img
|
||||
src={customer.pictureUrl || `https://ui-avatars.com/api/?name=${customer.displayName}`}
|
||||
alt="Profile"
|
||||
className="w-9 h-9 rounded-full border-2 border-spa-forest/30 cursor-pointer object-cover animate-fade-in"
|
||||
/>
|
||||
</Link>
|
||||
<div className="absolute top-full right-0 pt-3 w-36 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300">
|
||||
<div className="bg-white/95 backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-xl p-1">
|
||||
<div className="px-3 py-2 border-b border-spa-stone/10 mb-1">
|
||||
<p className="text-[10px] text-spa-stone/60 uppercase tracking-widest leading-tight truncate">{t('nav.signedInAs', 'Signed in as')}</p>
|
||||
<p className="text-xs text-spa-forest font-bold truncate">{customer.displayName}</p>
|
||||
</div>
|
||||
<Link
|
||||
to="/profile"
|
||||
className="block px-3 py-2 text-xs uppercase tracking-widest text-spa-stone hover:text-spa-forest hover:bg-spa-light rounded-lg transition-colors font-bold"
|
||||
>
|
||||
My Profile
|
||||
</Link>
|
||||
<button
|
||||
onClick={logoutLiff}
|
||||
className="w-full text-left px-3 py-2 text-xs uppercase tracking-widest text-rose-500 hover:text-rose-600 hover:bg-rose-50 rounded-lg transition-colors font-bold"
|
||||
>
|
||||
{t('nav.logout', 'Logout')}
|
||||
</button>
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-1.5 md:gap-2 shrink-0">
|
||||
{/* Search — desktop & mobile */}
|
||||
<button
|
||||
onClick={() => { setSearchOpen(true); setMobileOpen(false); }}
|
||||
className="p-1.5 md:p-2 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
aria-label="Search"
|
||||
>
|
||||
<svg className="w-4 h-4 group-hover:text-spa-forest transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Link
|
||||
to="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="relative p-1.5 md:p-2 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="w-4 h-4 group-hover:text-rose-500 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => { toggleCart(true); setMobileOpen(false); }}
|
||||
className="relative p-1.5 md:p-2 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4 group-hover:text-spa-forest transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isDesktop && (
|
||||
<>
|
||||
<div className="relative group flex items-center">
|
||||
<button className="px-3 py-2 rounded-full text-[10px] font-bold transition-all text-spa-forest border border-spa-forest/30 hover:bg-spa-sage bg-white flex items-center gap-1.5 uppercase tracking-wider">
|
||||
<Globe className="w-3.5 h-3.5" strokeWidth={1.5} />
|
||||
{LANGUAGES.find(l => i18n.language.startsWith(l.code))?.short || 'TH'}
|
||||
</button>
|
||||
<div className="absolute top-full right-0 pt-2 w-36 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300">
|
||||
<div className="bg-white/95 backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-2xl p-1.5 space-y-0.5">
|
||||
{LANGUAGES.map(lang => {
|
||||
const isActive = i18n.language.startsWith(lang.code)
|
||||
return (
|
||||
<button
|
||||
key={lang.code}
|
||||
onClick={() => changeLanguage(lang.code)}
|
||||
className={`w-full flex items-center justify-between px-3 py-2.5 text-xs rounded-lg transition-colors font-medium ${isActive ? 'text-spa-forest bg-spa-sage/60 font-bold' : 'text-spa-stone hover:bg-spa-light hover:text-spa-forest'}`}
|
||||
>
|
||||
<span>{lang.label}</span>
|
||||
{isActive && <Check className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={loginLiff}
|
||||
className="ml-2 px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest bg-[#00B900] text-white hover:bg-[#009900] transition-all duration-300 shadow-sm"
|
||||
>
|
||||
{t('nav.login', 'LINE Login')}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isDesktop && (
|
||||
<button
|
||||
onClick={() => setMobileOpen(prev => !prev)}
|
||||
className="p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
||||
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
{mobileOpen ? <X className="w-5 h-5" /> : <MenuIcon className="w-5 h-5" />}
|
||||
</button>
|
||||
)}
|
||||
{customer ? (
|
||||
<div className="relative group flex items-center ml-1">
|
||||
<Link to="/profile">
|
||||
<img
|
||||
src={customer.pictureUrl || `https://ui-avatars.com/api/?name=${customer.displayName}`}
|
||||
alt="Profile"
|
||||
className="w-9 h-9 rounded-full border-2 border-spa-forest/30 cursor-pointer object-cover animate-fade-in"
|
||||
/>
|
||||
</Link>
|
||||
<div className="absolute top-full right-0 pt-3 w-36 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300">
|
||||
<div className="bg-white/95 backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-xl p-1">
|
||||
<div className="px-3 py-2 border-b border-spa-stone/10 mb-1">
|
||||
<p className="text-[10px] text-spa-stone/60 uppercase tracking-widest leading-tight truncate">{t('nav.signedInAs', 'Signed in as')}</p>
|
||||
<p className="text-xs text-spa-forest font-bold truncate">{customer.displayName}</p>
|
||||
</div>
|
||||
<Link
|
||||
to="/profile"
|
||||
className="block px-3 py-2 text-xs uppercase tracking-widest text-spa-stone hover:text-spa-forest hover:bg-spa-light rounded-lg transition-colors font-bold"
|
||||
>
|
||||
My Profile
|
||||
</Link>
|
||||
<button
|
||||
onClick={logoutLiff}
|
||||
className="w-full text-left px-3 py-2 text-xs uppercase tracking-widest text-rose-500 hover:text-rose-600 hover:bg-rose-50 rounded-lg transition-colors font-bold"
|
||||
>
|
||||
{t('nav.logout', 'Logout')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={loginLiff}
|
||||
className="ml-2 px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest bg-[#00B900] text-white hover:bg-[#009900] transition-all duration-300 shadow-sm"
|
||||
>
|
||||
{t('nav.login', 'LINE Login')}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isDesktop && (
|
||||
<button
|
||||
onClick={() => setMobileOpen(prev => !prev)}
|
||||
className="p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
||||
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
{mobileOpen ? <X className="w-5 h-5" /> : <MenuIcon className="w-5 h-5" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
Reference in new issue
Block a user