283 lines
14 KiB
TypeScript
283 lines
14 KiB
TypeScript
/**
|
|
* @component MegaMenu
|
|
* @purpose Navigation bar matching TechShop reference UI with sticky header scroll effect, toggleable floating category dropdown menu when scrolled down (Image 2), and interactive mega flyouts.
|
|
* @dependencies catalogService, lucide-react
|
|
*/
|
|
'use client';
|
|
|
|
import { useState, useEffect, useRef } from 'react';
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { Menu, ChevronDown, ChevronRight, Percent, Flame, Sparkles, Smartphone, Laptop, Tv, Watch, Camera, Zap } from 'lucide-react';
|
|
import { catalogService, CategoryResponse, ProductResponse } from '@/services/api/catalogService';
|
|
|
|
interface SubCategoryGroup {
|
|
name: string;
|
|
items: string[];
|
|
}
|
|
|
|
const MEGA_DROPDOWNS: Record<string, SubCategoryGroup[]> = {
|
|
shop: [
|
|
{ name: 'Shop Layouts', items: ['Catalog Grid 4-Cols', 'Catalog List View', 'Category Sidebar Filters', 'Deal of the Day'] },
|
|
{ name: 'Product Types', items: ['Smartphones', 'Laptops & MacBooks', 'Smartwatches & Fitness', 'Wireless Audio'] },
|
|
],
|
|
categories: [
|
|
{ name: 'Consumer Electronics', items: ['Smartphones & Accessories', 'Tablets & E-Readers', 'Cameras & Photography', 'Audio & Headphones'] },
|
|
{ name: 'Computers & Office', items: ['Laptops & Ultraportables', 'Desktop PCs & Gaming', 'Monitors & Displays', 'Chargers & Power Adapters'] },
|
|
],
|
|
products: [
|
|
{ name: 'Top Trending', items: ['Apple iPhone 15 Pro Max', 'Sony WH-1000XM5 Headphones', 'Google Pixel 8 Pro', 'Apple Watch Ultra 2'] },
|
|
{ name: 'Certified Repairs', items: ['OLED Screen Replacement', 'Battery Health Calibration', 'Camera Module Fix', 'Water Damage Restoration'] },
|
|
],
|
|
};
|
|
|
|
export function MegaMenu() {
|
|
const [isSticky, setIsSticky] = useState(false);
|
|
const [categories, setCategories] = useState<CategoryResponse[]>([]);
|
|
const [featuredProducts, setFeaturedProducts] = useState<ProductResponse[]>([]);
|
|
const [categoriesOpen, setCategoriesOpen] = useState(false);
|
|
const [activeCategory, setActiveCategory] = useState<string | null>(null);
|
|
const [activeNavDropdown, setActiveNavDropdown] = useState<string | null>(null);
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
catalogService.getCategories().then((res) => {
|
|
if (Array.isArray(res)) setCategories(res);
|
|
});
|
|
catalogService.getProducts().then((res) => setFeaturedProducts(res.slice(0, 3)));
|
|
|
|
const handleScroll = () => {
|
|
setIsSticky(window.scrollY > 120);
|
|
};
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
|
setCategoriesOpen(false);
|
|
setActiveCategory(null);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
const categoryList = categories;
|
|
|
|
const getCategoryIcon = (slug: string) => {
|
|
switch (slug) {
|
|
case 'cameras': return <Camera className="w-4 h-4 text-[#1877f2]" />;
|
|
case 'phones': return <Smartphone className="w-4 h-4 text-[#1877f2]" />;
|
|
case 'tv-speaker': return <Tv className="w-4 h-4 text-[#1877f2]" />;
|
|
case 'smart-devices': return <Watch className="w-4 h-4 text-[#1877f2]" />;
|
|
case 'laptop': return <Laptop className="w-4 h-4 text-[#1877f2]" />;
|
|
case 'chargers-cables': return <Zap className="w-4 h-4 text-[#1877f2]" />;
|
|
default: return <Sparkles className="w-4 h-4 text-[#1877f2]" />;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<nav className={`w-full bg-white border-b border-gray-200 transition-all duration-200 z-50 ${
|
|
isSticky ? 'fixed top-0 left-0 right-0 shadow-md backdrop-blur-md bg-white/95 animate-in slide-in-from-top-2' : 'relative'
|
|
}`}>
|
|
<div className="max-w-7xl mx-auto px-4 flex items-center justify-between">
|
|
{/* Left: "All Categories" Toggle Button & Floating Dropdown Menu (Image 2) */}
|
|
<div ref={dropdownRef} className="relative w-64 shrink-0" onMouseLeave={() => { setCategoriesOpen(false); setActiveCategory(null); }}>
|
|
<button
|
|
onClick={() => setCategoriesOpen(!categoriesOpen)}
|
|
onMouseEnter={() => setCategoriesOpen(true)}
|
|
className="w-full bg-[#1877f2] hover:bg-[#1565c0] text-white font-bold text-xs uppercase tracking-wide py-3.5 px-4 rounded-t-lg flex items-center justify-between transition-colors cursor-pointer"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<Menu className="w-4 h-4" />
|
|
<span>All Categories</span>
|
|
</div>
|
|
<ChevronDown className={`w-4 h-4 transition-transform duration-200 ${categoriesOpen ? 'rotate-180' : ''}`} />
|
|
</button>
|
|
|
|
{/* Floating Dropdown Category Menu (Triggered on Click or Hover, Matching Image 2) */}
|
|
{categoriesOpen && (
|
|
<div className="absolute top-full left-0 flex z-50 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
{/* Main Categories List */}
|
|
<div className="w-64 bg-white border-x border-b border-gray-200 rounded-b-xl shadow-2xl py-2 divide-y divide-gray-100 text-xs">
|
|
{categoryList.map((cat) => (
|
|
<div
|
|
key={cat.category_id}
|
|
onMouseEnter={() => setActiveCategory(cat.slug)}
|
|
className="relative"
|
|
>
|
|
<Link
|
|
href={`/products?category=${cat.slug}`}
|
|
onClick={() => setCategoriesOpen(false)}
|
|
className={`flex items-center justify-between px-4 py-2.5 font-medium transition-colors ${
|
|
activeCategory === cat.slug ? 'bg-blue-50 text-[#1877f2]' : 'hover:bg-gray-50 text-gray-700 hover:text-[#1877f2]'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
{getCategoryIcon(cat.slug)}
|
|
<span>{cat.name}</span>
|
|
</div>
|
|
<ChevronRight className="w-3.5 h-3.5 text-gray-400" />
|
|
</Link>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Subcategory Flyout Panel */}
|
|
{activeCategory && (
|
|
<div className="w-64 bg-white border border-gray-200 shadow-2xl rounded-r-xl p-4 text-xs z-50 ml-0.5 border-l-2 border-l-[#1877f2] animate-in fade-in slide-in-from-left-2 duration-150">
|
|
<h4 className="font-extrabold text-[#1877f2] uppercase tracking-wider mb-3 border-b border-gray-100 pb-2 flex items-center gap-1.5">
|
|
<Sparkles className="w-3.5 h-3.5 text-[#1877f2]" /> Subcategories & Items
|
|
</h4>
|
|
<ul className="space-y-2 font-medium text-gray-700">
|
|
<li>
|
|
<Link href={`/products?category=${activeCategory}`} onClick={() => setCategoriesOpen(false)} className="hover:text-[#1877f2] block transition-colors">
|
|
• Top Flagship Models
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link href={`/products?category=${activeCategory}`} onClick={() => setCategoriesOpen(false)} className="hover:text-[#1877f2] block transition-colors">
|
|
• Certified OEM Accessories
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link href={`/products?category=${activeCategory}`} onClick={() => setCategoriesOpen(false)} className="hover:text-[#1877f2] block transition-colors">
|
|
• Discounted Refurbished Stock
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link href="/services" onClick={() => setCategoriesOpen(false)} className="text-[#e4382f] font-bold block hover:underline pt-1">
|
|
• Repair & Replacement Service
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Center: Horizontal Navigation Links */}
|
|
<div className="hidden lg:flex items-center gap-7 text-xs font-semibold text-gray-800 py-3.5 pl-6 flex-1 relative">
|
|
<Link href="/" className="text-[#1877f2] hover:text-[#1877f2] transition-colors">
|
|
Home
|
|
</Link>
|
|
|
|
{/* Shop Nav Item */}
|
|
<div
|
|
className="relative py-1 cursor-pointer"
|
|
onMouseEnter={() => setActiveNavDropdown('shop')}
|
|
onMouseLeave={() => setActiveNavDropdown(null)}
|
|
>
|
|
<Link href="/products" className="hover:text-[#1877f2] transition-colors flex items-center gap-1">
|
|
Shop <ChevronDown className="w-3 h-3 text-gray-400" />
|
|
</Link>
|
|
|
|
{activeNavDropdown === 'shop' && MEGA_DROPDOWNS.shop && (
|
|
<div className="absolute top-full left-0 w-96 bg-white border border-gray-200 shadow-2xl rounded-xl p-5 z-50 grid grid-cols-2 gap-4 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
{MEGA_DROPDOWNS.shop.map((grp, idx) => (
|
|
<div key={idx}>
|
|
<h5 className="font-extrabold text-[#1877f2] uppercase text-[11px] mb-2">{grp.name}</h5>
|
|
<ul className="space-y-1.5 text-xs text-gray-600 font-medium">
|
|
{grp.items.map((item, i) => (
|
|
<li key={i}>
|
|
<Link href="/products" className="hover:text-[#1877f2] transition-colors block">
|
|
{item}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Categories Nav Item with SALE badge */}
|
|
<div
|
|
className="relative py-1 cursor-pointer"
|
|
onMouseEnter={() => setActiveNavDropdown('categories')}
|
|
onMouseLeave={() => setActiveNavDropdown(null)}
|
|
>
|
|
<Link href="/products" className="hover:text-[#1877f2] transition-colors flex items-center gap-1">
|
|
Categories
|
|
<span className="bg-[#e4ecf7] text-[#1877f2] text-[9px] font-bold px-1.5 py-0.5 rounded uppercase">
|
|
SALE
|
|
</span>
|
|
<ChevronDown className="w-3 h-3 text-gray-400" />
|
|
</Link>
|
|
|
|
{activeNavDropdown === 'categories' && MEGA_DROPDOWNS.categories && (
|
|
<div className="absolute top-full left-0 w-96 bg-white border border-gray-200 shadow-2xl rounded-xl p-5 z-50 grid grid-cols-2 gap-4 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
{MEGA_DROPDOWNS.categories.map((grp, idx) => (
|
|
<div key={idx}>
|
|
<h5 className="font-extrabold text-[#1877f2] uppercase text-[11px] mb-2">{grp.name}</h5>
|
|
<ul className="space-y-1.5 text-xs text-gray-600 font-medium">
|
|
{grp.items.map((item, i) => (
|
|
<li key={i}>
|
|
<Link href="/products" className="hover:text-[#1877f2] transition-colors block">
|
|
{item}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Products Nav Item with HOT badge */}
|
|
<div
|
|
className="relative py-1 cursor-pointer"
|
|
onMouseEnter={() => setActiveNavDropdown('products')}
|
|
onMouseLeave={() => setActiveNavDropdown(null)}
|
|
>
|
|
<Link href="/products" className="hover:text-[#1877f2] transition-colors flex items-center gap-1">
|
|
Products
|
|
<span className="bg-red-50 text-[#e4382f] text-[9px] font-bold px-1.5 py-0.5 rounded uppercase">
|
|
HOT
|
|
</span>
|
|
<ChevronDown className="w-3 h-3 text-gray-400" />
|
|
</Link>
|
|
|
|
{activeNavDropdown === 'products' && (
|
|
<div className="absolute top-full left-0 w-[450px] bg-white border border-gray-200 shadow-2xl rounded-xl p-5 z-50 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
<h5 className="font-extrabold text-[#1877f2] uppercase text-[11px] mb-3">Featured Tech Products</h5>
|
|
<div className="grid grid-cols-3 gap-3">
|
|
{featuredProducts.map((p) => (
|
|
<Link key={p.product_id} href={`/products/${p.slug}`} className="group text-center">
|
|
<div className="relative w-full h-20 bg-gray-50 rounded-lg border border-gray-100 overflow-hidden mb-1">
|
|
<Image src={p.images[0]?.image_url || ''} alt={p.name} fill className="object-contain p-1 group-hover:scale-105 transition-transform" />
|
|
</div>
|
|
<span className="text-[10px] font-bold text-gray-900 group-hover:text-[#1877f2] line-clamp-1 block">{p.name}</span>
|
|
<span className="text-[10px] font-extrabold text-[#e4382f]">${p.variants[0]?.price}</span>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<Link href="/products?on_sale=true" className="hover:text-[#1877f2] transition-colors flex items-center gap-1">
|
|
Top deals <ChevronDown className="w-3 h-3 text-gray-400" />
|
|
</Link>
|
|
|
|
<Link href="/services" className="hover:text-[#1877f2] transition-colors flex items-center gap-1 text-[#e4382f] font-bold">
|
|
Device Repairs <ChevronDown className="w-3 h-3 text-gray-400" />
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Right Super Discount Tag */}
|
|
<div className="hidden md:flex items-center gap-1.5 text-xs font-bold text-gray-900 shrink-0">
|
|
<span className="w-5 h-5 rounded-full bg-[#e4382f] text-white flex items-center justify-center">
|
|
<Percent className="w-3 h-3" />
|
|
</span>
|
|
<span>Super Discount</span>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|