import { useEffect, useState } from "preact/hooks"; import { createPortal } from "preact/compat"; import { NavigationItem, NavigationMenuProps } from "./welcome-types.ts"; import { getNavigationGroups } from "./navigationConfig.ts"; /** * NavigationMenu Component * Dropdown menu with grouped navigation links * Positioned absolutely relative to the hamburger button * Closes when clicking outside * * @param showMenu - Whether menu is visible * @param menuButtonRef - Reference to the hamburger button for positioning * @param onClose - Handler to close the menu * @param isLoggedIn - User authentication status * @param currentRoute - Current route path * @param navigationGroups - Optional custom navigation groups */ export default function NavigationMenu( { showMenu, menuButtonRef, onClose, isLoggedIn, currentRoute, navigationGroups, }: NavigationMenuProps, ) { // Close menu when clicking outside - simplified to prevent issues useEffect(() => { if (!showMenu) return; const handleClickOutside = (event: MouseEvent) => { const target = event.target as HTMLElement; if (!target) return; // Simple check: if click is outside menu container and button if ( !target.closest(".navigation-menu") && !target.closest(".menu-button") ) { onClose(); } }; // Handle escape key const handleEscape = (event: KeyboardEvent) => { if (event.key === "Escape") { onClose(); } }; // Add listeners immediately without delay document.addEventListener("click", handleClickOutside, true); document.addEventListener("keydown", handleEscape); return () => { document.removeEventListener("click", handleClickOutside, true); document.removeEventListener("keydown", handleEscape); }; }, [showMenu, onClose]); /** * Render a single navigation item */ function renderNavigationItem(item: NavigationItem) { const baseClasses = "block w-full text-left px-4 py-2 transition-all duration-200"; const stateClasses = (() => { if (item.disabled) { return "text-gray-500 cursor-not-allowed"; } if (item.path === "/auth/logout") { return "text-red-400 hover:bg-red-400/10 hover:text-red-300 hover:shadow-[0_0_15px_rgba(248,113,113,0.3)]"; } if (item.path === "/auth/login") { return "text-purple-400 hover:bg-purple-400/10 hover:text-purple-300 hover:shadow-[0_0_15px_rgba(192,132,252,0.3)]"; } return "text-teal-400 hover:bg-teal-400/10 hover:text-teal-300 hover:shadow-[0_0_15px_rgba(20,184,166,0.3)]"; })(); // Skip current route items (except auth forms) if (!item.isForm && item.path === currentRoute) { return null; } // Handle form items (login/logout) if (item.isForm && item.formAction) { return (
); } // Handle regular links return ( { if (item.disabled) { e.preventDefault(); return; } onClose(); }} role="menuitem" aria-disabled={item.disabled} > {item.label} ); } /** * Render a navigation group with separator */ function renderNavigationGroup( group: { id: string; label: string; items: NavigationItem[] }, ) { if (group.items.length === 0) return null; return (