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 ; } 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 ( { setActiveTabByMenu(prev => ({ ...prev, [uniqueId]: colIdx })); setHoveredLinkImage(null); }} className={`flex w-full items-center justify-between rounded-lg px-3.5 py-2.5 text-left transition-all ${ isSelected ? 'bg-pop-primary/10 text-pop-primary font-bold shadow-sm' : 'text-pop-black/80 hover:bg-pop-card hover:text-pop-black font-medium' }`} > {getLocalizedText(column.title, lang)} ); })} {/* 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} )} )} ); })} ); }
{featuredTitle}
{featuredSubtitle}
{featuredConfig.price}