Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
22 changes: 22 additions & 0 deletions apps/site/components/Common/ArticleWithSidebarState.tsx
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;
26 changes: 26 additions & 0 deletions apps/site/components/Common/CollapsedSidebarRail.tsx
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 ${

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

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.

Done. I refactored CollapsedSidebarRail to use classnames with a CSS module and @apply, following the existing styling patterns in the repository.

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;
27 changes: 27 additions & 0 deletions apps/site/components/Common/ContentLayoutWithSidebarState.tsx
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;
59 changes: 59 additions & 0 deletions apps/site/components/Common/SidebarToggleButton.tsx
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" />
);

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" />
);

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.

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');

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`
);

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.

Done. I simplified the label generation using the direction and action approach, while keeping the existing translation keys and behavior intact.


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"

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.

{...props}
>
{icon}
</button>
);
};

export default SidebarToggleButton;
39 changes: 38 additions & 1 deletion apps/site/components/withMetaBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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')}
Expand Down Expand Up @@ -74,7 +103,15 @@ const WithMetaBar: FC = () => {
),
}}
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.

<SidebarToggleButton
side="right"
isCollapsed={isRightSidebarCollapsed}
onToggle={toggleRightSidebar}
/>
</div>
</MetaBar>
);
};

Expand Down
35 changes: 34 additions & 1 deletion apps/site/components/withSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import Sidebar from '@node-core/ui-components/Containers/Sidebar';
import { useTranslations } from 'next-intl';
import { useRef } from 'react';

import CollapsedSidebarRail from '#site/components/Common/CollapsedSidebarRail';
import SidebarToggleButton from '#site/components/Common/SidebarToggleButton';
import Link from '#site/components/Link';
import useClientContext from '#site/hooks/useClientContext';
import useScrollToElement from '#site/hooks/useScrollToElement';
import useSiteNavigation from '#site/hooks/useSiteNavigation';
import { useRouter, usePathname } from '#site/navigation.mjs';
import { useSidebarState } from '#site/providers/sidebarStateProvider';

import type { FormattedMessage, NavigationKeys } from '#site/types';
import type { RichTranslationValues } from 'next-intl';
Expand Down Expand Up @@ -48,6 +51,7 @@ const WithSidebar: FC<WithSidebarProps> = ({ navKeys, context, ...props }) => {
const { frontmatter } = useClientContext();
const sidebarRef = useRef<HTMLElement>(null);
const sideNavigation = getSideNavigation(navKeys, context);
const { isLeftSidebarCollapsed, toggleLeftSidebar } = useSidebarState();

// Preserve sidebar scroll position across navigations
useScrollToElement('sidebar', sidebarRef);
Expand All @@ -62,6 +66,27 @@ const WithSidebar: FC<WithSidebarProps> = ({ navKeys, context, ...props }) => {
})
);

const hasNavigationContent =
mappedSidebarItems.length > 0 && navKeys.length > 0;

// If no navigation content, don't render sidebar or controls at all
if (!hasNavigationContent) {
return null;
}

// Show collapsed rail when sidebar is collapsed (only if there's content)
if (isLeftSidebarCollapsed) {
return (
<CollapsedSidebarRail side="left">
<SidebarToggleButton
side="left"
isCollapsed={isLeftSidebarCollapsed}
onToggle={toggleLeftSidebar}
/>
</CollapsedSidebarRail>
);
}

return (
<Sidebar
ref={sidebarRef}
Expand All @@ -72,7 +97,15 @@ const WithSidebar: FC<WithSidebarProps> = ({ navKeys, context, ...props }) => {
onSelect={push}
as={Link}
{...props}
/>
>
<div className="mb-6 flex justify-end pr-2">
<SidebarToggleButton
side="left"
isCollapsed={isLeftSidebarCollapsed}
onToggle={toggleLeftSidebar}
/>
</div>
</Sidebar>
);
};

Expand Down
7 changes: 3 additions & 4 deletions apps/site/layouts/About.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Article from '@node-core/ui-components/Containers/Article';

import ArticleWithSidebarState from '#site/components/Common/ArticleWithSidebarState';
import WithBreadcrumbs from '#site/components/withBreadcrumbs';
import WithFooter from '#site/components/withFooter';
import WithMetaBar from '#site/components/withMetaBar';
Expand All @@ -12,7 +11,7 @@ const AboutLayout: FC<PropsWithChildren> = ({ children }) => (
<>
<WithNavBar />

<Article>
<ArticleWithSidebarState>
<WithSidebar navKeys={['about', 'getInvolved']} />

<div>
Expand All @@ -24,7 +23,7 @@ const AboutLayout: FC<PropsWithChildren> = ({ children }) => (
</div>

<WithBreadcrumbs navKeys={['about', 'getInvolved']} />
</Article>
</ArticleWithSidebarState>

<WithFooter />
</>
Expand Down
7 changes: 3 additions & 4 deletions apps/site/layouts/ArticlePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Article from '@node-core/ui-components/Containers/Article';

import ArticleWithSidebarState from '#site/components/Common/ArticleWithSidebarState';
import WithFooter from '#site/components/withFooter';
import WithMetaBar from '#site/components/withMetaBar';
import WithNavBar from '#site/components/withNavBar';
Expand All @@ -11,7 +10,7 @@ const ArticlePageLayout: FC<PropsWithChildren> = ({ children }) => (
<>
<WithNavBar />

<Article>
<ArticleWithSidebarState>
<WithSidebar navKeys={[]} />

<div>
Expand All @@ -21,7 +20,7 @@ const ArticlePageLayout: FC<PropsWithChildren> = ({ children }) => (

<WithMetaBar />
</div>
</Article>
</ArticleWithSidebarState>

<WithFooter />
</>
Expand Down
5 changes: 4 additions & 1 deletion apps/site/layouts/Base.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { NavigationStateProvider } from '#site/providers/navigationStateProvider';
import { SidebarStateProvider } from '#site/providers/sidebarStateProvider';

import type { FC, PropsWithChildren } from 'react';

import styles from './layouts.module.css';

const BaseLayout: FC<PropsWithChildren> = ({ children }) => (
<NavigationStateProvider>
<div className={styles.baseLayout}>{children}</div>
<SidebarStateProvider>
<div className={styles.baseLayout}>{children}</div>
</SidebarStateProvider>
</NavigationStateProvider>
);

Expand Down
7 changes: 3 additions & 4 deletions apps/site/layouts/Default.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Article from '@node-core/ui-components/Containers/Article';

import ArticleWithSidebarState from '#site/components/Common/ArticleWithSidebarState';
import WithFooter from '#site/components/withFooter';
import WithNavBar from '#site/components/withNavBar';
import WithSidebar from '#site/components/withSidebar';
Expand All @@ -10,15 +9,15 @@ const DefaultLayout: FC<PropsWithChildren> = ({ children }) => (
<>
<WithNavBar />

<Article>
<ArticleWithSidebarState>
<WithSidebar navKeys={[]} />

<div>
<main id="main" tabIndex={-1}>
{children}
</main>
</div>
</Article>
</ArticleWithSidebarState>

<WithFooter />
</>
Expand Down
Loading