import * as React from "react"; import { useSearchDialog } from "@redocly/theme/core/hooks"; import { SearchDialog } from "@redocly/theme/components/Search/SearchDialog"; // Import from modular components import { AlertBanner } from "./components/AlertBanner"; import { NavLogo } from "./components/NavLogo"; import { NavItems } from "./components/NavItems"; import { NavControls, HamburgerButton } from "./controls"; import { DevelopSubmenu, UseCasesSubmenu, CommunitySubmenu, NetworkSubmenu } from "./submenus"; import { MobileMenu } from "./mobile-menu"; import { alertBanner } from "./constants/navigation"; // Re-export AlertBanner for backwards compatibility export { AlertBanner } from "./components/AlertBanner"; // Props interface for Navbar (extensible for future use) interface NavbarProps { className?: string; } /** * Main Navbar Component. * Renders the complete navigation bar including: * - Alert banner (when enabled) * - Logo * - Navigation items with desktop submenus * - Control buttons (search, theme toggle, language) * - Mobile menu */ export function Navbar(_props: NavbarProps = {}) { const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false); const [activeSubmenu, setActiveSubmenu] = React.useState(null); const [closingSubmenu, setClosingSubmenu] = React.useState(null); const submenuTimeoutRef = React.useRef(null); const closingTimeoutRef = React.useRef(null); // Use Redocly's search dialog hook - shared across navbar and mobile menu const { isOpen: isSearchOpen, onOpen: onSearchOpen, onClose: onSearchClose } = useSearchDialog(); const handleHamburgerClick = () => { setMobileMenuOpen(true); }; const handleMobileMenuClose = () => { setMobileMenuOpen(false); }; const handleSubmenuMouseEnter = (itemLabel: string) => { // Clear any pending close/closing timeouts if (submenuTimeoutRef.current) { clearTimeout(submenuTimeoutRef.current); submenuTimeoutRef.current = null; } if (closingTimeoutRef.current) { clearTimeout(closingTimeoutRef.current); closingTimeoutRef.current = null; } // Cancel closing state and activate the new submenu setClosingSubmenu(null); setActiveSubmenu(itemLabel); }; const handleSubmenuMouseLeave = () => { submenuTimeoutRef.current = setTimeout(() => { // Start closing animation const currentSubmenu = activeSubmenu; if (currentSubmenu) { setClosingSubmenu(currentSubmenu); setActiveSubmenu(null); // After animation completes (300ms), clear closing state closingTimeoutRef.current = setTimeout(() => { setClosingSubmenu(null); }, 350); // Slightly longer than animation to ensure completion } }, 150); }; const handleSubmenuClose = () => { // Close submenu immediately (for keyboard navigation) if (activeSubmenu) { setClosingSubmenu(activeSubmenu); setActiveSubmenu(null); // After animation completes, clear closing state closingTimeoutRef.current = setTimeout(() => { setClosingSubmenu(null); }, 350); } }; // Handle scroll lock when submenu is open or closing React.useEffect(() => { if (activeSubmenu || closingSubmenu) { document.body.classList.add('bds-submenu-open'); } else { document.body.classList.remove('bds-submenu-open'); } return () => { document.body.classList.remove('bds-submenu-open'); }; }, [activeSubmenu, closingSubmenu]); React.useEffect(() => { return () => { if (submenuTimeoutRef.current) { clearTimeout(submenuTimeoutRef.current); } if (closingTimeoutRef.current) { clearTimeout(closingTimeoutRef.current); } }; }, []); const navbarClasses = [ "bds-navbar", alertBanner.show ? "bds-navbar--with-banner" : "" ].filter(Boolean).join(" "); return ( <> {/* Backdrop blur overlay when submenu is open or closing */}
setActiveSubmenu(null)} />
{/* Submenus positioned relative to navbar */}
activeSubmenu && handleSubmenuMouseEnter(activeSubmenu)}>
{/* Render SearchDialog when open - this is the actual search modal */} {isSearchOpen && } ); }