Skip to content

feat: add controls to collapse side navigations - #9119

Open
HamedFarazi wants to merge 3 commits into
nodejs:mainfrom
HamedFarazi:feat/9099-side-navigation-toggle
Open

feat: add controls to collapse side navigations#9119
HamedFarazi wants to merge 3 commits into
nodejs:mainfrom
HamedFarazi:feat/9099-side-navigation-toggle

Conversation

@HamedFarazi

Copy link
Copy Markdown

Description

This PR implements the ability to collapse the left and right side navigations, providing a more focused and distraction-free reading experience.

The implementation introduces independent controls for both sidebars:

The left sidebar containing the API/site navigation can be collapsed and restored.
The right sidebar containing the "On this page" / metadata navigation can be collapsed and restored.
When a sidebar is collapsed, a compact rail remains visible with a control to restore it.
Both sidebars can be collapsed independently, allowing users to hide either one or both at the same time.
When both sidebars are collapsed, the main content expands to make better use of the available viewport width.
Sidebar transitions are animated to make expanding and collapsing feel smooth.
Sidebar state is persisted in localStorage, so the user's preference is preserved across navigation and page reloads.
The state is initialized in a hydration-safe way to avoid server/client rendering mismatches.
Accessible labels are provided for both expand and collapse controls.
The existing navigation behavior is preserved when the sidebars are expanded.
Implementation details

A shared sidebar state provider was introduced to keep the left and right sidebar states independent and accessible throughout the relevant layouts.

Reusable components were added for:

Sidebar toggle controls
Collapsed sidebar rails
Article layouts that are aware of sidebar state
Content layouts that are aware of sidebar state

The existing article and content layouts were updated to react to the sidebar state and dynamically adjust their grid columns when one or both sidebars are collapsed.

The UI component layouts were also extended to support the additional layout state without changing their existing default behavior.

Validation

The following checks passed successfully during the commit process:

Prettier
ESLint
Stylelint
TypeScript type checking (lint:types)

The functionality was also manually verified across the affected layouts, including sidebar expansion/collapse behavior and navigation between pages.

Reviewers should specifically verify:

The left navigation can be collapsed and restored.
The right "On this page" navigation can be collapsed and restored.
Both sidebars can be collapsed independently.
Collapsing both sidebars provides the expected wider content area.
The collapsed rails remain accessible so the navigation can be restored.
Sidebar preferences persist after navigating between pages and refreshing the page.
Existing pages without sidebar navigation continue to behave correctly.
The layout remains responsive at supported viewport sizes.
Related 问题

image image image

Closes #9099

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.

  • I have run the project's formatting/linting checks and verified the changed files are correctly formatted.

  • I have run pnpm test to check if all tests are passing.

  • I have run pnpm build to check if the website builds without errors.

  • I've covered new added functionality with unit tests if necessary.

@HamedFarazi
HamedFarazi requested a review from a team as a code owner August 27, 2026 09:49
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment 操作 Updated (UTC)
nodejs-org Ready Ready Preview Aug 29, 2026 9:18am

Request Review

@mikeesto

Copy link
Copy Markdown
Member

Just quickly trying it out.. on a blog page the left hand side navigation control is there (and works) but there is no content there so it looks odd

Screenshot 2026-08-28 at 9 44 52 am

@HamedFarazi

HamedFarazi commented Aug 29, 2026

Copy link
Copy Markdown
Author

Thanks for catching this

Fixed the issue by making the sidebar and metabar controls content-aware. They now only render when there is actual content/navigation to display.

Blog pages with no left-side navigation no longer show an empty sidebar control or collapsed rail.

I also verified that pages with actual sidebar content still support collapsing and expanding as expected.

TypeScript checks and formatting pass successfully.

image image
Screen.Recording.2026-08-29.115027.mp4

@AugustinMauroy

Copy link
Copy Markdown
Member
Capture d’écran 2026-08-29 à 11 01 25

https://nodejs-org-git-fork-hamedfarazi-feat-9099-side-na-c776a6-openjs.vercel.app/fr/blog/events/nodejs-interactive-2026

this is completely broken

@AugustinMauroy

Copy link
Copy Markdown
Member

Other point, IMO you should add this feature and we will use it on doc. But in about page this feature should be disabled for simplicity.

