Integrate search functionality into Navbar and MobileMenu components, utilizing Redocly's search dialog. Update AlertBanner to prevent hydration mismatch with null initial state for displayDate. Adjust NavControls and NetworkSubmenu to handle theme detection more effectively.

This commit is contained in:
akcodez
2026-01-13 10:05:02 -08:00
parent 9006dc3812
commit 7fd39abb2b
5 changed files with 28 additions and 22 deletions

View File

@@ -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<NodeJS.Timeout | null>(null);
const closingTimeoutRef = React.useRef<NodeJS.Timeout | null>(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 = {}) {
<div className="bds-navbar__content">
<NavLogo />
<NavItems activeSubmenu={activeSubmenu} onSubmenuEnter={handleSubmenuMouseEnter} />
<NavControls />
<NavControls onSearch={onSearchOpen} />
<HamburgerButton onClick={handleHamburgerClick} />
</div>
{/* Submenus positioned relative to navbar */}
@@ -126,7 +131,9 @@ export function Navbar(_props: NavbarProps = {}) {
<NetworkSubmenu isActive={activeSubmenu === 'Network'} isClosing={closingSubmenu === 'Network'} />
</div>
</header>
<MobileMenu isOpen={mobileMenuOpen} onClose={handleMobileMenuClose} />
<MobileMenu isOpen={mobileMenuOpen} onClose={handleMobileMenuClose} onSearch={onSearchOpen} />
{/* Render SearchDialog when open - this is the actual search modal */}
{isSearchOpen && <SearchDialog onClose={onSearchClose} />}
</>
);
}

View File

@@ -18,7 +18,8 @@ export function AlertBanner({ message, button, link, show }: AlertBannerProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const bannerRef = React.useRef<HTMLAnchorElement>(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<string | null>(null);
React.useEffect(() => {
const calculateCountdown = () => {
@@ -69,7 +70,7 @@ export function AlertBanner({ message, button, link, show }: AlertBannerProps) {
>
<div className="banner-event-details">
<div className="event-info">{translate(message)}</div>
<div className="event-date">{displayDate}</div>
<div className="event-date">{displayDate ?? "JUNE 10-12"}</div>
</div>
<div className="banner-button">
<div className="button-text">{translate(button)}</div>

View File

@@ -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 (
<div className="bds-navbar__controls">
<SearchButton onClick={handleSearch} />
<SearchButton onClick={onSearch} />
<ModeToggleButton onClick={handleModeToggle} />
<LanguagePill onClick={handleLanguageClick} />
</div>

View File

@@ -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<string | null>("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();
};

View File

@@ -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<boolean | null>(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 = [