Skip to content

Redesigning Navbar Using TailwindCSS #589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/theme/Navbar/MobileSidebar/Header/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate';
import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle';
import IconClose from '@theme/Icon/Close';
import NavbarLogo from '@theme/Navbar/Logo';
function CloseButton() {
const mobileSidebar = useNavbarMobileSidebar();
return (
<button
type="button"
aria-label={translate({
id: 'theme.docs.sidebar.closeSidebarButtonAriaLabel',
message: 'Close navigation bar',
description: 'The ARIA label for close button of mobile sidebar',
})}
className="clean-btn navbar-sidebar__close"
onClick={() => mobileSidebar.toggle()}>
<IconClose color="var(--ifm-color-emphasis-600)" />
</button>
);
}
export default function NavbarMobileSidebarHeader() {
return (
<div className="navbar-sidebar__brand">
<NavbarLogo />
<NavbarColorModeToggle className="margin-right--md" />
<CloseButton />
</div>
);
}
53 changes: 53 additions & 0 deletions src/theme/Navbar/MobileSidebar/Layout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, {version} from 'react';
import clsx from 'clsx';
import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal';
import {ThemeClassNames} from '@docusaurus/theme-common';
// TODO Docusaurus v4: remove temporary inert workaround
// See https://github.com/facebook/react/issues/17157
// See https://github.com/radix-ui/themes/pull/509
function inertProps(inert) {
const isBeforeReact19 = parseInt(version.split('.')[0], 10) < 19;
if (isBeforeReact19) {
return {inert: inert ? '' : undefined};
}
return {inert};
}
function NavbarMobileSidebarPanel({children, inert}) {
return (
<div
className={clsx(
ThemeClassNames.layout.navbar.mobileSidebar.panel,
'navbar-sidebar__item menu',
)}
{...inertProps(inert)}>
{children}
</div>
);
}
export default function NavbarMobileSidebarLayout({
header,
primaryMenu,
secondaryMenu,
}) {
const {shown: secondaryMenuShown} = useNavbarSecondaryMenu();
return (
<div
className={clsx(
ThemeClassNames.layout.navbar.mobileSidebar.container,
'navbar-sidebar',
)}>
{header}
<div
className={clsx('navbar-sidebar__items', {
'navbar-sidebar__items--show-secondary': secondaryMenuShown,
})}>
<NavbarMobileSidebarPanel inert={secondaryMenuShown}>
{primaryMenu}
</NavbarMobileSidebarPanel>
<NavbarMobileSidebarPanel inert={!secondaryMenuShown}>
{secondaryMenu}
</NavbarMobileSidebarPanel>
</div>
</div>
);
}
143 changes: 143 additions & 0 deletions src/theme/Navbar/MobileSidebar/PrimaryMenu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, {useState} from "react";
import {useThemeConfig} from '@docusaurus/theme-common';
import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal';
import NavbarItem from '@theme/NavbarItem';
import Link from "@docusaurus/Link";
function useNavbarItems() {
// TODO temporary casting until ThemeConfig type is improved
return useThemeConfig().navbar.items;
}
// The primary menu displays the navbar items
export default function NavbarMobilePrimaryMenu() {
const [showProduct, setShowProduct] = useState(false);
const [showBlog, setShowBlog] = useState(false);

const mobileSidebar = useNavbarMobileSidebar();
// TODO how can the order be defined for mobile?
// Should we allow providing a different list of items?
const items = useNavbarItems();
return (
<ul className="menu__list">

<li className="menu__list-item">
<button
className="menu__link w-full"
onClick={() => setShowProduct(!showProduct)}
aria-expanded={showProduct}
aria-controls="product-submenu"
aria-haspopup="true"
>
<div className="flex justify-between items-center w-full">
<div>Product</div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className={`size-6 transition-transform duration-300 ${
showProduct ? 'rotate-90' : 'rotate-0'
}`}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m8.25 4.5 7.5 7.5-7.5 7.5"
/>
</svg>
</div>
</button>

{showProduct && (
<ul
id="product-submenu"
className="menu__list ml-4"
role="menu"
aria-label="Product Submenu"
>
<li className="menu__list-item" role="none">
<Link className="menu__link" role="menuitem" to="/running-keploy/utg-pr-agent/">
Unit Testing
</Link>
</li>
<li className="menu__list-item" role="none">
<Link className="menu__link" role="menuitem" to="/keploy-explained/introduction">
API Testing
</Link>
</li>
<li className="menu__list-item" role="none">
<Link className="menu__link" role="menuitem" to="/running-keploy/about-api-testing">
Integration Testing
</Link>
</li>
</ul>
)}
</li>

<li className="menu__list-item">
<button
className="menu__link w-full"
onClick={() => setShowBlog(!showBlog)}
aria-expanded={showBlog}
aria-controls="blog-submenu"
aria-haspopup="true"
>
<div className="flex justify-between items-center w-full">
<div>Blog</div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className={`size-6 transition-transform duration-300 ${
showBlog ? 'rotate-90' : 'rotate-0'
}`}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m8.25 4.5 7.5 7.5-7.5 7.5"
/>
</svg>
</div>
</button>

{showBlog && (
<ul
id="blog-submenu"
className="menu__list ml-4"
role="menu"
aria-label="Blog Submenu"
>
<li className="menu__list-item" role="none">
<Link className="menu__link" role="menuitem" to="https://keploy.io/blog/technology">
Technical Blogs
</Link>
</li>
<li className="menu__list-item" role="none">
<Link className="menu__link" role="menuitem" to="https://keploy.io/blog/community">
Community Articles
</Link>
</li>
<li className="menu__list-item" role="none">
<Link className="menu__link" role="menuitem" to="/concepts/reference/glossary/">
Glossary
</Link>
</li>
</ul>
)}
</li>


{items.map((item, i) => (
<NavbarItem
mobile
{...item}
onClick={() => mobileSidebar.toggle()}
key={i}
/>
))}
</ul>
);
}
30 changes: 30 additions & 0 deletions src/theme/Navbar/MobileSidebar/SecondaryMenu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import {useThemeConfig} from '@docusaurus/theme-common';
import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal';
import Translate from '@docusaurus/Translate';
function SecondaryMenuBackButton(props) {
return (
<button {...props} type="button" className="clean-btn navbar-sidebar__back">
<Translate
id="theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel"
description="The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)">
← Back to main menu
</Translate>
</button>
);
}
// The secondary menu slides from the right and shows contextual information
// such as the docs sidebar
export default function NavbarMobileSidebarSecondaryMenu() {
const isPrimaryMenuEmpty = useThemeConfig().navbar.items.length === 0;
const secondaryMenu = useNavbarSecondaryMenu();
return (
<>
{/* edge-case: prevent returning to the primaryMenu when it's empty */}
{!isPrimaryMenuEmpty && (
<SecondaryMenuBackButton onClick={() => secondaryMenu.hide()} />
)}
{secondaryMenu.content}
</>
);
}
22 changes: 22 additions & 0 deletions src/theme/Navbar/MobileSidebar/Toggle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate';
import IconMenu from '@theme/Icon/Menu';
export default function MobileSidebarToggle() {
const {toggle, shown} = useNavbarMobileSidebar();
return (
<button
onClick={toggle}
aria-label={translate({
id: 'theme.docs.sidebar.toggleSidebarButtonAriaLabel',
message: 'Toggle navigation bar',
description:
'The ARIA label for hamburger menu button of mobile navigation',
})}
aria-expanded={shown}
className="navbar__toggle clean-btn"
type="button">
<IconMenu />
</button>
);
}
23 changes: 23 additions & 0 deletions src/theme/Navbar/MobileSidebar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import {
useLockBodyScroll,
useNavbarMobileSidebar,
} from '@docusaurus/theme-common/internal';
import NavbarMobileSidebarLayout from '@theme/Navbar/MobileSidebar/Layout';
import NavbarMobileSidebarHeader from '@theme/Navbar/MobileSidebar/Header';
import NavbarMobileSidebarPrimaryMenu from '@theme/Navbar/MobileSidebar/PrimaryMenu';
import NavbarMobileSidebarSecondaryMenu from '@theme/Navbar/MobileSidebar/SecondaryMenu';
export default function NavbarMobileSidebar() {
const mobileSidebar = useNavbarMobileSidebar();
useLockBodyScroll(mobileSidebar.shown);
if (!mobileSidebar.shouldRender) {
return null;
}
return (
<NavbarMobileSidebarLayout
header={<NavbarMobileSidebarHeader />}
primaryMenu={<NavbarMobileSidebarPrimaryMenu />}
secondaryMenu={<NavbarMobileSidebarSecondaryMenu />}
/>
);
}
25 changes: 25 additions & 0 deletions src/theme/NavbarItem/ComponentTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
import HtmlNavbarItem from '@theme/NavbarItem/HtmlNavbarItem';
import DocNavbarItem from '@theme/NavbarItem/DocNavbarItem';
import DocSidebarNavbarItem from '@theme/NavbarItem/DocSidebarNavbarItem';
import DocsVersionNavbarItem from '@theme/NavbarItem/DocsVersionNavbarItem';
import DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
import CustomProductsDropdown from '@theme/NavbarItem/CustomProductsDropdown';
import CustomBlogDropdown from '@theme/NavbarItem/CustomBlogDropdown';
const ComponentTypes = {
default: DefaultNavbarItem,
localeDropdown: LocaleDropdownNavbarItem,
search: SearchNavbarItem,
dropdown: DropdownNavbarItem,
html: HtmlNavbarItem,
doc: DocNavbarItem,
docSidebar: DocSidebarNavbarItem,
docsVersion: DocsVersionNavbarItem,
docsVersionDropdown: DocsVersionDropdownNavbarItem,
'custom-products-dropdown': CustomProductsDropdown,
'custom-blog-dropdown': CustomBlogDropdown
Copy link
Preview

Copilot AI Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a trailing comma after the last property for consistency with the project's style guide.

Suggested change
'custom-blog-dropdown': CustomBlogDropdown
'custom-blog-dropdown': CustomBlogDropdown,

Copilot uses AI. Check for mistakes.

};
export default ComponentTypes;
Loading