mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-29 15:37:48 +00:00
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:
@@ -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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user