Skip to content

Revamp mobile navigation #3282

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 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3a762dc
Revamp mobile navigation
nolannbiron Jun 7, 2025
bb850e5
Remove top/bottom sheet
nolannbiron Jun 7, 2025
a56216d
Update design and handle innerHeader
nolannbiron Jun 7, 2025
451290a
Fix styling
nolannbiron Jun 8, 2025
f523575
Handle circular-corners
nolannbiron Jun 8, 2025
2d5fc43
Fixes
nolannbiron Jun 8, 2025
e018b65
Put scrollbar back
nolannbiron Jun 8, 2025
6659bfb
Missing comment
nolannbiron Jun 8, 2025
e848cf5
Fix layout
nolannbiron Jun 8, 2025
cdcd991
Fixes after review
nolannbiron Jun 9, 2025
7b6dff7
Update comments
nolannbiron Jun 9, 2025
eef6d2f
Render TOCScrollContent only once
nolannbiron Jun 9, 2025
a2a852e
Always close sheet on pathname change
nolannbiron Jun 9, 2025
92ae60f
Fix MobileMenu
nolannbiron Jun 13, 2025
abfba66
Update overlay
nolannbiron Jun 13, 2025
b8dc0e6
Fix dropdown
nolannbiron Jun 13, 2025
098fe2d
fix padding
nolannbiron Jun 13, 2025
8d88eb6
Revamp mobile navigation
nolannbiron Jun 13, 2025
ed007c8
Fix classes
nolannbiron Jun 13, 2025
ab4af82
chores
nolannbiron Jun 13, 2025
15e0adc
Fix bun.lock
nolannbiron Jun 14, 2025
f855d49
Add usePreventScroll
nolannbiron Jun 16, 2025
bc8d59f
Fix z-index
nolannbiron Jun 16, 2025
01db884
Fix padding
nolannbiron Jun 16, 2025
06b8a27
Add tests for mobile menu
nolannbiron Jun 17, 2025
040be0b
Fix spacing for innerHeader
nolannbiron Jun 17, 2025
8795621
Merge branch 'main' into nolann/revamp-mobile-navigation
nolannbiron Jul 24, 2025
b357acb
Fixes
nolannbiron Jul 24, 2025
b92e78b
Fix
nolannbiron Jul 24, 2025
53433ef
Fix spacing
nolannbiron Jul 24, 2025
88cd02f
Fixes
nolannbiron Jul 24, 2025
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
5 changes: 5 additions & 0 deletions .changeset/sweet-hornets-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gitbook': minor
---

Revamp mobile navigation
7 changes: 4 additions & 3 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.14.0",
"version": "0.14.1",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/cache-tags": "workspace:*",
Expand Down Expand Up @@ -98,6 +98,7 @@
"openapi-types": "^12.1.3",
"p-map": "^7.0.3",
"quick-lru": "^7.0.1",
"react-aria": "^3.37.0",
"react-hotkeys-hook": "^4.4.1",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
Expand Down Expand Up @@ -166,7 +167,7 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "2.2.1",
"version": "2.2.2",
"dependencies": {
"@scalar/openapi-parser": "^0.18.0",
"@scalar/openapi-types": "^0.1.9",
Expand Down Expand Up @@ -213,7 +214,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.3.3",
"version": "1.3.4",
"dependencies": {
"@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.3.16",
Expand Down
51 changes: 51 additions & 0 deletions packages/gitbook/e2e/internal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,57 @@ const testCases: TestsCase[] = [
]),
],
},
{
name: 'Mobile menu',
contentBaseURL: 'https://gitbook-open-e2e-sites.gitbook.io/',
tests: [
{
name: 'Mobile menu open',
viewports: ['iphone-x'],
url: '',
run: async (page) => {
// Set mobile viewport size to ensure mobile menu is visible
await page.setViewportSize({ width: 375, height: 812 }); // iPhone X dimensions

await page.locator('[data-testid="mobile-menu-button"]').click();

// Wait for table of contents to appear
const tableOfContents = page.locator('[data-testid="table-of-contents"]');
await tableOfContents.waitFor({ state: 'visible', timeout: 5000 });
await expect(tableOfContents).toBeVisible();
},
},
{
name: 'Mobile menu with dropdown menu',
viewports: ['iphone-x'],
url: 'multi-variants/',
run: async (page) => {
// Set mobile viewport size to ensure mobile menu is visible
await page.setViewportSize({ width: 375, height: 812 }); // iPhone X dimensions

await page.locator('[data-testid="mobile-menu-button"]').click();

// Wait for table of contents to appear
const tableOfContents = page.locator('[data-testid="table-of-contents"]');
await tableOfContents.waitFor({ state: 'visible', timeout: 5000 });
await expect(tableOfContents).toBeVisible();

// Wait for space dropdown button to be visible
const spaceDropdownButton = tableOfContents.locator(
'[data-testid="space-dropdown-button"]'
);
await spaceDropdownButton.waitFor({ state: 'visible', timeout: 5000 });
await expect(spaceDropdownButton).toBeVisible();
await spaceDropdownButton.click();

// Wait for space dropdown to appear
const spaceDropdown = page.locator('[data-testid="dropdown-menu"]');
await spaceDropdown.waitFor({ state: 'visible', timeout: 5000 });
await expect(spaceDropdown).toBeVisible();
},
},
],
},
];

runTestCases(testCases);
15 changes: 12 additions & 3 deletions packages/gitbook/e2e/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export interface Test {
* Whether to only run this test.
*/
only?: boolean;
/**
* Viewport to use for the test.
*/
viewports?: ('macbook-16' | 'macbook-13' | 'ipad-2' | 'iphone-x')[];
}

