x
This commit is contained in:
1 parent
3965f4f4da
commit
70877882b0
82 files changed
+4353
-186
No files matched your search
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import Link from '../LocalizedLink'
|
||||
import Image from 'next/image'
|
||||
import { useState, useRef } from 'react'
|
||||
import type { MegaMenuPayload } from './menuTypes'
|
||||
import { getLocalizedMenuText } from './menuTypes'
|
||||
@@ -11,17 +12,56 @@ interface DesktopMegaMenuProps {
|
||||
currentPath: string
|
||||
}
|
||||
|
||||
// Render a 1:1 image using next/image for optimization (website uses CDN images)
|
||||
function MenuImage({
|
||||
src,
|
||||
alt,
|
||||
placeholder,
|
||||
className = '',
|
||||
}: {
|
||||
src?: string | null
|
||||
alt: string
|
||||
placeholder?: string
|
||||
className?: string
|
||||
}) {
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
|
||||
if (!src) {
|
||||
if (!placeholder) return null
|
||||
return (
|
||||
<span className="text-text-main/30 text-xs text-center px-3 leading-relaxed">{placeholder}</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
fill
|
||||
sizes="(max-width: 1024px) 0px, 224px"
|
||||
className={`object-cover transition-opacity duration-300 ${loaded ? 'opacity-100' : 'opacity-0'} ${className}`}
|
||||
onLoad={() => setLoaded(true)}
|
||||
loading="lazy"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function DesktopMegaMenu({ items, lang, currentPath }: DesktopMegaMenuProps) {
|
||||
const [openId, setOpenId] = useState<string | null>(null)
|
||||
const [hoveredLinkImage, setHoveredLinkImage] = useState<string | null>(null)
|
||||
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const handleOpen = (id: string) => {
|
||||
if (closeTimer.current) clearTimeout(closeTimer.current)
|
||||
if (openId !== id) setHoveredLinkImage(null)
|
||||
setOpenId(id)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
closeTimer.current = setTimeout(() => setOpenId(null), 150)
|
||||
closeTimer.current = setTimeout(() => {
|
||||
setOpenId(null)
|
||||
setHoveredLinkImage(null)
|
||||
}, 150)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -32,6 +72,10 @@ export function DesktopMegaMenu({ items, lang, currentPath }: DesktopMegaMenuPro
|
||||
const hasColumns = !!item.columns?.length
|
||||
const isActive = item.url ? currentPath === item.url : false
|
||||
|
||||
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}
|
||||
@@ -70,26 +114,68 @@ export function DesktopMegaMenu({ items, lang, currentPath }: DesktopMegaMenuPro
|
||||
onMouseLeave={handleClose}
|
||||
>
|
||||
<div className="w-full cursor-default border-b border-black/5 bg-surface-card/95 shadow-card backdrop-blur-xl">
|
||||
<div className="mx-auto flex max-w-7xl justify-center gap-16 px-6 py-10">
|
||||
{item.columns!.map((column, columnIndex) => (
|
||||
<div key={columnIndex} className="flex min-w-[140px] flex-col text-left">
|
||||
<h4 className="mb-4 text-[10px] font-bold uppercase tracking-widest text-text-main/50">
|
||||
{getLocalizedMenuText(column.title, lang)}
|
||||
</h4>
|
||||
<ul className="space-y-3">
|
||||
{column.links.map((link, linkIndex) => (
|
||||
<li key={linkIndex}>
|
||||
<Link
|
||||
href={link.url || '#'}
|
||||
className="block text-sm font-normal normal-case tracking-normal text-text-main/65 transition-colors hover:text-brand-primary"
|
||||
>
|
||||
{getLocalizedMenuText(link.label, lang)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mx-auto flex max-w-7xl items-start gap-10 px-6 py-10">
|
||||
|
||||
{/* LEFT: Root menu image (1:1) */}
|
||||
{item.rootImageUrl && (
|
||||
<div className="relative hidden h-56 w-56 shrink-0 overflow-hidden rounded-xl border border-black/8 lg:block">
|
||||
<MenuImage
|
||||
src={item.rootImageUrl}
|
||||
alt={getLocalizedMenuText(item.label, lang)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
|
||||
{/* CENTER: Columns */}
|
||||
<div className="flex flex-1 gap-12">
|
||||
{item.columns!.map((column, columnIndex) => (
|
||||
<div key={columnIndex} className="flex min-w-[160px] gap-4 text-left items-start">
|
||||
|
||||
{/* Column icon (1:1) */}
|
||||
{hasAnyColumnIcon && (
|
||||
<div className="relative h-14 w-14 shrink-0 mt-0.5 overflow-hidden rounded-lg border border-black/8 bg-surface-muted">
|
||||
<MenuImage
|
||||
src={column.iconUrl}
|
||||
alt={getLocalizedMenuText(column.title, lang)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Title and links */}
|
||||
<div className="flex flex-col">
|
||||
<h4 className="mb-3 text-[10px] font-bold uppercase tracking-widest text-text-main/50">
|
||||
{getLocalizedMenuText(column.title, lang)}
|
||||
</h4>
|
||||
<ul className="space-y-2.5">
|
||||
{column.links.map((link, linkIndex) => (
|
||||
<li key={linkIndex}>
|
||||
<Link
|
||||
href={link.url || '#'}
|
||||
className="block text-sm font-normal normal-case tracking-normal text-text-main/65 transition-colors duration-150 hover:text-brand-primary"
|
||||
onMouseEnter={() => setHoveredLinkImage(link.imageUrl || null)}
|
||||
onMouseLeave={() => setHoveredLinkImage(null)}
|
||||
>
|
||||
{getLocalizedMenuText(link.label, lang)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Link hover preview / Root fallback (1:1) */}
|
||||
{showRightPanel && (
|
||||
<div className="relative hidden h-56 w-56 shrink-0 overflow-hidden rounded-xl border border-black/8 bg-surface-muted lg:flex items-center justify-center">
|
||||
<MenuImage
|
||||
src={hoveredLinkImage ?? item.rootImageUrl}
|
||||
alt="Preview"
|
||||
placeholder="Hover a link to preview"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,13 @@ export interface MenuLink {
|
||||
label: Record<string, string> | string
|
||||
url?: string
|
||||
links?: MenuLink[]
|
||||
imageUrl?: string | null // รูปที่แสดงเมื่อ Hover ลิงก์
|
||||
}
|
||||
|
||||
export interface MenuColumn {
|
||||
title: Record<string, string> | string
|
||||
links: MenuLink[]
|
||||
iconUrl?: string | null // รูปประจำคอลัมน์ (1:1)
|
||||
}
|
||||
|
||||
export interface MegaMenuPayload {
|
||||
@@ -15,6 +17,7 @@ export interface MegaMenuPayload {
|
||||
label: Record<string, string> | string
|
||||
url?: string
|
||||
columns?: MenuColumn[]
|
||||
rootImageUrl?: string | null // รูปหลักฝั่งซ้ายสุด (1:1)
|
||||
}
|
||||
|
||||
export function getLocalizedMenuText(text: Record<string, string> | string | undefined, lang: string) {
|
||||
|
||||
Reference in new issue
Block a user