"use client" import React, { useState, useRef, useEffect } from 'react' import Link from 'next/link' import { X, Menu, ChevronDown, ChevronRight, } from 'lucide-react' import { cn } from '@/lib/utils' import { colors, surfaces } from '@/lib/theme' import type { NavigationIcon, NavigationMenuItem, NavigationDropdownConfig, NavigationDropdownGroup, } from '@/lib/types/navigation' import { headerNavigationConfig } from './header-config' const NAVIGATION_CONFIG: NavigationMenuItem[] = headerNavigationConfig interface NavItemProps { href: string icon: NavigationIcon children: React.ReactNode } const NavItem = ({ href, icon, children }: NavItemProps) => (
{React.createElement(icon, { className: "text-md mr-2", strokeWidth: 2.5, size: 20 })} {children}
); interface DropdownNavItemProps { id: string href: string icon: NavigationIcon children: React.ReactNode dropdownContent: React.ReactNode isMobile?: boolean isOpen?: boolean onOpenChange?: (id: string | null, immediate?: boolean) => void } const DropdownNavItem = ({ id, href, icon, children, dropdownContent, isMobile = false, isOpen = false, onOpenChange }: DropdownNavItemProps) => { const dropdownRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { onOpenChange?.(null, true); } }; if (isMobile && isOpen) { document.addEventListener('click', handleClickOutside); return () => document.removeEventListener('click', handleClickOutside); } }, [isMobile, isOpen, onOpenChange]); const handleMouseEnter = () => { if (!isMobile) { onOpenChange?.(id, true); } }; const handleMouseLeave = (e: React.MouseEvent) => { if (!isMobile) { const relatedTarget = e.relatedTarget as Node | null; if (relatedTarget instanceof Node && dropdownRef.current?.contains(relatedTarget)) { return; } onOpenChange?.(null); } }; const handleClick = (e: React.MouseEvent) => { if (isMobile) { e.preventDefault(); e.stopPropagation(); onOpenChange?.(isOpen ? null : id, true); } }; return (
{React.createElement(icon, { className: "text-md mr-2", strokeWidth: 2.5, size: 20 })} {children} {isOpen && ( <> {!isMobile &&
}
{dropdownContent}
)}
); }; interface NestedDropdownItemProps { children: React.ReactNode nestedContent: React.ReactNode icon: NavigationIcon isMobile?: boolean itemKey: string activeNested: string | null onNestedChange: (key: string | null, immediate?: boolean) => void } const NestedDropdownItem = ({ children, nestedContent, icon: Icon, isMobile = false, itemKey, activeNested, onNestedChange }: NestedDropdownItemProps) => { const itemRef = useRef(null); const isOpen = activeNested === itemKey; const handleMouseEnter = () => { if (!isMobile) { onNestedChange(itemKey, true); } }; const handleMouseLeave = (e: React.MouseEvent) => { if (!isMobile) { const relatedTarget = e.relatedTarget as Node | null; if (relatedTarget instanceof Node && itemRef.current?.contains(relatedTarget)) { return; } onNestedChange(null); } }; const handleClick = (e: React.MouseEvent) => { if (isMobile) { e.preventDefault(); e.stopPropagation(); onNestedChange(isOpen ? null : itemKey, true); } }; if (isMobile) { return (
{isOpen && (
{nestedContent}
)}
); } return (
{isOpen && ( <>
onNestedChange(itemKey, true)} onMouseLeave={(e) => { const relatedTarget = e.relatedTarget as Node | null; if (relatedTarget instanceof Node && itemRef.current?.contains(relatedTarget)) return; onNestedChange(null); }} > {nestedContent}
)}
); }; const renderNestedGroups = (groups: NavigationDropdownGroup[], isMobile: boolean) => { const hasAnyTitle = groups.some(group => group.title); return (
{groups.map((group, index) => (
{group.title && (
{group.title}
)} {group.links.map((link) => ( {React.createElement(link.icon, { className: 'mr-3', strokeWidth: 2.5, size: 18 })} {link.label} ))}
))}
); } const renderDropdownContent = (config: NavigationDropdownConfig, isMobile: boolean, activeNested: string | null, onNestedChange: (key: string | null, immediate?: boolean) => void) => (
{config.items.map((item) => { if (item.type === 'link') { return ( { if (!isMobile && activeNested) { onNestedChange(null, true); } }} > {React.createElement(item.icon, { className: 'mr-3', strokeWidth: 2.5, size: 18 })} {item.label} ) } return ( {item.label} ) })}
) export default function Header() { const [isOpen, setIsOpen] = useState(false); const [isMobile, setIsMobile] = useState(false); const [activeDropdown, setActiveDropdown] = useState(null); const [activeNested, setActiveNested] = useState(null); const [showDesktopOverlay, setShowDesktopOverlay] = useState(false); const [overlayVisible, setOverlayVisible] = useState(false); const overlayCloseTimeoutRef = useRef | null>(null); const overlayOpenFrameRef = useRef(null); const dropdownTimeoutRef = useRef(null); const nestedTimeoutRef = useRef(null); const toggleMenu = () => { setIsOpen(!isOpen); if (isOpen) { setActiveDropdown(null); setActiveNested(null); } }; const handleDropdownChange = (id: string | null, immediate: boolean = false) => { if (dropdownTimeoutRef.current) { clearTimeout(dropdownTimeoutRef.current); dropdownTimeoutRef.current = null; } if (nestedTimeoutRef.current) { clearTimeout(nestedTimeoutRef.current); nestedTimeoutRef.current = null; } if (id !== null || immediate) { setActiveDropdown(id); setActiveNested(null); } else { dropdownTimeoutRef.current = setTimeout(() => { setActiveDropdown(null); setActiveNested(null); dropdownTimeoutRef.current = null; }, 300); } }; const handleNestedChange = (key: string | null, immediate: boolean = false) => { if (nestedTimeoutRef.current) { clearTimeout(nestedTimeoutRef.current); nestedTimeoutRef.current = null; } if (key !== null || immediate) { setActiveNested(key); } else { nestedTimeoutRef.current = setTimeout(() => { setActiveNested(null); nestedTimeoutRef.current = null; }, 300); } }; useEffect(() => { const checkMobile = () => { setIsMobile(window.innerWidth < 1024); }; checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); useEffect(() => { if (isMobile) { if (overlayOpenFrameRef.current !== null) { cancelAnimationFrame(overlayOpenFrameRef.current); overlayOpenFrameRef.current = null; } if (overlayCloseTimeoutRef.current !== null) { clearTimeout(overlayCloseTimeoutRef.current); overlayCloseTimeoutRef.current = null; } setOverlayVisible(false); setShowDesktopOverlay(false); } else if (activeDropdown) { if (overlayCloseTimeoutRef.current !== null) { clearTimeout(overlayCloseTimeoutRef.current); overlayCloseTimeoutRef.current = null; } setShowDesktopOverlay(true); overlayOpenFrameRef.current = requestAnimationFrame(() => { setOverlayVisible(true); overlayOpenFrameRef.current = null; }); } else { if (overlayOpenFrameRef.current !== null) { cancelAnimationFrame(overlayOpenFrameRef.current); overlayOpenFrameRef.current = null; } setOverlayVisible(false); overlayCloseTimeoutRef.current = setTimeout(() => { setShowDesktopOverlay(false); overlayCloseTimeoutRef.current = null; }, 300); } return () => { if (overlayOpenFrameRef.current !== null) { cancelAnimationFrame(overlayOpenFrameRef.current); overlayOpenFrameRef.current = null; } if (overlayCloseTimeoutRef.current !== null) { clearTimeout(overlayCloseTimeoutRef.current); overlayCloseTimeoutRef.current = null; } }; }, [activeDropdown, isMobile]); return ( <> {showDesktopOverlay && (