// Replaces the top navbar with our custom XRPL.org top navbar import React from 'react' import { useThemeConfig, useThemeHooks } from "@redocly/theme/core/hooks"; import { BdsLink } from "../../../shared/components/Link/Link"; import moment from "moment-timezone"; // Icons import xrpSymbolBlack from "../../../static/img/navbar/xrp-symbol-black.svg"; import xrpLogotypeBlack from "../../../static/img/navbar/xrp-logotype-black.svg"; import searchIcon from "../../../static/img/navbar/search-icon.svg"; import modeToggleIcon from "../../../static/img/navbar/mode-toggle.svg"; import globeIcon from "../../../static/img/navbar/globe-icon.svg"; import chevronDown from "../../../static/img/navbar/chevron-down.svg"; import hamburgerIcon from "../../../static/img/navbar/hamburger-icon.svg"; import arrowUpRight from "../../../static/img/icons/arrow-up-right-custom.svg"; // Wallet icons for submenu import greenWallet from "../../../static/img/navbar/green-wallet.svg"; import lilacWallet from "../../../static/img/navbar/lilac-wallet.svg"; import yellowWallet from "../../../static/img/navbar/yellow-wallet.svg"; import pinkWallet from "../../../static/img/navbar/pink-wallet.svg"; import blueWallet from "../../../static/img/navbar/blue-wallet.svg"; // Network submenu pattern images import resourcesPurplePattern from "../../../static/img/navbar/resources-purple.svg"; import insightsGreenPattern from "../../../static/img/navbar/insights-green.svg"; import darkInsightsGreenPattern from "../../../static/img/navbar/dark-insights-green.svg"; import darkLilacPattern from "../../../static/img/navbar/dark-lilac.svg"; // Alert Banner Configuration const alertBanner = { show: false, message: "APEX 2025", button: "REGISTER", link: "https://www.xrpledgerapex.com/?utm_source=xrplwebsite&utm_medium=direct&utm_campaign=xrpl-event-ho-xrplapex-glb-2025-q1_xrplwebsite_ari_arp_bf_rsvp&utm_content=cta_btn_english_pencilbanner" }; // Nav items with submenu support const navItems = [ { label: "Develop", labelTranslationKey: "navbar.develop", href: "/docs", hasSubmenu: true }, { label: "Use Cases", labelTranslationKey: "navbar.usecases", href: "/about/uses", hasSubmenu: true }, { label: "Community", labelTranslationKey: "navbar.community", href: "/community", hasSubmenu: true }, { label: "Network", labelTranslationKey: "navbar.network", href: "/docs/concepts/networks-and-servers", hasSubmenu: true }, ]; // Wallet icon mapping const walletIcons: Record = { green: greenWallet, lilac: lilacWallet, yellow: yellowWallet, pink: pinkWallet, blue: blueWallet, }; // Types for submenu data interface SubmenuChild { label: string; href: string; active?: boolean; } interface SubmenuItemBase { label: string; href: string; icon: string; } interface SubmenuItemWithChildren extends SubmenuItemBase { children: SubmenuChild[]; } type SubmenuItem = SubmenuItemBase | SubmenuItemWithChildren; // Develop submenu data structure const developSubmenuData: { left: SubmenuItemBase[]; right: SubmenuItemWithChildren[]; } = { left: [ { label: "Developer's Home", href: "/docs", icon: "green" }, { label: "Learn", href: "/docs/tutorials", icon: "lilac" }, { label: "Code Samples", href: "/_code-samples", icon: "yellow" }, ], right: [ { label: "Docs", href: "/docs", icon: "pink", children: [ { label: "API Reference", href: "/docs/references" }, { label: "Tutorials", href: "/docs/tutorials" }, { label: "Concepts", href: "/docs/concepts" }, { label: "Infrastructure", href: "/docs/infrastructure" }, ], }, { label: "Client Libraries", href: "/docs/references/client-libraries", icon: "blue", children: [ { label: "JavaScript", href: "/docs/references/xrpljs" }, { label: "Python", href: "/docs/references/xrpl-py" }, { label: "PHP", href: "/docs/references/xrpl-php" }, { label: "Go", href: "/docs/references/xrpl-go" }, ], }, ], }; // Use Cases submenu data structure const useCasesSubmenuData: { left: SubmenuItemWithChildren[]; right: SubmenuItemWithChildren[]; } = { left: [ { label: "Payments", href: "/about/uses/payments", icon: "green", children: [ { label: "Direct XRP Payments", href: "/about/uses/direct-xrp-payments" }, { label: "Cross-currency Payments", href: "/about/uses/cross-currency-payments" }, { label: "Escrow", href: "/about/uses/escrow" }, { label: "Checks", href: "/about/uses/checks" }, ], }, { label: "Tokenization", href: "/about/uses/tokenization", icon: "pink", children: [ { label: "Stablecoin", href: "/about/uses/stablecoin" }, { label: "NFT", href: "/about/uses/nft" }, ], }, ], right: [ { label: "Credit", href: "/about/uses/credit", icon: "lilac", children: [ { label: "Lending", href: "/about/uses/lending" }, { label: "Collateralization", href: "/about/uses/collateralization" }, { label: "Sustainability", href: "/about/uses/sustainability" }, ], }, { label: "Trading", href: "/about/uses/trading", icon: "yellow", children: [ { label: "DEX", href: "/about/uses/dex" }, { label: "Permissioned Trading", href: "/about/uses/permissioned-trading" }, { label: "AMM", href: "/about/uses/amm" }, ], }, ], }; // Community submenu data structure // Mixed layout: some sections have children, some don't const communitySubmenuData: { left: SubmenuItem[]; right: SubmenuItem[]; } = { left: [ { label: "Community", href: "/community", icon: "pink", children: [ { label: "Events", href: "/community/events" }, { label: "News", href: "/blog", active: true }, { label: "Blog", href: "/blog" }, { label: "Marketplace", href: "/community/marketplace" }, { label: "Partner Connect", href: "/community/partner-connect" }, ], }, { label: "Funding", href: "/community/developer-funding", icon: "yellow" }, ], right: [ { label: "Contribute", href: "/resources/contribute-documentation", icon: "blue", children: [ { label: "Ecosystem Map", href: "/community/ecosystem-map" }, { label: "Bug Bounty", href: "/community/bug-bounty" }, { label: "Research", href: "/community/research" }, ], }, { label: "Creators", href: "/community/ambassadors", icon: "green" }, ], }; // Network submenu data structure - interface for sections with decorative images interface NetworkSubmenuSection { label: string; href: string; icon: string; children: SubmenuChild[]; patternColor: 'lilac' | 'green'; } // Network submenu data - 2 sections side by side with decorative images const networkSubmenuData: NetworkSubmenuSection[] = [ { label: "Resources", href: "/docs/concepts/networks-and-servers", icon: "pink", children: [ { label: "Validators", href: "/docs/concepts/networks-and-servers/validators" }, { label: "Governance", href: "/docs/concepts/networks-and-servers/governance", active: true }, { label: "XRPL Roadmap", href: "/docs/concepts/networks-and-servers/xrpl-roadmap" }, ], patternColor: 'lilac', }, { label: "Insights", href: "/docs/concepts/networks-and-servers/insights", icon: "green", children: [ { label: "Explorer", href: "https://livenet.xrpl.org" }, { label: "Data Dashboard", href: "/docs/concepts/networks-and-servers/data-dashboard" }, { label: "Amendment Voting Status", href: "/docs/concepts/networks-and-servers/amendments" }, ], patternColor: 'green', }, ]; // Internal Arrow Icon Component for submenu parent links // Uses same pattern as LinkArrow: chevron (static) + horizontal line (animates away on hover) function SubmenuArrow({ className, color = "currentColor" }: { className?: string; color?: string }) { return ( ); } // Full Arrow for submenu child links (no animation, just show/hide) // Shows full arrow (→) not just chevron (>) function SubmenuChildArrow({ className, color = "currentColor" }: { className?: string; color?: string }) { return ( ); } // Alert Banner Component export function AlertBanner({ message, button, link, show }) { const { useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const bannerRef = React.useRef(null); const [displayDate, setDisplayDate] = React.useState("JUNE 10-12"); React.useEffect(() => { const calculateCountdown = () => { const target = moment.tz('2025-06-11 08:00:00', 'Asia/Singapore'); const now = moment(); const daysUntil = target.diff(now, 'days'); let newDisplayDate = "JUNE 10-12"; if (daysUntil > 0) { newDisplayDate = daysUntil === 1 ? 'IN 1 DAY' : `IN ${daysUntil} DAYS`; } else if (daysUntil === 0) { const hoursUntil = target.diff(now, 'hours'); newDisplayDate = hoursUntil > 0 ? 'TODAY' : "JUNE 10-12"; } setDisplayDate(newDisplayDate); }; calculateCountdown(); const interval = setInterval(calculateCountdown, 60 * 60 * 1000); return () => clearInterval(interval); }, []); React.useEffect(() => { const banner = bannerRef.current; if (!banner) return; const handleMouseEnter = () => { banner.classList.add("has-hover"); }; banner.addEventListener("mouseenter", handleMouseEnter); return () => { banner.removeEventListener("mouseenter", handleMouseEnter); }; }, []); if (!show) return null; return (
{translate(message)}
{displayDate}
{translate(button)}
Get Tickets Icon
); } // Logo Component - Shows symbol on desktop/mobile, full logotype on tablet function NavLogo() { return ( XRP Ledger XRP Ledger ); } // Desktop Develop Submenu Component function DevelopSubmenu({ isActive, isClosing }: { isActive: boolean; isClosing: boolean }) { const classNames = [ 'bds-submenu', isActive ? 'bds-submenu--active' : '', isClosing ? 'bds-submenu--closing' : '', ].filter(Boolean).join(' '); return (
{/* Left Column */}
{developSubmenuData.left.map((item) => (
{item.label}
))}
{/* Right Column */}
{developSubmenuData.right.map((section) => (
{section.label} {section.children && (
{section.children.map((child) => ( {child.label} ))}
)}
))}
); } // Desktop Use Cases Submenu Component function UseCasesSubmenu({ isActive, isClosing }: { isActive: boolean; isClosing: boolean }) { const classNames = [ 'bds-submenu', 'bds-submenu--use-cases', isActive ? 'bds-submenu--active' : '', isClosing ? 'bds-submenu--closing' : '', ].filter(Boolean).join(' '); return (
{/* Left Column */}
{useCasesSubmenuData.left.map((section) => (
{section.label}
{section.children.map((child) => ( {child.label} ))}
))}
{/* Right Column */}
{useCasesSubmenuData.right.map((section) => (
{section.label}
{section.children.map((child) => ( {child.label} ))}
))}
); } // Desktop Community Submenu Component // Mixed layout: some sections have children, some are header-only function CommunitySubmenu({ isActive, isClosing }: { isActive: boolean; isClosing: boolean }) { const classNames = [ 'bds-submenu', 'bds-submenu--community', isActive ? 'bds-submenu--active' : '', isClosing ? 'bds-submenu--closing' : '', ].filter(Boolean).join(' '); return (
{/* Left Column */}
{communitySubmenuData.left.map((item) => (
{item.label} {hasChildren(item) && (
{item.children.map((child) => ( {child.label} ))}
)}
))}
{/* Right Column */}
{communitySubmenuData.right.map((item) => (
{item.label} {hasChildren(item) && (
{item.children.map((child) => ( {child.label} ))}
)}
))}
); } // Desktop Network Submenu Component // Two sections side by side with decorative images at bottom function NetworkSubmenu({ isActive, isClosing }: { isActive: boolean; isClosing: boolean }) { const [isDarkMode, setIsDarkMode] = React.useState(false); // Detect theme changes React.useEffect(() => { const checkTheme = () => { setIsDarkMode(document.documentElement.classList.contains('dark')); }; checkTheme(); // Listen for theme changes const observer = new MutationObserver(checkTheme); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); return () => observer.disconnect(); }, []); // Pattern image mapping - theme-aware const patternImages: Record<'lilac' | 'green', string> = React.useMemo(() => ({ lilac: isDarkMode ? darkLilacPattern : resourcesPurplePattern, green: isDarkMode ? darkInsightsGreenPattern : insightsGreenPattern, }), [isDarkMode]); const classNames = [ 'bds-submenu', 'bds-submenu--network', isActive ? 'bds-submenu--active' : '', isClosing ? 'bds-submenu--closing' : '', ].filter(Boolean).join(' '); return (
{networkSubmenuData.map((section) => (
{/* Header */} {section.label} {/* Content area with links and pattern */}
{/* Links list */}
{section.children.map((child) => ( {child.label} ))}
{/* Decorative pattern */}
))}
); } // Nav Items Component - Centered navigation links with submenu support function NavItems({ activeSubmenu, onSubmenuEnter }: { activeSubmenu: string | null; onSubmenuEnter: (itemLabel: string) => void; }) { const { useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const [activeItem, setActiveItem] = React.useState(null); const handleMouseEnter = (itemLabel: string, hasSubmenu: boolean) => { setActiveItem(itemLabel); if (hasSubmenu) { onSubmenuEnter(itemLabel); } }; const handleMouseLeave = (hasSubmenu: boolean) => { if (!hasSubmenu) { setActiveItem(null); } // Don't close submenu on leave - let the parent Navbar handle that }; // Sync activeItem with activeSubmenu state React.useEffect(() => { if (!activeSubmenu) { setActiveItem(null); } }, [activeSubmenu]); return ( ); } // Search Button Component function SearchButton({ onClick }: { onClick?: () => void }) { return ( ); } // Mode Toggle Button Component function ModeToggleButton({ onClick }: { onClick?: () => void }) { return ( ); } // Language Pill Button Component function LanguagePill({ onClick }: { onClick?: () => void }) { return ( ); } // Nav Controls Component - Right side icons and language pill function NavControls() { const handleSearch = () => { // Phase 1: Basic click handler - will be enhanced in future phases const searchTrigger = document.querySelector('[data-component-name="Search/SearchTrigger"]') as HTMLElement; if (searchTrigger) { searchTrigger.click(); } }; const handleModeToggle = () => { // Phase 1: Basic theme toggle const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark"; window.localStorage.setItem("user-prefers-color", newTheme); document.body.style.transition = "background-color .2s ease"; document.documentElement.classList.remove("dark", "light"); document.documentElement.classList.add(newTheme); }; const handleLanguageClick = () => { // Phase 1: Placeholder - language selection will be enhanced in future phases console.log("Language selector clicked"); }; return (
); } // Hamburger Menu Button Component - Mobile only function HamburgerButton({ onClick }: { onClick?: () => void }) { return ( ); } // Close Icon Component for Mobile Menu function CloseIcon() { return ( ); } // Chevron Icon Component for Mobile Accordion function ChevronIcon({ expanded }: { expanded: boolean }) { return ( ); } // Type guard to check if item has children function hasChildren(item: SubmenuItem): item is SubmenuItemWithChildren { return 'children' in item && Array.isArray((item as SubmenuItemWithChildren).children); } // Mobile Menu Develop Content (Accordion content for Develop section) function MobileMenuDevelopContent() { // Flatten the submenu data for mobile single-column layout const mobileItems: SubmenuItem[] = [ ...developSubmenuData.left, ...developSubmenuData.right, ]; return (
{mobileItems.map((item) => ( {item.label} {/* Show children if they exist (for Docs and Client Libraries) */} {hasChildren(item) && (
{item.children.map((child) => ( {child.label} ))}
)}
))}
); } // Mobile Menu Use Cases Content (Accordion content for Use Cases section) function MobileMenuUseCasesContent() { // Flatten the submenu data for mobile single-column layout const mobileItems: SubmenuItemWithChildren[] = [ ...useCasesSubmenuData.left, ...useCasesSubmenuData.right, ]; return (
{mobileItems.map((item) => ( {item.label} {/* All Use Cases items have children */}
{item.children.map((child) => ( {child.label} ))}
))}
); } // Mobile Menu Community Content (Accordion content for Community section) function MobileMenuCommunityContent() { // Flatten the submenu data for mobile single-column layout const mobileItems: SubmenuItem[] = [ ...communitySubmenuData.left, ...communitySubmenuData.right, ]; return (
{mobileItems.map((item) => ( {item.label} {/* Show children if they exist */} {hasChildren(item) && (
{item.children.map((child) => ( {child.label} ))}
)}
))}
); } // Mobile Menu Network Content (Accordion content for Network section) function MobileMenuNetworkContent() { return (
{networkSubmenuData.map((section) => ( {section.label} {/* Network sections always have children */}
{section.children.map((child) => ( {child.label} ))}
))}
); } // Mobile Menu Component function MobileMenu({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { const { useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const [expandedItem, setExpandedItem] = React.useState("Develop"); // Handle body scroll lock React.useEffect(() => { if (isOpen) { document.body.classList.add('bds-mobile-menu-open'); } else { document.body.classList.remove('bds-mobile-menu-open'); } return () => { document.body.classList.remove('bds-mobile-menu-open'); }; }, [isOpen]); const toggleAccordion = (item: string) => { setExpandedItem(expandedItem === item ? null : item); }; const handleSearch = () => { const searchTrigger = document.querySelector('[data-component-name="Search/SearchTrigger"]') as HTMLElement; if (searchTrigger) { searchTrigger.click(); } onClose(); }; const handleModeToggle = () => { const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark"; window.localStorage.setItem("user-prefers-color", newTheme); document.body.style.transition = "background-color .2s ease"; document.documentElement.classList.remove("dark", "light"); document.documentElement.classList.add(newTheme); }; return (
{/* Header */}
XRP Ledger
{/* Content */}
{navItems.map((item) => ( {item.hasSubmenu && (
{item.label === 'Develop' && } {item.label === 'Use Cases' && } {item.label === 'Community' && } {item.label === 'Network' && }
)}
))}
{/* Footer */}
); } // Main Navbar Component export function Navbar(props) { 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); 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); }; // 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)}>
); } // Legacy exports for backwards compatibility (can be removed after full migration) export function NavWrapper(props) { return ; } export function TopNavCollapsible({ children }) { return null; // Phase 1: Not needed } export function NavDropdown(props) { return null; // Phase 1: Submenus not implemented yet } export function NavControls_Legacy(props) { return null; // Phase 1: Using new NavControls } export function MobileMenuIcon() { return null; // Phase 1: Using new HamburgerButton } export function GetStartedButton() { return null; // Phase 1: Not in new design } export function NavItems_Legacy(props) { return null; // Phase 1: Using new NavItems } export function NavItem(props) { return null; // Phase 1: Using inline rendering } export function LogoBlock(props) { return null; // Phase 1: Using new NavLogo } export class ThemeToggle extends React.Component { render() { return null; // Phase 1: Using new ModeToggleButton } }