x
This commit is contained in:
1 parent
f6c188bd93
commit
b78c9467bc
9 files changed
+656
-49
No files matched your search
@@ -4,17 +4,21 @@ import type { MegaMenuPayload, MenuColumn } from '../store/menuStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ArrowRight,
|
||||
Award,
|
||||
Box,
|
||||
ChevronRight,
|
||||
Gift,
|
||||
Leaf,
|
||||
Package,
|
||||
ShoppingBag,
|
||||
Sparkles,
|
||||
Star,
|
||||
type LucideIcon,
|
||||
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 <IconComponent className={className} strokeWidth={strokeWidth} />;
|
||||
}
|
||||
|
||||
|
||||
interface DesktopMegaMenuProps {
|
||||
items: MegaMenuPayload[];
|
||||
@@ -26,14 +30,7 @@ const getLocalizedText = (textObj: Record<string, string> | string | undefined,
|
||||
return textObj[lang] || textObj.en || textObj.th || Object.values(textObj)[0] || '';
|
||||
};
|
||||
|
||||
const MENU_ICONS: LucideIcon[] = [
|
||||
ShoppingBag,
|
||||
Sparkles,
|
||||
Leaf,
|
||||
Package,
|
||||
Gift,
|
||||
Award,
|
||||
];
|
||||
|
||||
|
||||
const MAX_TOP_LEVEL_LINKS = 5;
|
||||
const MAX_INLINE_CHILD_LINKS = 3;
|
||||
@@ -48,16 +45,16 @@ const getMoreLabel = (count: number, lang: string) => {
|
||||
};
|
||||
|
||||
// Image block shared between Root and Hover areas
|
||||
function MenuImage({ src, alt, placeholder }: { src?: string | null; alt: string; placeholder?: string }) {
|
||||
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 (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className={`w-full h-full flex items-center justify-center ${className || ''}`} style={style}>
|
||||
<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" />;
|
||||
return <img src={src} alt={alt} className={`w-full h-full object-cover ${className || ''}`} style={style} loading="lazy" />;
|
||||
}
|
||||
|
||||
export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
@@ -105,6 +102,8 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
const showFeatured = featuredConfig?.enabled !== false;
|
||||
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)
|
||||
@@ -165,15 +164,16 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
className={`pr-8 text-left ${idx > 0 ? 'border-l border-pop-black/10 pl-8' : ''}`}
|
||||
>
|
||||
<h4 className="mb-5 flex items-center gap-3 text-xs font-bold normal-case tracking-normal text-pop-black">
|
||||
{(() => {
|
||||
const HeadingIcon = MENU_ICONS[idx] ?? Sparkles;
|
||||
return <HeadingIcon className="h-4 w-4 text-pop-black/70" strokeWidth={1.5} />;
|
||||
})()}
|
||||
<DynamicIcon
|
||||
iconName={col.icon}
|
||||
defaultIcon={Sparkles}
|
||||
className="h-4 w-4 text-pop-black/70"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
{getLocalizedText(col.title, lang)}
|
||||
</h4>
|
||||
<ul className="space-y-2">
|
||||
{col.links.slice(0, MAX_TOP_LEVEL_LINKS).map((link, lIdx) => {
|
||||
const RowIcon = MENU_ICONS[(idx * 3 + lIdx + 1) % MENU_ICONS.length] ?? Box;
|
||||
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);
|
||||
@@ -191,7 +191,12 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
className="group flex items-center gap-4 rounded-md px-1 py-2.5 text-left transition-colors hover:bg-pop-black/5"
|
||||
>
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-pop-black/10 bg-pop-bg text-pop-black/70 transition-colors group-hover:border-pop-black/30">
|
||||
<RowIcon className="h-5 w-5" strokeWidth={1.35} />
|
||||
<DynamicIcon
|
||||
iconName={link.icon}
|
||||
defaultIcon={Box}
|
||||
className="h-5 w-5"
|
||||
strokeWidth={1.35}
|
||||
/>
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-sm font-bold normal-case tracking-normal text-pop-black">
|
||||
@@ -261,6 +266,8 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) {
|
||||
src={featuredImage}
|
||||
alt={featuredTitle}
|
||||
placeholder={getLocalizedText(item.label, lang)}
|
||||
className="transition-transform duration-700 group-hover:scale-105"
|
||||
style={{ objectPosition }}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2 p-4">
|
||||
|
||||
@@ -6,12 +6,14 @@ export interface MenuLink {
|
||||
url?: string;
|
||||
links?: MenuLink[];
|
||||
imageUrl?: string | null; // รูปที่แสดงเมื่อ Hover ลิงก์
|
||||
icon?: string; // ชื่อ icon จาก registry (lucide)
|
||||
}
|
||||
|
||||
export interface MenuColumn {
|
||||
title: Record<string, string> | string;
|
||||
links: MenuLink[];
|
||||
iconUrl?: string | null; // รูปประจำคอลัมน์ (1:1)
|
||||
icon?: string; // ชื่อ icon จาก registry (lucide)
|
||||
}
|
||||
|
||||
export interface MenuFeaturedCard {
|
||||
@@ -22,6 +24,7 @@ export interface MenuFeaturedCard {
|
||||
subtitle?: Record<string, string> | string;
|
||||
price?: string;
|
||||
imageUrl?: string | null;
|
||||
focalPoint?: { x: string; y: string };
|
||||
url?: string;
|
||||
ctaLabel?: Record<string, string> | string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
// Business
|
||||
Sparkles, Leaf, Wind, Droplets, Flower2, Flame, Sofa, BedDouble, Bath,
|
||||
Sun, Moon, TreePine, Sprout, Coffee, Waves, Cherry, Citrus, Snowflake,
|
||||
Palmtree, Crown, Gem, Feather, Smile, Zap, Cloud, Beaker, SprayCan,
|
||||
Wine, MountainSnow, Vegan,
|
||||
|
||||
// E-commerce
|
||||
ShoppingBag, ShoppingCart, Package, Truck, CreditCard, Wallet, Tag, Percent,
|
||||
Gift, Ticket, Store, User, Heart, Clock, MapPin, Settings, LogOut, LogIn,
|
||||
Bell, Bookmark, Search, Menu, X, ArrowRight, ArrowLeft, ChevronRight,
|
||||
ChevronDown, Plus, Minus, Trash2, Eye, Filter, SlidersHorizontal, RefreshCw,
|
||||
Camera, Share2, Award, ShieldCheck, Star, ThumbsUp, HelpCircle, Info,
|
||||
Phone, Mail, MessageCircle, CheckCircle2, XCircle, AlertCircle, Calendar, Box
|
||||
} from 'lucide-react';
|
||||
|
||||
export const ICON_REGISTRY = {
|
||||
// Business
|
||||
'sparkles': Sparkles,
|
||||
'leaf': Leaf,
|
||||
'wind': Wind,
|
||||
'droplets': Droplets,
|
||||
'flower': Flower2,
|
||||
'flame': Flame,
|
||||
'sofa': Sofa,
|
||||
'bed': BedDouble,
|
||||
'bath': Bath,
|
||||
'sun': Sun,
|
||||
'moon': Moon,
|
||||
'tree-pine': TreePine,
|
||||
'sprout': Sprout,
|
||||
'coffee': Coffee,
|
||||
'waves': Waves,
|
||||
'cherry': Cherry,
|
||||
'citrus': Citrus,
|
||||
'snowflake': Snowflake,
|
||||
'palmtree': Palmtree,
|
||||
'crown': Crown,
|
||||
'gem': Gem,
|
||||
'feather': Feather,
|
||||
'smile': Smile,
|
||||
'zap': Zap,
|
||||
'cloud': Cloud,
|
||||
'beaker': Beaker,
|
||||
'spray-can': SprayCan,
|
||||
'wine': Wine,
|
||||
'mountain': MountainSnow,
|
||||
'vegan': Vegan,
|
||||
|
||||
// E-commerce
|
||||
'shopping-bag': ShoppingBag,
|
||||
'shopping-cart': ShoppingCart,
|
||||
'package': Package,
|
||||
'truck': Truck,
|
||||
'credit-card': CreditCard,
|
||||
'wallet': Wallet,
|
||||
'tag': Tag,
|
||||
'percent': Percent,
|
||||
'gift': Gift,
|
||||
'ticket': Ticket,
|
||||
'store': Store,
|
||||
'user': User,
|
||||
'heart': Heart,
|
||||
'clock': Clock,
|
||||
'map-pin': MapPin,
|
||||
'settings': Settings,
|
||||
'logout': LogOut,
|
||||
'login': LogIn,
|
||||
'bell': Bell,
|
||||
'bookmark': Bookmark,
|
||||
'search': Search,
|
||||
'menu': Menu,
|
||||
'close': X,
|
||||
'arrow-right': ArrowRight,
|
||||
'arrow-left': ArrowLeft,
|
||||
'chevron-right': ChevronRight,
|
||||
'chevron-down': ChevronDown,
|
||||
'plus': Plus,
|
||||
'minus': Minus,
|
||||
'trash': Trash2,
|
||||
'eye': Eye,
|
||||
'filter': Filter,
|
||||
'sliders': SlidersHorizontal,
|
||||
'refresh': RefreshCw,
|
||||
'camera': Camera,
|
||||
'share': Share2,
|
||||
'award': Award,
|
||||
'shield-check': ShieldCheck,
|
||||
'star': Star,
|
||||
'thumbs-up': ThumbsUp,
|
||||
'help': HelpCircle,
|
||||
'info': Info,
|
||||
'phone': Phone,
|
||||
'mail': Mail,
|
||||
'chat': MessageCircle,
|
||||
'check-circle': CheckCircle2,
|
||||
'x-circle': XCircle,
|
||||
'alert': AlertCircle,
|
||||
'calendar': Calendar,
|
||||
'box': Box,
|
||||
} as const;
|
||||
|
||||
export type AvailableIcons = keyof typeof ICON_REGISTRY;
|
||||
Reference in new issue
Block a user