aria-label={label}
aria-expanded={!isCollapsed}
title={label}
className="flex size-8 items-center justify-center rounded-md border border-neutral-200 bg-white text-neutral-700 shadow-sm transition-all duration-200 ease-out hover:bg-neutral-50 hover:shadow-md focus:ring-2 focus:ring-neutral-400 focus:ring-offset-1 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:shadow-neutral-900/20 dark:hover:bg-neutral-700 dark:focus:ring-neutral-500"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't use inline tailwind-css use module css with @apply directive

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'll move the button styles into a CSS module using @apply to follow the existing project styling conventions.

Comment thread apps/site/components/withMetaBar.tsx Outdated
headings={{ items: headings }}
/>
>
<div className="mb-6 flex justify-end pr-2">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
<div className="mb-6 flex justify-end pr-2">
<div className="mb-1 flex justify-end pr-2">

why this huge margin ?

Image

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

i will reduce it to keep the toggle button aligned with the sidebar content.

@HamedFarazi

Copy link
Copy Markdown
Author
Capture d’écran 2026-08-29 à 11 01 25 https://nodejs-org-git-fork-hamedfarazi-feat-9099-side-na-c776a6-openjs.vercel.app/fr/blog/events/nodejs-interactive-2026

this is completely broken

Thanks for catching this. The issue was caused by returning null from the sidebar wrapper, which broke the expected grid structure. I'll keep the grid children intact and only hide the sidebar content when no navigation is available.

@HamedFarazi

Copy link
Copy Markdown
Author

Other point, IMO you should add this feature and we will use it on doc. But in about page this feature should be disabled for simplicity.

Agree. I'll disable the toggle controls for the 关于 layout and keep this feature enabled for documentation pages only.

@HamedFarazi

Copy link
Copy Markdown
Author

@AugustinMauroy Thanks for the feedback
I've addressed all requested changes:

  • Fixed the broken blog layout by preserving the sidebar grid structure.
  • Disabled sidebar toggle controls for the 关于 layout.
  • Moved SidebarToggleButton styles into a CSS module using @apply.
  • Reduced the toggle button spacing.

The changes are available in the latest commit.

@AugustinMauroy AugustinMauroy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

better also need to move collapsed logic to ui-component package so it's will be accessible for the docs

> = ({ side, children }) => {
return (
<aside
className={`flex w-full flex-col bg-white transition-all duration-200 ease-out dark:bg-neutral-950 ${

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use classnames + css module + apply directive

Comment on lines +24 to +35
const icon =
side === 'left' ? (
isCollapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronLeftIcon className="size-4" />
)
) : isCollapsed ? (
<ChevronLeftIcon className="size-4" />
) : (
<ChevronRightIcon className="size-4" />
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
const icon =
side === 'left' ? (
isCollapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronLeftIcon className="size-4" />
)
) : isCollapsed ? (
<ChevronLeftIcon className="size-4" />
) : (
<ChevronRightIcon className="size-4" />
);
const icon =
side === 'left' ? isCollapsed : !isCollapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronLeftIcon className="size-4" />
);

Comment on lines +22 to +44
const t = useTranslations();

const icon =
side === 'left' ? (
isCollapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronLeftIcon className="size-4" />
)
) : isCollapsed ? (
<ChevronLeftIcon className="size-4" />
) : (
<ChevronRightIcon className="size-4" />
);

const label =
side === 'left'
? isCollapsed
? t('components.common.sidebar.expandLeftSidebar')
: t('components.common.sidebar.collapseLeftSidebar')
: isCollapsed
? t('components.common.sidebar.expandRightSidebar')
: t('components.common.sidebar.collapseRightSidebar');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
const t = useTranslations();
const icon =
side === 'left' ? (
isCollapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronLeftIcon className="size-4" />
)
) : isCollapsed ? (
<ChevronLeftIcon className="size-4" />
) : (
<ChevronRightIcon className="size-4" />
);
const label =
side === 'left'
? isCollapsed
? t('components.common.sidebar.expandLeftSidebar')
: t('components.common.sidebar.collapseLeftSidebar')
: isCollapsed
? t('components.common.sidebar.expandRightSidebar')
: t('components.common.sidebar.collapseRightSidebar');
const t = useTranslations();
const direction = side === 'left' ? 'Left' : 'Right';
const action = isCollapsed ? 'expand' : 'collapse';
const icon =
side === 'left' === isCollapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronLeftIcon className="size-4" />
);
const label = t(
`components.common.sidebar.${action}${direction}Sidebar`
);

注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

None yet

项目

None yet

Development

Successfully merging this pull request may close these issues.

Option to close side navigations

3 participants