export type TestsCase = {
Expand Down Expand Up @@ -162,7 +166,7 @@ export function runTestCases(testCases: TestsCase[]) {

test.describe(testCase.name, () => {
for (const testEntry of testCase.tests) {
const { mode = 'page' } = testEntry;
const { mode = 'page', viewports } = testEntry;
const testFn = testEntry.only ? test.only : test;
testFn(testEntry.name, async ({ page, context }) => {
const testEntryPathname =
Expand Down Expand Up @@ -207,13 +211,18 @@ export function runTestCases(testCases: TestsCase[]) {
const screenshotName = `${testCase.name} - ${testEntry.name}`;
if (mode === 'image') {
await argosScreenshot(page, screenshotName, {
viewports: ['macbook-13'],
viewports: viewports ?? ['macbook-13'],
threshold: screenshotOptions?.threshold ?? undefined,
fullPage: true,
});
} else {
await argosScreenshot(page, screenshotName, {
viewports: ['macbook-16', 'macbook-13', 'ipad-2', 'iphone-x'],
viewports: viewports ?? [
'macbook-16',
'macbook-13',
'ipad-2',
'iphone-x',
],
argosCSS: `
/* Hide Intercom */
.intercom-lightweight-app {
Expand Down
1 change: 1 addition & 0 deletions packages/gitbook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@tailwindcss/typography": "^0.5.16",
"ai": "^4.2.2",
"assert-never": "^1.2.1",
"react-aria": "^3.37.0",
"bun-types": "^1.1.20",
"classnames": "^2.5.1",
"direction": "^2.0.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/gitbook/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { GitBookSiteContext } from '@/lib/context';

import { HeaderMobileMenuButton } from '@/components/Header/HeaderMobileMenuButton';
import { CONTAINER_STYLE, HEADER_HEIGHT_DESKTOP } from '@/components/layout';
import { getSpaceLanguage, t } from '@/intl/server';
import { tcls } from '@/lib/tailwind';
Expand All @@ -8,7 +9,6 @@ import { HeaderLink } from './HeaderLink';
import { HeaderLinkMore } from './HeaderLinkMore';
import { HeaderLinks } from './HeaderLinks';
import { HeaderLogo } from './HeaderLogo';
import { HeaderMobileMenu } from './HeaderMobileMenu';
import { SpacesDropdown } from './SpacesDropdown';

/**
Expand Down Expand Up @@ -78,7 +78,7 @@ export function Header(props: {
'min-w-0 shrink items-center justify-start gap-2 lg:gap-4'
)}
>
<HeaderMobileMenu
<HeaderMobileMenuButton
className={tcls(
'lg:hidden',
'-ml-2',
Expand Down
57 changes: 0 additions & 57 deletions packages/gitbook/src/components/Header/HeaderMobileMenu.tsx

This file was deleted.

36 changes: 36 additions & 0 deletions packages/gitbook/src/components/Header/HeaderMobileMenuButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import { Icon } from '@gitbook/icons';

import { useMobileMenuSheet } from '@/components/MobileMenu/useMobileMenuSheet';
import { tString, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind';

/**
* Button to show/hide the table of content on mobile.
*/
export function HeaderMobileMenuButton(
props: Partial<React.ButtonHTMLAttributes<HTMLButtonElement>>
) {
const language = useLanguage();
const { open, setOpen } = useMobileMenuSheet();

const toggleNavigation = () => {
setOpen(!open);
};

return (
<button
{...props}
aria-label={tString(language, 'table_of_contents_button_label')}
data-testid="mobile-menu-button"
onClick={toggleNavigation}
className={tcls(
'flex flex-row items-center rounded straight-corners:rounded-sm px-2 py-1',
props.className
)}
>
<Icon icon="bars" className="size-4 text-inherit" />
</button>
);
}
4 changes: 3 additions & 1 deletion packages/gitbook/src/components/Header/SpacesDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ export function SpacesDropdown(props: {
siteSpace: SiteSpace;
siteSpaces: SiteSpace[];
className?: string;
withPortal?: boolean;
}) {
const { context, siteSpace, siteSpaces, className } = props;
const { context, siteSpace, siteSpaces, className, withPortal } = props;

return (
<DropdownMenu
className={tcls(
'group-hover/dropdown:invisible', // Prevent hover from opening the dropdown, as it's annoying in this context
'group-focus-within/dropdown:group-hover/dropdown:visible' // When the dropdown is already open, it should remain visible when hovered
)}
withPortal={withPortal}
button={
<div
data-testid="space-dropdown-button"
Expand Down
23 changes: 23 additions & 0 deletions packages/gitbook/src/components/MobileMenu/MobileMenuScript.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

import { useMobileMenuSheet } from '@/components/MobileMenu';
import { usePathname } from 'next/navigation';
import { useEffect } from 'react';
import { usePreventScroll } from 'react-aria';

export function MobileMenuScript() {
const pathname = usePathname();
const { open, setOpen } = useMobileMenuSheet();

// biome-ignore lint/correctness/useExhaustiveDependencies: Close the navigation when navigating to a page
useEffect(() => {
setOpen(false);
}, [pathname]);

// Prevent scrolling when the menu is open
usePreventScroll({
isDisabled: !open,
});

return null;
}
2 changes: 2 additions & 0 deletions packages/gitbook/src/components/MobileMenu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './useMobileMenuSheet';
export * from './MobileMenuScript';
12 changes: 12 additions & 0 deletions packages/gitbook/src/components/MobileMenu/useMobileMenuSheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { create } from 'zustand';

/**
* Hooks to manage the mobile menu sheet state.
*/
export const useMobileMenuSheet = create<{
open: boolean;
setOpen: (open: boolean) => void;
}>((set) => ({
open: false,
setOpen: (open) => set({ open }),
}));
Loading