Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 ${
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" />
);

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

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"
{...props}
>
{icon}
</button>
);
};

export default SidebarToggleButton;
49 changes: 48 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,48 @@ 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;

// Always show collapsed rail when right sidebar is collapsed
if (isRightSidebarCollapsed) {
return (
<CollapsedSidebarRail side="right">
<SidebarToggleButton
side="right"
isCollapsed={isRightSidebarCollapsed}
onToggle={toggleRightSidebar}
/>
</CollapsedSidebarRail>
);
}

// If no content, show empty metabar area with toggle button
if (!hasContent) {
return (
<div className="flex w-full flex-col border-l border-neutral-200 bg-white dark:border-neutral-900 dark:bg-neutral-950">
<div className="mb-6 flex justify-end pt-6 pr-2">
<SidebarToggleButton
side="right"
isCollapsed={isRightSidebarCollapsed}
onToggle={toggleRightSidebar}
/>
</div>
</div>
);
}

return (
<MetaBar
heading={t('components.metabar.tableOfContents')}
Expand Down Expand Up @@ -74,7 +113,15 @@ const WithMetaBar: FC = () => {
),
}}
headings={{ items: headings }}
/>
>
<div className="mb-6 flex justify-end pr-2">
<SidebarToggleButton
side="right"
isCollapsed={isRightSidebarCollapsed}
onToggle={toggleRightSidebar}
/>
</div>
</MetaBar>
);
};

Expand Down
45 changes: 44 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,37 @@ const WithSidebar: FC<WithSidebarProps> = ({ navKeys, context, ...props }) => {
})
);

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

// Always show collapsed rail when sidebar is collapsed
if (isLeftSidebarCollapsed) {
return (
<CollapsedSidebarRail side="left">
<SidebarToggleButton
side="left"
isCollapsed={isLeftSidebarCollapsed}
onToggle={toggleLeftSidebar}
/>
</CollapsedSidebarRail>
);
}

// If no navigation content, show empty sidebar with toggle button
if (!hasNavigationContent) {
return (
<div className="flex w-full flex-col border-r border-neutral-200 bg-white dark:border-neutral-900 dark:bg-neutral-950">
<div className="flex justify-end pt-6 pr-3">
<SidebarToggleButton
side="left"
isCollapsed={isLeftSidebarCollapsed}
onToggle={toggleLeftSidebar}
/>
</div>
</div>
);
}

return (
<Sidebar
ref={sidebarRef}
Expand All @@ -72,7 +107,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