'use client'; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import Link from 'next/link'; import { ChevronDown } from 'lucide-react'; import { catalogService, CategoryResponse } from '@/services/api/catalogService'; export const SIDEBAR_WIDTH = 280; type SubLink = { label: string; href: string }; type MegaGroup = { title: string; href: string; links: SubLink[]; }; type MenuItem = { id: string; label: string; href: string; /** Multi-column flyout — only on Our Store / Smart Devices / Phones */ mega?: MegaGroup[]; chevron?: 'down'; }; function catHref(slug: string) { return `/shop?category=${encodeURIComponent(slug)}`; } function buildMenuFromCatalog(rawCategories: CategoryResponse[]): MenuItem[] { if (!rawCategories || rawCategories.length === 0) return []; const categories = rawCategories.filter((c) => (c as any).is_active !== false); const rootCategories = categories.filter((c) => !c.parent_category_id); const topList = rootCategories.length > 0 ? rootCategories : categories; return topList.map((cat) => { const subCats = categories.filter((c) => c.parent_category_id === cat.category_id); const subLinks: SubLink[] = subCats.map((sub) => ({ label: sub.name, href: catHref(sub.slug), })); return { id: cat.category_id, label: cat.name, href: catHref(cat.slug), chevron: subLinks.length > 0 ? 'down' : undefined, mega: subLinks.length > 0 ? [{ title: cat.name, href: catHref(cat.slug), links: subLinks }] : undefined, }; }); } export const STATIC_MENU: MenuItem[] = []; export function getCategoryMenuItems(categories: CategoryResponse[]) { return buildMenuFromCatalog(categories); } export function menuItemCategoryParam(href: string): string { const match = String(href || '').match(/[?&]category=([^&]+)/i); return match ? decodeURIComponent(match[1]) : ''; } function MegaPanel({ groups }: { groups: MegaGroup[] }) { const panelRef = useRef(null); const [offsetY, setOffsetY] = useState(0); const [maxHeight, setMaxHeight] = useState(undefined); // Pack into 3 columns (2 groups each when possible), like the reference const columns: MegaGroup[][] = [[], [], []]; groups.forEach((g, i) => { columns[i % 3].push(g); }); useLayoutEffect(() => { const el = panelRef.current; if (!el) return; const EDGE = 12; const sync = () => { // Reset so we measure from the natural top-aligned position el.style.transform = 'translateY(0)'; el.style.maxHeight = ''; const rect = el.getBoundingClientRect(); const vh = window.innerHeight; const nextMax = Math.max(120, vh - EDGE * 2); setMaxHeight(nextMax); const height = Math.min(rect.height, nextMax); let shift = 0; const bottom = rect.top + height; if (bottom > vh - EDGE) { shift = vh - EDGE - bottom; } if (rect.top + shift < EDGE) { shift = EDGE - rect.top; } setOffsetY(shift); }; sync(); window.addEventListener('resize', sync); return () => window.removeEventListener('resize', sync); }, [groups]); return (
{/* Keeps hover alive across the seam between the row and the flyout */}
{columns.map((col, colIdx) => (
{col.map((group) => (
{group.title}
    {group.links.map((item) => (
  • {item.label}
  • ))}
))}
))}
); } interface CategorySidebarProps { initialCategories?: CategoryResponse[]; className?: string; /** Floating panel under the navbar All Categories button */ isDropdown?: boolean; } export const CategorySidebar: React.FC = ({ initialCategories, className = '', isDropdown = false, }) => { const [categories, setCategories] = useState( initialCategories || [] ); const [openId, setOpenId] = useState(null); useEffect(() => { if (initialCategories?.length) { setCategories(initialCategories); return; } let cancelled = false; catalogService .getCategories() .then((res) => { if (!cancelled && res?.length) setCategories(res); }) .catch(() => {}); return () => { cancelled = true; }; }, [initialCategories]); const menu = useMemo( () => categories.length > 0 ? buildMenuFromCatalog(categories) : STATIC_MENU, [categories] ); return ( ); };