x
This commit is contained in:
1 parent
3965f4f4da
commit
70877882b0
82 files changed
+4353
-186
No files matched your search
@@ -13,21 +13,41 @@ const getLocalizedText = (textObj: Record<string, string> | string | undefined,
|
||||
return textObj[lang] || textObj['en'] || Object.values(textObj)[0] || '';
|
||||
};
|
||||
|
||||
// Image block shared between Root and Hover areas
|
||||
function MenuImage({ src, alt, placeholder }: { src?: string | null; alt: string; placeholder?: string }) {
|
||||
if (!src) {
|
||||
if (!placeholder) return null;
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="text-pop-black/30 text-xs text-center px-3 leading-relaxed">{placeholder}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <img src={src} alt={alt} className="w-full h-full object-cover" loading="lazy" />;
|
||||
}
|
||||
|
||||
export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
const [hoveredLinkImage, setHoveredLinkImage] = 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
|
||||
// Open: cancel any pending close, reset hover image when switching root menu
|
||||
const handleOpen = (id: string) => {
|
||||
if (closeTimer.current) clearTimeout(closeTimer.current);
|
||||
if (openId !== id) {
|
||||
setHoveredLinkImage(null);
|
||||
}
|
||||
setOpenId(id);
|
||||
};
|
||||
|
||||
// Close: wait 150ms — if mouse enters the dropdown in time, the close is cancelled
|
||||
const handleClose = () => {
|
||||
closeTimer.current = setTimeout(() => setOpenId(null), 150);
|
||||
closeTimer.current = setTimeout(() => {
|
||||
setOpenId(null);
|
||||
setHoveredLinkImage(null);
|
||||
}, 150);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -37,6 +57,11 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
const isOpen = openId === uniqueId;
|
||||
const hasColumns = item.columns && item.columns.length > 0;
|
||||
|
||||
// Determine if any column has an icon OR any link has an image
|
||||
const hasAnyColumnIcon = item.columns?.some(col => !!col.iconUrl);
|
||||
const hasAnyLinkImage = item.columns?.some(col => col.links.some(l => !!l.imageUrl));
|
||||
const showRightPanel = item.rootImageUrl || hasAnyLinkImage;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={uniqueId}
|
||||
@@ -66,9 +91,7 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
)}
|
||||
</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. */}
|
||||
{/* Dropdown panel — fixed, full-width */}
|
||||
{hasColumns && (
|
||||
<div
|
||||
className={`fixed left-0 right-0 z-50 transition-opacity duration-200 ${
|
||||
@@ -79,26 +102,65 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
onMouseLeave={handleClose}
|
||||
>
|
||||
<div className="w-full bg-pop-bg border-b border-pop-black/10 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-pop-black/70 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-pop-black/80 hover:text-pop-black hover:opacity-80 block transition-colors"
|
||||
>
|
||||
{getLocalizedText(link.label, lang)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-start gap-10 py-10 max-w-7xl mx-auto px-6">
|
||||
|
||||
{/* LEFT: Root menu image (1:1) — hidden on smaller screens */}
|
||||
{item.rootImageUrl && (
|
||||
<div className="w-56 h-56 shrink-0 rounded-xl overflow-hidden border border-pop-black/8 hidden lg:block">
|
||||
<MenuImage src={item.rootImageUrl} alt={getLocalizedText(item.label, lang)} />
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
|
||||
{/* CENTER: Columns */}
|
||||
<div className="flex gap-12 flex-1">
|
||||
{item.columns!.map((col, idx) => (
|
||||
<div key={idx} className="flex gap-4 min-w-[160px] text-left items-start">
|
||||
|
||||
{/* Column icon (1:1) — shown only if this column has an iconUrl */}
|
||||
{(hasAnyColumnIcon) && (
|
||||
<div className="w-14 h-14 shrink-0 rounded-lg overflow-hidden bg-pop-black/5 border border-pop-black/8 mt-0.5">
|
||||
<MenuImage
|
||||
src={col.iconUrl}
|
||||
alt={getLocalizedText(col.title, lang)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Title and links */}
|
||||
<div className="flex flex-col">
|
||||
<h4 className="text-[10px] uppercase tracking-widest text-pop-black/60 font-bold mb-3">
|
||||
{getLocalizedText(col.title, lang)}
|
||||
</h4>
|
||||
<ul className="space-y-2.5">
|
||||
{col.links.map((link, lIdx) => (
|
||||
<li key={lIdx}>
|
||||
<Link
|
||||
to={link.url || '#'}
|
||||
className="text-sm font-normal normal-case tracking-normal text-pop-black/75 hover:text-pop-black block transition-colors duration-150"
|
||||
onMouseEnter={() => setHoveredLinkImage(link.imageUrl || null)}
|
||||
onMouseLeave={() => setHoveredLinkImage(null)}
|
||||
>
|
||||
{getLocalizedText(link.label, lang)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Hover preview image (1:1) — shown only if any link has an imageUrl */}
|
||||
{showRightPanel && (
|
||||
<div className="w-56 h-56 shrink-0 rounded-xl overflow-hidden border border-pop-black/8 bg-pop-black/5 hidden lg:flex items-center justify-center">
|
||||
<MenuImage
|
||||
src={hoveredLinkImage ?? item.rootImageUrl}
|
||||
alt="Preview"
|
||||
placeholder="Hover a link to preview"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user