feat: add controls to collapse side navigations - #9119
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
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" |
There was a problem hiding this comment.
don't use inline tailwind-css use module css with @apply directive
There was a problem hiding this comment.
I'll move the button styles into a CSS module using @apply to follow the existing project styling conventions.
| headings={{ items: headings }} | ||
| /> | ||
| > | ||
| <div className="mb-6 flex justify-end pr-2"> |
There was a problem hiding this comment.
i will reduce it to keep the toggle button aligned with the sidebar content.
Agree. I'll disable the toggle controls for the 关于 layout and keep this feature enabled for documentation pages only. |
|
@AugustinMauroy Thanks for the feedback
The changes are available in the latest commit. |
AugustinMauroy
left a comment
There was a problem hiding this comment.
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 ${ |
There was a problem hiding this comment.
use classnames + css module + apply directive
| const icon = | ||
| side === 'left' ? ( | ||
| isCollapsed ? ( | ||
| <ChevronRightIcon className="size-4" /> | ||
| ) : ( | ||
| <ChevronLeftIcon className="size-4" /> | ||
| ) | ||
| ) : isCollapsed ? ( | ||
| <ChevronLeftIcon className="size-4" /> | ||
| ) : ( | ||
| <ChevronRightIcon className="size-4" /> | ||
| ); |
There was a problem hiding this comment.
| 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" /> | |
| ); |
| 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'); |
There was a problem hiding this comment.
| 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` | |
| ); |






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 问题
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.