diff --git a/docs/docs/reference/cli/_category_.yml b/docs/docs/reference/cli/_category_.yml
index 184af4a2efd..392f4efc2e7 100644
--- a/docs/docs/reference/cli/_category_.yml
+++ b/docs/docs/reference/cli/_category_.yml
@@ -1,4 +1,4 @@
position: 4
label: CLI Reference
-collapsible: true
-collapsed: true
\ No newline at end of file
+collapsible: false
+collapsed: false
\ No newline at end of file
diff --git a/docs/docs/reference/project-files/_category_.yml b/docs/docs/reference/project-files/_category_.yml
index 3a6d2a3edd2..846359dbac7 100644
--- a/docs/docs/reference/project-files/_category_.yml
+++ b/docs/docs/reference/project-files/_category_.yml
@@ -1,4 +1,4 @@
position: 00
label: Project Files
-collapsible: true
+collapsible: false
collapsed: false
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
index 1e7a88bf22a..4c8cfcd78c8 100644
--- a/docs/docusaurus.config.js
+++ b/docs/docusaurus.config.js
@@ -130,29 +130,40 @@ const config = {
to: "/",
label: "Docs",
position: "left",
- className: "navbar-docs-link",
activeBaseRegex: "^(?!/(reference|api|contact|notes)).*", // Keep Docs active for all doc pages
},
{
- to: "/reference/project-files",
+ type: "dropdown",
label: "Reference",
position: "left",
- className: "navbar-reference-link",
- activeBasePath: "/reference",
- },
- {
- to: "/api/admin/",
- label: "API",
- position: "left",
- className: "navbar-api-link",
- activeBasePath: "/api/admin",
+ to: "/reference/project-files",
+ className: 'my-custom-dropdown',
+ activeBaseRegex: "^(/reference|/api/admin)",
+ items: [
+ {
+ to: "/reference/project-files",
+ label: "Project Files",
+ },
+ {
+ to: "/reference/cli",
+ label: "CLI",
+ },
+ {
+ to: "/reference/rill-iso-extensions",
+ label: "Rill ISO 8601",
+ },
+ {
+ to: "/api/admin/",
+ label: "REST API",
+ },
+
+ ],
},
{
to: "/contact",
label: "Contact Us",
position: "left",
- className: "navbar-contact-link",
- activeBasePath: "/contact",
+ activeBaseRegex: "^/contact",
},
diff --git a/docs/src/css/_navbar.scss b/docs/src/css/_navbar.scss
index 1d4f1c5dfbd..e06a1442d24 100644
--- a/docs/src/css/_navbar.scss
+++ b/docs/src/css/_navbar.scss
@@ -1,265 +1,370 @@
-
/* =========================
- Search Bar Custom Styles
+ Navbar Styles
========================= */
-.navbar {
- height: 60px;
- padding: 0 16px;
-}
-// all of the navbar items
-.navbar__link {
- height: 70%;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0px 12px;
- position: relative;
- transition: color 0.2s ease; /* Smooth text color transition */
-}
-
-// on hover of navbar items
-.navbar__link:hover {
- background: var(--palette_slate_50);
- border-radius: 8px;
- padding: 12px 12px;
-}
-
-// Dark mode hover adjustment
-[data-theme='dark'] .navbar__link:hover {
- background: rgba(255, 255, 255, 0.1);
-}
-
-// Remove hover background for active items
-.navbar__link--active:hover {
- background: transparent;
-}
-
-// Keep default color on hover (don't change text color)
-.navbar__link.navbar-docs-link:hover,
-.navbar__link.navbar-reference-link:hover,
-.navbar__link.navbar-contact-link:hover,
-.navbar__link.navbar-api-link:hover {
- color: var(--ifm-navbar-link-color);
-}
-
-// But if it's active, keep the primary color even when hovering
-.navbar__link--active.navbar-docs-link:hover,
-.navbar__link--active.navbar-reference-link:hover,
-.navbar__link--active.navbar-contact-link:hover,
-.navbar__link--active.navbar-api-link:hover {
- color: var(--ifm-color-primary);
-}
-
-.navbar button[class*="toggleButton"] {
- background: transparent;
- border: none;
- box-shadow: none;
-}
-
-.navbar button[class*="toggleButton"]:hover {
- background: transparent;
- box-shadow: none;
-}
-/* Active state styling for navbar links */
-.navbar__link--active.navbar-docs-link,
-.navbar__link--active.navbar-reference-link,
-.navbar__link--active.navbar-contact-link,
-.navbar__link--active.navbar-api-link,
-.navbar__link--active.navbar-icon-link {
- color: var(--ifm-color-primary);
-}
-
-/* Underline that only spans text width */
-.navbar__link--active.navbar-docs-link::after,
-.navbar__link--active.navbar-reference-link::after,
-.navbar__link--active.navbar-contact-link::after,
-.navbar__link--active.navbar-api-link::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 50%;
- transform: translateX(-50%);
- height: 2px;
- background: var(--ifm-color-primary);
- border-radius: 1px 1px 0 0;
- width: calc(100% - 24px);
+/* Hide caret for custom dropdown links - global rule */
+.navbar .navbar__item.dropdown:has(.navbar__link.my-custom-dropdown) .menu__caret,
+.navbar .navbar__item.dropdown:has(.navbar__link.my-custom-dropdown) button.menu__caret,
+.navbar .navbar__item.dropdown:has(.navbar__link.my-custom-dropdown) .clean-btn.menu__caret,
+.navbar .navbar__item.dropdown:has(.navbar__link.my-custom-dropdown) .menu__caret svg,
+.navbar .navbar__item.dropdown:has(.navbar__link.my-custom-dropdown) button[class*="caret"],
+.navbar .navbar__item.dropdown:has(.navbar__link.my-custom-dropdown) button[aria-label*="dropdown"],
+.navbar .navbar__item.dropdown:has(.navbar__link.my-custom-dropdown) button[aria-label*="Collapse"] {
+ display: none !important;
+ visibility: hidden !important;
+ opacity: 0 !important;
+ width: 0 !important;
+ height: 0 !important;
}
-
-// NavBar Icon Link (GitHub and Blog) Change to Icons later
-// NavBar Icon Link (GitHub and Blog) Change to Icons later
-.navbar-icon-link {
- width: 24px;
- height: 24px;
- margin-right: 6px;
- margin-left: 6px;
+/* Base navbar link styles - all text black, no decorations */
+.navbar .navbar__link {
+ color: var(--ifm-color-black);
text-decoration: none;
- color: var(--ifm-menu-color);
- font-size: 0.875rem;
font-weight: 500;
- transition: color 0.2s;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.375rem;
+ transition: background-color 0.15s ease;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.03);
+ }
+
+ /* Active links don't need hover effect */
+ &.navbar__link--active:hover {
+ background-color: transparent;
+ }
}
-.navbar-icon-link:hover {
- background: transparent;
- box-shadow: none;
- text-decoration: none;
- color: var(--ifm-menu-color);
+/* Active navbar link - theme color */
+.navbar .navbar__link--active,
+.navbar__item .navbar__link--active {
+ color: var(--ifm-color-primary);
+ text-decoration: underline;
+ text-decoration-thickness: 2px;
+ text-underline-offset: 14px;
+ text-decoration-color: var(--ifm-color-primary);
+ text-decoration-style: solid;
+ text-decoration-skip-ink: none;
+ text-decoration-skip: none;
}
-// NavBar Search Bar
-.navbar .navbar__items--right .DocSearch-Button {
- background: transparent;
- border: 0.5px solid #e5e7eb;
- border-radius: 8px;
- padding: 12px 12px;
- width: 250px; /* Adjust this value to change width */
- min-width: 150px; /* Minimum width */
- height: 100%;
+/* Dropdown items - black text, no decorations */
+/* Target elements with both navbar__item and dropdown classes */
+.navbar .navbar__item.dropdown {
+ /* Dropdown link - inherits base .navbar__link styles, only adds extra right padding for chevron */
+ > .navbar__link {
+ position: relative;
+ padding-right: 2rem;
+ }
+ /* Hide the default chevron icon for custom dropdown links */
+ &:has(.navbar__link.my-custom-dropdown) {
+ .menu__caret,
+ button.menu__caret,
+ .clean-btn.menu__caret,
+ button[class*="caret"],
+ button[aria-label*="dropdown"],
+ button[aria-label*="Collapse"],
+ .navbar__link.my-custom-dropdown ~ .menu__caret,
+ .navbar__link.my-custom-dropdown + .menu__caret,
+ .navbar__link.my-custom-dropdown .menu__caret,
+ .navbar__link.my-custom-dropdown::after {
+ display: none !important;
+ visibility: hidden !important;
+ opacity: 0 !important;
+ width: 0 !important;
+ height: 0 !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ }
+ }
+
+ /* Rotate custom chevron on hover and when dropdown is open */
+ &:has(.navbar__link.my-custom-dropdown) {
+ .navbar__link.my-custom-dropdown .custom-chevron {
+ transition: transform 0.2s ease;
+ }
+
+ &:hover .navbar__link.my-custom-dropdown .custom-chevron,
+ &.dropdown--show .navbar__link.my-custom-dropdown .custom-chevron,
+ .navbar__link.my-custom-dropdown[aria-expanded="true"] .custom-chevron {
+ transform: translateY(0%) rotate(180deg);
+ }
+ }
+ /* Active dropdown - theme color when any sub-page is active */
+ /* JavaScript adds 'navbar__dropdown--has-active' class when dropdown menu has active item */
+ &.navbar__dropdown--has-active > .navbar__link {
+ color: var(--ifm-color-primary);
+ text-decoration: underline;
+ text-decoration-thickness: 2px;
+ text-underline-offset: 14px;
+ text-decoration-color: var(--ifm-color-primary);
+ text-decoration-style: solid;
+ text-decoration-skip-ink: none;
+ text-decoration-skip: none;
+ }
+
+ /* Extend hover area downward to bridge gap to dropdown menu */
position: relative;
- &::after {
- content: "⌘K";
+ &::before {
+ content: '';
position: absolute;
- right: 8px;
- top: 50%;
- transform: translateY(-50%);
- font-size: 0.75rem;
- color: var(--docsearch-muted-color);
- font-family: monospace;
- pointer-events: none;
+ bottom: -8px;
+ left: 0;
+ right: 0;
+ height: 8px;
+ background: transparent;
+ pointer-events: auto;
+ z-index: 1000;
}
}
-// Dark mode specific fixes for Search Bar
-[data-theme='dark'] .navbar .navbar__items--right .DocSearch-Button {
- background: transparent;
- border: 0.5px solid var(--docsearch-muted-color);
- border-radius: 8px;
- padding: 12px 12px;
+/* Dropdown menu - add breathing room between trigger and popover */
+.navbar .dropdown__menu {
+ margin-top: 8px;
}
-[data-theme='dark'] .navbar .navbar__items--right .DocSearch-Button:hover {
- box-shadow: none;
- color: var(--docsearch-muted-color);
- border: 0.5px solid var(--ifm-color-primary);
-}
-
-// On hover of Search Bar
-.navbar .navbar__items--right .DocSearch-Button:hover {
- box-shadow: none;
- color: var(--docsearch-muted-color);
- border: 0.5px solid var(--ifm-color-primary);
+/* Temporarily disable dropdown hover when closing - prevents re-opening from hover bridge */
+.navbar.navbar--closing-dropdown {
+ .navbar__item.dropdown {
+ /* Disable the hover bridge */
+ &::before {
+ pointer-events: none !important;
+ }
+
+ /* Hide dropdown menu regardless of hover state */
+ .dropdown__menu {
+ display: none !important;
+ opacity: 0 !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+ }
+ }
}
-// Search Bar Search Icon
-.navbar .navbar__items--right .DocSearch-Search-Icon {
- height: 16px;
- width: 16px;
-}
+/* Dropdown menu items - black text with hover effects */
+.navbar .dropdown__link {
+ color: var(--ifm-color-black);
+ text-decoration: none;
+ border-radius: 0.375rem;
+ padding: 0.375rem 0.75rem;
+ display: block;
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-black);
+ text-decoration: none;
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ /* Active dropdown menu items - theme color */
+ &.dropdown__link--active {
+ color: var(--ifm-color-primary);
+ text-decoration: none;
-// Search Bar Search Placeholder
-.navbar .navbar__items--right .DocSearch-Button-Placeholder {
- font-size: 0.875rem;
+ }
}
-
-.navbar .navbar__items--right .DocSearch-Button-Keys {
- display: none;
+/* Custom HTML links (GitHub, Blog) */
+.navbar .navbar-icon-link {
+ color: var(--ifm-color-black);
+ text-decoration: none;
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-black);
+ text-decoration: none;
+ }
}
-.DocSearch--active .DocSearch-Footer {
- display: none;
+/* DocSearch button - smaller text */
+.navbar .DocSearch-Button {
+ font-size: 0.875rem; /* 14px */
+ min-width: 16rem; /* Increased width */
+ border: 0.5px solid var(--ifm-color-gray-300, #e5e7eb);
+
+ .DocSearch-Button-Placeholder {
+ font-size: 0.875rem;
+ }
}
-
-
+/* Right-hand navbar items - only for smaller widths */
@media (max-width: 996px) {
- /* Make custom HTML navbar items look like menu__link in mobile menu */
- .navbar .menu__list .menu__link,
- .navbar .menu__list .navbar-icon-link {
- display: flex;
- align-items: center;
- width: 100%;
- gap: 6px;
- padding: 2.5px 9px;
- color: var(--ifm-menu-color);
- font-size: var(--default-font);
- text-decoration: none;
- border-radius: 4px;
- margin: 0;
- background: none;
- box-shadow: none;
+ .navbar .navbar-icon-link {
+ padding: 0.25rem 0rem;
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-primary);
+ }
}
+}
- .navbar .navbar__items--left .navbar-icon-link {
- width: 100%;
- display: flex;
- align-items: center;
+/* Dark mode adjustments */
+[data-theme='dark'] .navbar {
+ .navbar__link {
+ color: var(--ifm-color-white);
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-white);
+ background-color: rgba(255, 255, 255, 0.06);
+ }
+
+ /* Active links don't need hover effect */
+ &.navbar__link--active:hover {
+ background-color: transparent;
+ }
}
-
- .navbar .navbar-icon-link {
- display: none !important;
+
+ /* Active navbar link - theme color in dark mode */
+ .navbar__item--has-active-link .navbar__link,
+ .navbar__item .navbar__link--active,
+ .navbar__link--active {
+ color: var(--ifm-color-primary);
}
-
-}
-
-@media (max-width: 768px) {
- .navbar .navbar__item {
- display: none
+
+ .navbar__item.dropdown {
+ /* Active dropdown in dark mode - theme color */
+ &.navbar__dropdown--has-active > .navbar__link {
+ color: var(--ifm-color-primary);
+ }
}
-}
-
-@media (max-width: 600px) {
- .navbar .navbar__items--right .DocSearch-Button {
- width: 100%;
- min-width: 0;
- margin-left: 0;
- height: 36px;
- box-sizing: border-box;
+
+ /* Dropdown menu in dark mode - maintain breathing room */
+ .dropdown__menu {
+ margin-top: 8px;
}
- .navbar .navbar__items--right .DocSearch-Button::after {
- display: none !important;
+
+ .dropdown__link {
+ color: var(--ifm-color-white);
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-white);
+ background-color: rgba(255, 255, 255, 0.1);
+ }
+
+ /* Active dropdown menu items - theme color */
+ &.dropdown__link--active {
+ color: var(--ifm-color-primary);
+ }
}
-}
-
-
-
-/* Hide text on very small screens for right-side items */
-@media (max-width: 480px) {
- .navbar .navbar__items--right .navbar-icon-link {
- display: none;
+
+ .navbar-icon-link {
+ color: var(--ifm-color-white);
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-white);
+ }
+ }
+
+ /* DocSearch button in dark mode */
+ .DocSearch-Button {
+ border: 0.5px solid rgba(255, 255, 255, 0.2);
+ }
+
+ /* Right-hand navbar items in dark mode - only for smaller widths */
+ @media (max-width: 996px) {
+ .navbar-icon-link {
+ &:hover,
+ &:focus {
+ background-color: rgba(255, 255, 255, 0.1);
+ }
+ }
+
+ /* Search button styling in dark mode */
+ .navbar__item .DocSearch-Button {
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ }
+ }
}
-}
-
-//Mobile Hamburger Menu Header
-
-.navbar-sidebar__brand {
- height: 60px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 16px;
-}
-
-.navbar-sidebar__brand .mobile-nav-icon-links {
- display: flex;
- align-items: center;
- gap: 24px;
-}
-
-.navbar-sidebar__brand .mobile-nav-icon-link {
- display: inline-flex;
- align-items: center;
- justify-content: center;
}
-.navbar-sidebar__brand .mobile-nav-icon-link img {
- width: 100%;
- height: 100%;
+/* =========================
+ Mobile Sidebar Panel Styles
+ ========================= */
+.navbar-sidebar {
+ /* Fix spacing for navbar items in mobile sidebar */
+ .navbar-sidebar__item {
+ margin: 0;
+ padding: 0;
+ }
+
+ /* Fix spacing for dropdown items in mobile sidebar */
+ .navbar-sidebar__item.dropdown {
+ .dropdown__menu {
+ padding: 0.5rem 0;
+ margin: 0;
+ }
+
+ .dropdown__link {
+ padding: 0.625rem 1.5rem 0.625rem 1.75rem;
+ margin: 0.125rem 0;
+ border-radius: 0.375rem;
+ }
+ }
+
+ /* Right-hand items in mobile sidebar - match left-hand styling */
+ .navbar-sidebar__item {
+ /* Apply same styling to search button */
+ .DocSearch-Button {
+ padding: 0.75rem 1rem;
+ margin: 0.25rem 0;
+ border-radius: 0.375rem;
+ width: 100%;
+ justify-content: flex-start;
+ font-size: 0.875rem; /* 14px */
+
+ .DocSearch-Button-Placeholder {
+ font-size: 0.875rem;
+ }
+ }
+
+ /* Apply same styling to HTML links (GitHub, Blog) to match left-side items */
+ .navbar-icon-link {
+ display: block;
+ padding: 0.2rem 0rem;
+ margin: 0.25rem 0;
+ border-radius: 0.375rem;
+ color: var(--ifm-color-black);
+ text-decoration: none;
+ font-weight: 500;
+ font-size: 0.875rem;
+ transition: background-color 0.2s ease;
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-primary);
+ background-color: rgba(0, 0, 0, 0.05);
+ font-weight: 600;
+ }
+ }
+ }
+
+ /* Dark mode for mobile sidebar HTML links */
+ [data-theme='dark'] & {
+ .navbar-sidebar__item {
+ .navbar-icon-link {
+ color: var(--ifm-color-white);
+
+ &:hover,
+ &:focus {
+ color: var(--ifm-color-primary);
+ background-color: rgba(255, 255, 255, 0.1);
+ }
+ }
+ }
+ }
+
+ /* Ensure consistent spacing between all items */
+ .navbar-sidebar__items {
+ padding: 1rem 0.5rem;
+
+ > .navbar-sidebar__item {
+ margin-bottom: 0.25rem;
+ }
+ }
}
diff --git a/docs/src/theme/Navbar/index.js b/docs/src/theme/Navbar/index.js
index 20c9deffc37..986486c3675 100644
--- a/docs/src/theme/Navbar/index.js
+++ b/docs/src/theme/Navbar/index.js
@@ -1,69 +1,41 @@
import { useColorMode } from '@docusaurus/theme-common';
+import { useLocation } from '@docusaurus/router';
import Navbar from '@theme-original/Navbar';
import { useEffect, useLayoutEffect } from 'react';
-const MOBILE_ICON_LINKS = [
- {
- href: 'https://github.com/rilldata/rill',
- label: 'GitHub',
- src: '/icons/Github.svg',
- },
- {
- href: '/notes',
- label: 'Release Notes',
- src: '/icons/ReleaseNotes.svg',
- },
- {
- href: 'https://www.rilldata.com/blog',
- label: 'Blog',
- src: '/icons/MessageSquareQuote.svg',
- },
-];
-
-function createIconLink({ href, label, src }) {
- const anchor = document.createElement('a');
- anchor.href = href;
- anchor.target = '_blank';
- anchor.rel = 'noopener noreferrer';
- anchor.className = 'mobile-nav-icon-link';
- anchor.setAttribute('aria-label', label);
-
- const img = document.createElement('img');
- img.src = src;
- img.alt = label;
- img.width = 24;
- img.height = 24;
-
- anchor.appendChild(img);
- return anchor;
-}
-
-function ensureSidebarIcons() {
- const brand = document.querySelector('.navbar-sidebar__brand');
- // Check if icons already exist
- if (!brand || brand.querySelector('.mobile-nav-icon-links')) {
- return;
- }
-
- const container = document.createElement('div');
- container.className = 'mobile-nav-icon-links';
-
- MOBILE_ICON_LINKS.forEach((link) => {
- container.appendChild(createIconLink(link));
- });
-
- const closeButton = brand.querySelector('.navbar-sidebar__close');
- if (closeButton) {
- brand.insertBefore(container, closeButton);
- } else {
- brand.appendChild(container);
- }
-}
+// Mobile icon functionality removed
export default function NavbarWrapper(props) {
// We only need colorMode to determine which icon to show.
// The toggle logic is handled by the original button's onClick.
const { colorMode } = useColorMode();
+ const location = useLocation();
+
+ // Close all open dropdowns when route changes
+ // This fixes the issue where hover-based dropdowns stay open after clicking an item
+ useEffect(() => {
+ const closeAllDropdowns = () => {
+ // Remove dropdown--show class from all dropdowns
+ const openDropdowns = document.querySelectorAll('.navbar__item.dropdown.dropdown--show');
+ openDropdowns.forEach((dropdown) => {
+ dropdown.classList.remove('dropdown--show');
+ });
+
+ // Also reset aria-expanded attributes
+ const expandedLinks = document.querySelectorAll('.navbar__link[aria-expanded="true"]');
+ expandedLinks.forEach((link) => {
+ link.setAttribute('aria-expanded', 'false');
+ });
+
+ // Blur any focused navbar elements to prevent hover state from re-triggering
+ const activeElement = document.activeElement;
+ if (activeElement && activeElement.closest('.navbar__item.dropdown')) {
+ activeElement.blur();
+ }
+ };
+
+ closeAllDropdowns();
+ }, [location.pathname]);
// Handle Dark Mode Toggle Icons
// useLayoutEffect fires synchronously before paint, reducing flicker
@@ -80,9 +52,9 @@ export default function NavbarWrapper(props) {
if (!iconContainer) {
iconContainer = document.createElement('span');
iconContainer.className = 'icon-container';
-
+
// Clear existing Docusaurus toggle content (text/emojis)
- btn.innerHTML = '';
+ btn.innerHTML = '';
btn.appendChild(iconContainer);
}
@@ -90,7 +62,7 @@ export default function NavbarWrapper(props) {
// If Dark Mode -> Show Sun (to switch to Light)
// If Light Mode -> Show Moon (to switch to Dark)
const isDark = colorMode === 'dark';
-
+
iconContainer.innerHTML = `
`;
-
+
btn.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode');
});
}, [colorMode]);
- // Handle Mobile Sidebar Icons
+ // Mobile sidebar icons removed - no longer needed
+
+ // Consolidated MutationObserver for navbar updates
useEffect(() => {
- // Observer to inject icons when the mobile menu opens/renders
- const observer = new MutationObserver(() => {
- ensureSidebarIcons();
- });
+ const markActiveDropdowns = () => {
+ // Target elements with both navbar__item and dropdown classes
+ const dropdownItems = document.querySelectorAll('.navbar__item.dropdown');
+ dropdownItems.forEach((dropdownItem) => {
+ const activeDropdownLink = dropdownItem.querySelector('.dropdown__link--active');
+ if (activeDropdownLink) {
+ dropdownItem.classList.add('navbar__dropdown--has-active');
+ } else {
+ dropdownItem.classList.remove('navbar__dropdown--has-active');
+ }
+ });
+ };
+
+ const markActiveNavItems = () => {
+ // Target non-dropdown navbar items
+ const navItems = document.querySelectorAll('.navbar__item:not(.dropdown)');
+ navItems.forEach((navItem) => {
+ const activeLink = navItem.querySelector('.navbar__link--active');
+ if (activeLink) {
+ navItem.classList.add('navbar__item--has-active-link');
+ } else {
+ navItem.classList.remove('navbar__item--has-active-link');
+ }
+ });
+ };
+
+ const addDataTextAttributes = () => {
+ const navLinks = document.querySelectorAll('.navbar__link');
+ navLinks.forEach((link) => {
+ // Only add if not already present
+ if (!link.hasAttribute('data-text')) {
+ const text = link.textContent?.trim() || '';
+ if (text) {
+ link.setAttribute('data-text', text);
+ }
+ }
+ });
+ };
+
+ const replaceCustomDropdownCarets = () => {
+ // Add custom SVG chevron for custom dropdown links
+ // CSS already hides the default caret, so we just need to add our custom one
+ const customDropdownLinks = document.querySelectorAll('.navbar__link.my-custom-dropdown');
+ customDropdownLinks.forEach((link) => {
+ const dropdownItem = link.closest('.navbar__item.dropdown');
+ if (dropdownItem && !link.hasAttribute('data-custom-chevron-added')) {
+ // Mark as processed
+ link.setAttribute('data-custom-chevron-added', 'true');
+
+ // Create a container for the custom chevron
+ let chevronContainer = link.querySelector('.custom-chevron');
+ if (!chevronContainer) {
+ chevronContainer = document.createElement('span');
+ chevronContainer.className = 'custom-chevron';
+ chevronContainer.style.position = 'absolute';
+ chevronContainer.style.right = '0.5rem';
+ chevronContainer.style.top = '30%';
+ chevronContainer.style.transform = 'translateY(0%)';
+ chevronContainer.style.transition = 'transform 0.2s ease';
+ chevronContainer.style.opacity = '0.7';
+ chevronContainer.style.width = '14px';
+ chevronContainer.style.height = '14px';
+ chevronContainer.style.display = 'block';
+ chevronContainer.style.pointerEvents = 'none';
+ link.style.position = 'relative';
+ link.appendChild(chevronContainer);
+ }
+
+ // Clear and add custom SVG chevron
+ chevronContainer.innerHTML = '';
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('height', '14px');
+ svg.setAttribute('viewBox', '0 0 24 24');
+ svg.setAttribute('fill', 'currentColor');
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
+ svg.style.width = '14px';
+ svg.style.height = '14px';
+ svg.style.display = 'block';
+
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ path.setAttribute('fill-rule', 'evenodd');
+ path.setAttribute('clip-rule', 'evenodd');
+ path.setAttribute('d', 'M19.189 9.43683C19.3842 9.63209 19.3842 9.94867 19.189 10.1439L11.9999 17.333L4.81075 10.1439C4.61549 9.94867 4.61549 9.63209 4.81075 9.43683L5.98898 8.2586C6.18424 8.06334 6.50082 8.06334 6.69609 8.2586L11.9999 13.5624L17.3036 8.2586C17.4989 8.06334 17.8155 8.06334 18.0108 8.2586L19.189 9.43683Z');
+
+ svg.appendChild(path);
+ chevronContainer.appendChild(svg);
+
+ // CSS will handle the rotation on hover/open
+ }
+ });
+ };
+
+ // Combined update function
+ const updateNavbar = () => {
+ markActiveDropdowns();
+ markActiveNavItems();
+ addDataTextAttributes();
+ replaceCustomDropdownCarets();
+ };
+ // Run on mount and when DOM changes
+ updateNavbar();
+
+ // Staggered timeouts to handle dynamically rendered navbar elements.
+ // Docusaurus may hydrate or lazy-load navbar items at different times,
+ // especially for dropdowns and client-side navigation. These delays
+ // ensure our custom styling (data-text attrs, active states, chevrons)
+ // is applied even if elements render after initial mount.
+ setTimeout(updateNavbar, 100);
+ setTimeout(updateNavbar, 500);
+ setTimeout(updateNavbar, 1000);
+
+ const observer = new MutationObserver(updateNavbar);
observer.observe(document.body, { childList: true, subtree: true });
- ensureSidebarIcons(); // Initial check
return () => {
observer.disconnect();
- const existing = document.querySelector('.mobile-nav-icon-links');
- if (existing) {
- existing.remove();
- }
};
}, []);
diff --git a/docs/src/theme/NavbarItem/DropdownNavbarItem.js b/docs/src/theme/NavbarItem/DropdownNavbarItem.js
new file mode 100644
index 00000000000..da1a5c3536e
--- /dev/null
+++ b/docs/src/theme/NavbarItem/DropdownNavbarItem.js
@@ -0,0 +1,65 @@
+import React, { useEffect, useCallback } from 'react';
+import DropdownNavbarItem from '@theme-original/NavbarItem/DropdownNavbarItem';
+import { useLocation } from '@docusaurus/router';
+
+/**
+ * Swizzled DropdownNavbarItem component that closes dropdown on item click.
+ *
+ * This fixes the issue where hover-based dropdowns stay open after clicking
+ * a menu item because the ::before pseudo-element (hover bridge) keeps the
+ * dropdown open if the mouse is still in that area.
+ */
+export default function DropdownNavbarItemWrapper(props) {
+ const location = useLocation();
+
+ // Helper to close all dropdowns and temporarily disable hover
+ const closeAllDropdowns = useCallback(() => {
+ // Add a class to the navbar to temporarily disable hover
+ const navbar = document.querySelector('.navbar');
+ if (navbar) {
+ navbar.classList.add('navbar--closing-dropdown');
+ }
+
+ // Remove dropdown--show class from all dropdowns
+ const openDropdowns = document.querySelectorAll('.navbar__item.dropdown.dropdown--show');
+ openDropdowns.forEach((dropdown) => {
+ dropdown.classList.remove('dropdown--show');
+ });
+
+ // Reset aria-expanded attributes and blur
+ const expandedLinks = document.querySelectorAll('.navbar__link[aria-expanded="true"]');
+ expandedLinks.forEach((link) => {
+ link.setAttribute('aria-expanded', 'false');
+ link.blur();
+ });
+
+ // Remove the closing class after mouse has likely moved away
+ setTimeout(() => {
+ if (navbar) {
+ navbar.classList.remove('navbar--closing-dropdown');
+ }
+ }, 300);
+ }, []);
+
+ // Close dropdown when route changes
+ useEffect(() => {
+ closeAllDropdowns();
+ }, [location.pathname, closeAllDropdowns]);
+
+ // Global click handler for dropdown links
+ useEffect(() => {
+ const handleClick = (e) => {
+ const dropdownLink = e.target.closest('.dropdown__link');
+ if (dropdownLink) {
+ // Immediately close
+ closeAllDropdowns();
+ }
+ };
+
+ // Use capture phase to catch events before they bubble
+ document.addEventListener('click', handleClick, true);
+ return () => document.removeEventListener('click', handleClick, true);
+ }, [closeAllDropdowns]);
+
+ return ;
+}