import { useState, useRef, useEffect } from 'react'; import { Link, useLocation } from 'react-router-dom'; import type { MegaMenuPayload, MenuColumn } from '../store/menuStore'; import { useTranslation } from 'react-i18next'; import { ArrowRight, ChevronRight, Star, Sparkles, Box, } from 'lucide-react'; import { ICON_REGISTRY, type AvailableIcons } from '../utils/iconRegistry'; function DynamicIcon({ iconName, defaultIcon: DefaultIcon, className, strokeWidth }: { iconName?: string, defaultIcon: any, className?: string, strokeWidth?: number }) { const IconComponent = (iconName && ICON_REGISTRY[iconName as AvailableIcons]) ? ICON_REGISTRY[iconName as AvailableIcons] : DefaultIcon; return ; } interface DesktopMegaMenuProps { items: MegaMenuPayload[]; } const getLocalizedText = (textObj: Record | string | undefined, lang: string) => { if (typeof textObj === 'string') return textObj; if (!textObj) return ''; return textObj[lang] || textObj.en || textObj.th || Object.values(textObj)[0] || ''; }; const MAX_TOP_LEVEL_LINKS = 5; const MAX_INLINE_CHILD_LINKS = 3; const getMoreLabel = (count: number, lang: string) => { if (lang === 'th') return `+ อีก ${count} รายการ`; if (lang === 'ja') return `+ 他${count}件`; if (lang === 'zh') return `+ 还有 ${count} 项`; if (lang === 'vi') return `+ ${count} mục khác`; if (lang === 'ko') return `+ ${count}개 더`; return `+ ${count} more`; }; // Image block shared between Root and Hover areas function MenuImage({ src, alt, placeholder, className, style }: { src?: string | null; alt: string; placeholder?: string; className?: string; style?: React.CSSProperties }) { if (!src) { if (!placeholder) return null; return (
{placeholder}
); } return {alt}; } export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) { const [openId, setOpenId] = useState(null); const [hoveredLinkImage, setHoveredLinkImage] = useState(null); const [activeTabByMenu, setActiveTabByMenu] = useState>({}); const closeTimer = useRef | null>(null); const location = useLocation(); const { i18n } = useTranslation(); const lang = i18n.language ? i18n.language.substring(0, 2) : 'en'; // Automatically close mega menu when route changes useEffect(() => { if (closeTimer.current) clearTimeout(closeTimer.current); setOpenId(null); setHoveredLinkImage(null); }, [location.pathname, location.search]); // 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); setHoveredLinkImage(null); }, 150); }; const handleLinkClick = () => { if (closeTimer.current) clearTimeout(closeTimer.current); setOpenId(null); setHoveredLinkImage(null); }; const getFirstImageLink = (columns: MenuColumn[] = []) => { for (const column of columns) { const found = column.links.find((link) => link.imageUrl); if (found) return found; } return columns[0]?.links[0]; }; return (
{items.map((item, index) => { const uniqueId = item.nodeId || item.id || `menu-${item.url || index}`; const isOpen = openId === uniqueId; const hasColumns = item.columns && item.columns.length > 0; const layout = item.layout || 'columns'; const isClassic = layout === 'classic'; const isTabbed = layout === 'tabbed'; const isVisualCards = layout === 'visual-cards'; const configuredCols = item.layoutConfig?.columnCount || (item.columns?.length ? Math.min(Math.max(item.columns.length, 2), 4) : 2); const visibleColumns = item.columns?.slice(0, configuredCols) ?? []; const featuredLink = getFirstImageLink(item.columns); const featuredConfig = item.featured; const showFeatured = (item.layoutConfig?.showFeatured !== false) && (featuredConfig?.enabled !== false) && !isClassic && !isVisualCards && !isTabbed && ( !!(featuredConfig?.imageUrl || featuredConfig?.title || featuredConfig?.sectionTitle || featuredLink?.imageUrl) ); const configuredImage = featuredConfig?.imageUrl || null; const featuredImage = hoveredLinkImage ?? configuredImage ?? featuredLink?.imageUrl ?? item.rootImageUrl; const focalPoint = featuredConfig?.focalPoint; const objectPosition = focalPoint ? `${focalPoint.x} ${focalPoint.y}` : 'center'; const featuredHeading = getLocalizedText(featuredConfig?.sectionTitle, lang) || 'Featured'; const featuredBadge = getLocalizedText(featuredConfig?.badge, lang) || 'Recommended'; const featuredTitle = getLocalizedText(featuredConfig?.title, lang) || getLocalizedText(featuredLink?.label, lang) || getLocalizedText(item.label, lang); const featuredSubtitle = getLocalizedText(featuredConfig?.subtitle, lang) || (item.columns?.[0] ? getLocalizedText(item.columns[0].title, lang) : getLocalizedText(item.label, lang)); const featuredHref = featuredConfig?.url || featuredLink?.url || item.url || '#'; const featuredCta = getLocalizedText(featuredConfig?.ctaLabel, lang) || 'View details'; const getMegaContainerClass = (cols: number, hasFeatured: boolean) => { if (hasFeatured) { if (cols >= 4) return 'w-[min(100vw-2rem,1260px)]'; if (cols === 3) return 'w-[min(100vw-2rem,1120px)]'; return 'w-[min(100vw-2rem,980px)]'; } if (cols >= 4) return 'w-[min(100vw-2rem,1180px)]'; if (cols === 3) return 'w-[min(100vw-2rem,960px)]'; return 'w-[min(100vw-2rem,720px)]'; }; const getMegaGridColsClass = (cols: number, hasFeatured: boolean) => { if (hasFeatured) { if (cols >= 4) return 'grid-cols-[1fr_1fr_1fr_1fr_260px]'; if (cols === 3) return 'grid-cols-[1fr_1fr_1fr_270px]'; return 'grid-cols-[1fr_1fr_270px]'; } if (cols >= 4) return 'grid-cols-4'; if (cols === 3) return 'grid-cols-3'; return 'grid-cols-2'; }; return (
handleOpen(uniqueId)} onMouseLeave={handleClose} > {/* Nav trigger */} {getLocalizedText(item.label, lang)} {hasColumns && ( )} {hasColumns && isClassic && (
handleOpen(uniqueId)} onMouseLeave={handleClose} >
{item.columns?.map((col, colIdx) => (
0 ? 'mt-2 border-t border-pop-black/10 pt-2' : ''}> {getLocalizedText(col.title, lang) && item.columns && item.columns.length > 1 && (
{getLocalizedText(col.title, lang)}
)}
    {col.links.map((link, linkIdx) => (
  • {link.icon && ( )} {getLocalizedText(link.label, lang)} {link.links?.length ? ( ) : null} {link.links && link.links.length > 0 && (
      {link.links.map((child, childIdx) => (
    • {getLocalizedText(child.label, lang)}
    • ))}
    )}
  • ))}
))}
)} {hasColumns && isTabbed && (
handleOpen(uniqueId)} onMouseLeave={handleClose} >
{/* Left Category Tabs Sidebar */}
{lang === 'th' ? 'หมวดหมู่หลัก' : 'Categories'}
{item.columns?.map((column, colIdx) => { const activeIdx = activeTabByMenu[uniqueId] ?? 0; const isSelected = activeIdx === colIdx; return ( ); })}
{/* Right Content Panel for Selected Tab */} {(() => { const activeIdx = activeTabByMenu[uniqueId] ?? 0; const activeCol = item.columns?.[activeIdx] || item.columns?.[0]; if (!activeCol) return null; return (

{getLocalizedText(activeCol.title, lang)}

{lang === 'th' ? 'ดูรายการทั้งหมด' : 'View all'}
{activeCol.links.map((link, lIdx) => (
setHoveredLinkImage(link.imageUrl || null)} onMouseLeave={() => setHoveredLinkImage(null)} > {getLocalizedText(link.label, lang)} {link.links && link.links.length > 0 && (
{link.links.slice(0, 3).map((child, cIdx) => ( {getLocalizedText(child.label, lang)} ))}
)}
))}
{item.columns?.length || 0} {lang === 'th' ? 'หมวดหมู่ย่อย' : 'categories'} {getLocalizedText(item.label, lang)}
); })()}
)} {hasColumns && isVisualCards && (
handleOpen(uniqueId)} onMouseLeave={handleClose} >

{getLocalizedText(item.label, lang)}

{lang === 'th' ? 'ดูทั้งหมด' : 'Explore all'}
{item.columns?.slice(0, 4).map((column, colIdx) => { const cardImg = column.iconUrl || column.links?.[0]?.imageUrl || configuredImage || item.rootImageUrl; const cardHref = column.links?.[0]?.url || item.url || '#'; const cardTitle = getLocalizedText(column.title, lang); const firstSubLink = column.links?.[0]; const subText = firstSubLink ? getLocalizedText(firstSubLink.label, lang) : null; return (
{cardTitle} {subText && ( {subText} )}
); })}
)} {hasColumns && !isClassic && !isTabbed && !isVisualCards && (
handleOpen(uniqueId)} onMouseLeave={handleClose} >
{visibleColumns.map((col, idx) => (
0 ? 'border-l border-pop-black/10 pl-8' : ''}`} >

{getLocalizedText(col.title, lang)}

    {col.links.slice(0, MAX_TOP_LEVEL_LINKS).map((link, lIdx) => { const hasNestedLinks = !!link.links?.length; const visibleChildLinks = link.links?.slice(0, MAX_INLINE_CHILD_LINKS) ?? []; const hiddenChildCount = Math.max((link.links?.length ?? 0) - MAX_INLINE_CHILD_LINKS, 0); const moreChildHref = link.url || link.links?.[MAX_INLINE_CHILD_LINKS]?.url || link.links?.[0]?.url || '#'; return (
  • setHoveredLinkImage(link.imageUrl || null)} onMouseLeave={() => setHoveredLinkImage(null)} > {getLocalizedText(link.label, lang)} {getLocalizedText(col.title, lang)} {hasNestedLinks && (
      {visibleChildLinks.map((childLink, childIndex) => (
    • setHoveredLinkImage(childLink.imageUrl || link.imageUrl || null)} > {getLocalizedText(childLink.label, lang)}
    • ))} {hiddenChildCount > 0 && (
    • {getMoreLabel(hiddenChildCount, lang)}
    • )}
    )}
  • ); })}
{getLocalizedText(item.label, lang)}
))} {showFeatured && (

{featuredHeading}

setHoveredLinkImage(configuredImage ?? featuredLink?.imageUrl ?? null)} onMouseLeave={() => setHoveredLinkImage(null)} >
{featuredBadge && ( {featuredBadge} )}

{featuredTitle}

{featuredSubtitle && (

{featuredSubtitle}

)} {featuredConfig?.price && (

{featuredConfig.price}

)}
{featuredCta}
)}
)}
); })}
); }