"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 && (
)}
{isOpen && (
)}
>
);
}