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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user