diff --git a/@theme/components/Navbar/Navbar.tsx b/@theme/components/Navbar/Navbar.tsx index ea2c067ae1..c91faaff13 100644 --- a/@theme/components/Navbar/Navbar.tsx +++ b/@theme/components/Navbar/Navbar.tsx @@ -1,4 +1,6 @@ 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"; @@ -33,6 +35,9 @@ export function Navbar(_props: NavbarProps = {}) { 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); }; @@ -115,7 +120,7 @@ export function Navbar(_props: NavbarProps = {}) {
- +
{/* Submenus positioned relative to navbar */} @@ -126,7 +131,9 @@ export function Navbar(_props: NavbarProps = {}) { - + + {/* Render SearchDialog when open - this is the actual search modal */} + {isSearchOpen && } ); } diff --git a/@theme/components/Navbar/components/AlertBanner.tsx b/@theme/components/Navbar/components/AlertBanner.tsx index e98df172f8..38f6f9b2d4 100644 --- a/@theme/components/Navbar/components/AlertBanner.tsx +++ b/@theme/components/Navbar/components/AlertBanner.tsx @@ -18,7 +18,8 @@ export function AlertBanner({ message, button, link, show }: AlertBannerProps) { const { useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const bannerRef = React.useRef(null); - const [displayDate, setDisplayDate] = React.useState("JUNE 10-12"); + // Use null initial state to avoid hydration mismatch - server and client both render null initially + const [displayDate, setDisplayDate] = React.useState(null); React.useEffect(() => { const calculateCountdown = () => { @@ -69,7 +70,7 @@ export function AlertBanner({ message, button, link, show }: AlertBannerProps) { >
{translate(message)}
-
{displayDate}
+
{displayDate ?? "JUNE 10-12"}
{translate(button)}
diff --git a/@theme/components/Navbar/controls/NavControls.tsx b/@theme/components/Navbar/controls/NavControls.tsx index fa715b95c4..dad75a4e30 100644 --- a/@theme/components/Navbar/controls/NavControls.tsx +++ b/@theme/components/Navbar/controls/NavControls.tsx @@ -1,21 +1,16 @@ -import * as React from "react"; import { SearchButton } from "./SearchButton"; import { ModeToggleButton } from "./ModeToggleButton"; import { LanguagePill } from "./LanguagePill"; +interface NavControlsProps { + onSearch?: () => void; +} + /** * Nav Controls Component. * Right side of the navbar containing search, mode toggle, and language selector. */ -export function NavControls() { - const handleSearch = () => { - // Trigger the search modal - const searchTrigger = document.querySelector('[data-component-name="Search/SearchTrigger"]') as HTMLElement; - if (searchTrigger) { - searchTrigger.click(); - } - }; - +export function NavControls({ onSearch }: NavControlsProps) { const handleModeToggle = () => { // Toggle between light and dark theme const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark"; @@ -32,7 +27,7 @@ export function NavControls() { return (
- +
diff --git a/@theme/components/Navbar/mobile-menu/MobileMenu.tsx b/@theme/components/Navbar/mobile-menu/MobileMenu.tsx index a85c1f126f..16f855ee33 100644 --- a/@theme/components/Navbar/mobile-menu/MobileMenu.tsx +++ b/@theme/components/Navbar/mobile-menu/MobileMenu.tsx @@ -9,13 +9,14 @@ import { MobileMenuContent, type MobileMenuKey } from "./MobileMenuContent"; interface MobileMenuProps { isOpen: boolean; onClose: () => void; + onSearch?: () => void; } /** * Mobile Menu Component. * Full-screen slide-out menu for mobile devices. */ -export function MobileMenu({ isOpen, onClose }: MobileMenuProps) { +export function MobileMenu({ isOpen, onClose, onSearch }: MobileMenuProps) { const { useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const [expandedItem, setExpandedItem] = React.useState("Develop"); @@ -37,9 +38,8 @@ export function MobileMenu({ isOpen, onClose }: MobileMenuProps) { }; const handleSearch = () => { - const searchTrigger = document.querySelector('[data-component-name="Search/SearchTrigger"]') as HTMLElement; - if (searchTrigger) { - searchTrigger.click(); + if (onSearch) { + onSearch(); } onClose(); }; diff --git a/@theme/components/Navbar/submenus/Submenu.tsx b/@theme/components/Navbar/submenus/Submenu.tsx index 4762a906a4..9430d7cf92 100644 --- a/@theme/components/Navbar/submenus/Submenu.tsx +++ b/@theme/components/Navbar/submenus/Submenu.tsx @@ -72,7 +72,9 @@ export function Submenu({ variant, isActive, isClosing }: SubmenuProps) { /** Network submenu with theme-aware pattern images */ function NetworkSubmenuContent({ isActive, isClosing }: { isActive: boolean; isClosing: boolean }) { - const [isDarkMode, setIsDarkMode] = React.useState(false); + // Start with null to indicate "not yet determined" - avoids hydration mismatch + // by ensuring server and client both render the same initial state + const [isDarkMode, setIsDarkMode] = React.useState(null); React.useEffect(() => { const checkTheme = () => { @@ -84,9 +86,10 @@ function NetworkSubmenuContent({ isActive, isClosing }: { isActive: boolean; isC return () => observer.disconnect(); }, []); + // Default to light mode patterns until client-side detection runs const patternImages = React.useMemo(() => ({ - lilac: isDarkMode ? darkLilacPattern : resourcesPurplePattern, - green: isDarkMode ? darkInsightsGreenPattern : insightsGreenPattern, + lilac: isDarkMode === true ? darkLilacPattern : resourcesPurplePattern, + green: isDarkMode === true ? darkInsightsGreenPattern : insightsGreenPattern, }), [isDarkMode]); const classNames = [