-
Notifications
You must be signed in to change notification settings - Fork 6.5k
feat: add controls to collapse side navigations #9119
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
7891e6d
dc30f6d
f2b905b
c6fe24c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| 'use client'; | ||
|
|
||
| import Article from '@node-core/ui-components/Containers/Article'; | ||
|
|
||
| import { useSidebarState } from '#site/providers/sidebarStateProvider'; | ||
|
|
||
| import type { FC, PropsWithChildren } from 'react'; | ||
|
|
||
| const ArticleWithSidebarState: FC<PropsWithChildren> = ({ children }) => { | ||
| const { isLeftSidebarCollapsed, isRightSidebarCollapsed } = useSidebarState(); | ||
|
|
||
| return ( | ||
| <Article | ||
| data-left-sidebar-collapsed={isLeftSidebarCollapsed ? 'true' : 'false'} | ||
| data-right-sidebar-collapsed={isRightSidebarCollapsed ? 'true' : 'false'} | ||
| > | ||
| {children} | ||
| </Article> | ||
| ); | ||
| }; | ||
|
|
||
| export default ArticleWithSidebarState; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| 'use client'; | ||
|
|
||
| import type { FC, PropsWithChildren } from 'react'; | ||
|
|
||
| type CollapsedSidebarRailProps = { | ||
| side: 'left' | 'right'; | ||
| }; | ||
|
|
||
| const CollapsedSidebarRail: FC< | ||
| PropsWithChildren<CollapsedSidebarRailProps> | ||
| > = ({ side, children }) => { | ||
| return ( | ||
| <aside | ||
| className={`flex w-full flex-col bg-white transition-all duration-200 ease-out dark:bg-neutral-950 ${ | ||
| side === 'left' | ||
| ? 'border-r border-neutral-200 dark:border-neutral-900' | ||
| : 'border-l border-neutral-200 dark:border-neutral-900' | ||
| }`} | ||
| > | ||
| {/* Button container positioned at top with proper spacing */} | ||
| <div className="flex w-full justify-center px-3 pt-6">{children}</div> | ||
| </aside> | ||
| ); | ||
| }; | ||
|
|
||
| export default CollapsedSidebarRail; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| 'use client'; | ||
|
|
||
| import { useSidebarState } from '#site/providers/sidebarStateProvider'; | ||
|
|
||
| import type { FC, PropsWithChildren } from 'react'; | ||
|
|
||
| type ContentLayoutWithSidebarStateProps = { | ||
| className?: string; | ||
| }; | ||
|
|
||
| const ContentLayoutWithSidebarState: FC< | ||
| PropsWithChildren<ContentLayoutWithSidebarStateProps> | ||
| > = ({ children, className = '' }) => { | ||
| const { isLeftSidebarCollapsed, isRightSidebarCollapsed } = useSidebarState(); | ||
|
|
||
| return ( | ||
| <div | ||
| className={className} | ||
| data-left-sidebar-collapsed={isLeftSidebarCollapsed ? 'true' : 'false'} | ||
| data-right-sidebar-collapsed={isRightSidebarCollapsed ? 'true' : 'false'} | ||
| > | ||
| {children} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ContentLayoutWithSidebarState; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,59 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useTranslations } from 'next-intl'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { FC, ButtonHTMLAttributes } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type SidebarToggleButtonProps = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| side: 'left' | 'right'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCollapsed: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onToggle: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } & ButtonHTMLAttributes<HTMLButtonElement>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const SidebarToggleButton: FC<SidebarToggleButtonProps> = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| side, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCollapsed, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onToggle, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...props | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. I simplified the icon logic to avoid the nested ternaries while preserving the existing behavior for both left and right sidebars. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. I simplified the label generation using the |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={onToggle} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't use inline tailwind-css use module css with
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...props} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {icon} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default SidebarToggleButton; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,15 @@ import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub'; | |
| import { defaultLocale } from '@node-core/website-i18n'; | ||
| import { useFormatter, useLocale, useTranslations } from 'next-intl'; | ||
|
|
||
| import CollapsedSidebarRail from '#site/components/Common/CollapsedSidebarRail'; | ||
| import SidebarToggleButton from '#site/components/Common/SidebarToggleButton'; | ||
| import Link from '#site/components/Link'; | ||
| import WithAvatarGroup from '#site/components/withAvatarGroup'; | ||
| import useClientContext from '#site/hooks/useClientContext'; | ||
| import useMediaQuery from '#site/hooks/useMediaQuery'; | ||
| import { DEFAULT_DATE_FORMAT } from '#site/next.calendar.constants.mjs'; | ||
| import { TRANSLATION_URL } from '#site/next.constants.mjs'; | ||
| import { useSidebarState } from '#site/providers/sidebarStateProvider'; | ||
| import { getGitHubBlobUrl } from '#site/util/github'; | ||
|
|
||
| import type { FC } from 'react'; | ||
|
|
@@ -34,12 +37,38 @@ const WithMetaBar: FC = () => { | |
|
|
||
| const t = useTranslations(); | ||
| const locale = useLocale(); | ||
| const { isRightSidebarCollapsed, toggleRightSidebar } = useSidebarState(); | ||
|
|
||
| // Since we cannot show the same number of avatars in Mobile / Tablet | ||
| // resolution as we do on desktop and there is overflow, we are adjusting | ||
| // the number of avatars manually for the resolutions below | ||
| const isSmallerThanDesktop = useMediaQuery('(max-width: 1280px)'); | ||
|
|
||
| // Check if there's any content to show in the metabar | ||
| const hasContent = | ||
| lastUpdated || | ||
| readingTimeText || | ||
| usernames.length > 0 || | ||
| headings.length > 0; | ||
|
|
||
| // If no content, don't render metabar or controls at all | ||
| if (!hasContent) { | ||
| return null; | ||
| } | ||
|
|
||
| // Show collapsed rail when right sidebar is collapsed (only if there's content) | ||
| if (isRightSidebarCollapsed) { | ||
| return ( | ||
| <CollapsedSidebarRail side="right"> | ||
| <SidebarToggleButton | ||
| side="right" | ||
| isCollapsed={isRightSidebarCollapsed} | ||
| onToggle={toggleRightSidebar} | ||
| /> | ||
| </CollapsedSidebarRail> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <MetaBar | ||
| heading={t('components.metabar.tableOfContents')} | ||
|
|
@@ -74,7 +103,15 @@ const WithMetaBar: FC = () => { | |
| ), | ||
| }} | ||
| headings={{ items: headings }} | ||
| /> | ||
| > | ||
| <div className="mb-6 flex justify-end pr-2"> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| <SidebarToggleButton | ||
| side="right" | ||
| isCollapsed={isRightSidebarCollapsed} | ||
| onToggle={toggleRightSidebar} | ||
| /> | ||
| </div> | ||
| </MetaBar> | ||
| ); | ||
| }; | ||
|
|
||
|
|
||

There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I refactored
CollapsedSidebarRailto useclassnameswith a CSS module and@apply, following the existing styling patterns in the repository.