Skip to content
Open
74 changes: 50 additions & 24 deletions components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ export default function Layout({

const mobileNavRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (showMobileNav) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
Comment on lines +45 to +49
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Instead of hardcoding the overflow value to unset, we should save the previous value and restore it.

}, [showMobileNav]);

React.useEffect(
() => useStore.setState({ overlayNavigation: null }),
[router.asPath],
Expand Down Expand Up @@ -106,8 +117,12 @@ export default function Layout({
<MainNavigation />
</div>
</header>
<div ref={mobileNavRef}>
{showMobileNav && <MobileNav />}
<div ref={mobileNavRef}>{showMobileNav && <MobileNav />}</div>
<div
className={`transition-all duration-300 ${
showMobileNav ? 'mt-[200px]' : 'mt-0'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should avoid hardcoding the margins. This could break the UI if the navbar’s style changes or nav items are added or removed

}`}
>
{children}
</div>
<ScrollButton />
Expand Down Expand Up @@ -300,29 +315,40 @@ const MobileNav = () => {
const section = useContext(SectionContext);

return (
<div className='flex flex-col lg:hidden shadow-xl justify-end fixed bg-white w-full z-[190] top-16 left-0 pl-8 dark:bg-slate-800'>
<MainNavLink
uri='/specification'
label='Specification'
isActive={section === 'specification'}
/>
<MainNavLink
uri='/learn/getting-started-step-by-step'
label='Docs'
isActive={section === 'docs'}
/>
<div className='flex flex-col lg:hidden shadow-xl fixed bg-white w-full z-[190] top-16 left-0 pl-8 dark:bg-slate-800 transition-all duration-300 ease-in-out'>
<div className='flex flex-col space-y-2 py-2'>
<MainNavLink
className='block py-1.5'
uri='/specification'
label='Specification'
isActive={section === 'specification'}
/>
<MainNavLink
className='block py-1.5'
uri='/learn/getting-started-step-by-step'
label='Docs'
isActive={section === 'docs'}
/>

<MainNavLink
uri='/tools?query=&sortBy=name&sortOrder=ascending&groupBy=toolingTypes&licenses=&languages=&drafts=&toolingTypes=&environments='
label='Tools'
isActive={section === 'tools'}
/>
<MainNavLink uri='/blog' label='Blog' isActive={section === 'blog'} />
<MainNavLink
uri='/community'
label='Community'
isActive={section === 'community'}
/>
<MainNavLink
className='block py-1.5'
uri='/tools?query=&sortBy=name&sortOrder=ascending&groupBy=toolingTypes&licenses=&languages=&drafts=&toolingTypes=&environments='
label='Tools'
isActive={section === 'tools'}
/>
<MainNavLink
className='block py-1.5'
uri='/blog'
label='Blog'
isActive={section === 'blog'}
/>
<MainNavLink
className='block py-1.5'
uri='/community'
label='Community'
isActive={section === 'community'}
/>
</div>
</div>
);
};
Expand Down
145 changes: 117 additions & 28 deletions components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable linebreak-style */
/* istanbul ignore file */
import useStore from '~/store';
import { getLayout as getSiteLayout } from './SiteLayout';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef, useLayoutEffect } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import { HOST } from '~/lib/config';
Expand Down Expand Up @@ -202,32 +203,89 @@ export const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
const router = useRouter();
const [open, setOpen] = useState(false);
const [rotateChevron, setRotateChevron] = useState(false);
const handleRotate = () => setRotateChevron(!rotateChevron);
const rotate = rotateChevron ? 'rotate(180deg)' : 'rotate(0)';
const [topOffset, setTopOffset] = useState('0px');
const headerRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);

const pathWtihoutFragment = extractPathWithoutFragment(router.asPath);
const shouldHideSidebar = pathWtihoutFragment === '/md-style-guide';
useEffect(() => {
if (open) {
useStore.setState({ overlayNavigation: null });
}
}, [open]);

const overlayNavigation = useStore((s) => s.overlayNavigation);

useEffect(() => {
if (window) {
window.addEventListener('resize', () => {
if (window.innerWidth > 1024) {
setOpen(false);
}
});
if (overlayNavigation !== null && open) {
setOpen(false);
}
}, [overlayNavigation, open]);

useEffect(() => {
const originalOverflow =
typeof window !== 'undefined' ? document.body.style.overflow : '';
if (open) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = originalOverflow || '';
}
}, [typeof window !== 'undefined']);
return () => {
document.body.style.overflow = originalOverflow || '';
};
}, [open]);

useLayoutEffect(() => {
const calculateOffset = () => {
if (!headerRef.current) return;
let totalHeight = headerRef.current.offsetHeight;
if (open && dropdownRef.current) {
totalHeight += dropdownRef.current.offsetHeight;
}
setTopOffset(`${totalHeight}px`);
};
calculateOffset();
const handleResize = () => {
if (window.innerWidth > 1024) {
setOpen(false);
}
calculateOffset();
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [open]);

const handleToggle = (e: React.MouseEvent) => {
e.stopPropagation();
setRotateChevron(!rotateChevron);
setOpen(!open);
};

let panelStyle: React.CSSProperties | undefined;
if (open) {
const top = headerRef.current
? headerRef.current.offsetTop + headerRef.current.offsetHeight + 'px'
: undefined;
panelStyle = {
top,
height: '80vh',
};
} else {
panelStyle = undefined;
}

return (
<div className='max-w-[1400px] mx-auto flex flex-col items-center'>
<section>
<div className='bg-primary dark:bg-slate-900 w-full h-12 mt-[4.5rem] z-150 flex relative flex-col justify-center items-center lg:hidden'>
<section className='w-full'>
<div
ref={headerRef}
className='bg-primary dark:bg-slate-900 w-full h-12 mt-[4.5rem] z-150 flex relative flex-col justify-center items-center lg:hidden'
>
<div
className='z-[150] flex w-full bg-primary dark:bg-slate-900 justify-between items-center'
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => {
e.stopPropagation();
handleRotate();
setOpen(!open);
}}
onClick={handleToggle}
>
{getDocsPath.includes(pathWtihoutFragment) && (
<h3 className='text-white ml-12'>Introduction</h3>
Expand All @@ -251,7 +309,7 @@ export const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
style={{
marginRight: '50px',
color: 'white',
transform: rotate,
transform: rotateChevron ? 'rotate(180deg)' : 'rotate(0)',
transition: 'all 0.2s linear',
}}
xmlns='http://www.w3.org/2000/svg'
Expand All @@ -260,25 +318,53 @@ export const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
>
<path
d='M64 448c-8.188 0-16.38-3.125-22.62-9.375c-12.5-12.5-12.5-32.75 0-45.25L178.8 256L41.38 118.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0l160 160c12.5 12.5 12.5 32.75 0 45.25l-160 160C80.38 444.9 72.19 448 64 448z'
id='mainIconPathAttribute'
fill='#ffffff'
></path>
/>
</svg>
</div>
</div>

{open && (
<div
className='fixed inset-0 z-[148] lg:hidden'
onClick={() => setOpen(false)}
aria-hidden='true'
/>
)}
<div
className={`z-[150] absolute top-10 mt-24 left-0 h-full w-screen bg-white dark:bg-slate-900 dark:shadow-lg transform ${
open ? '-translate-x-0' : '-translate-x-full'
} transition-transform duration-300 ease-in-out filter drop-shadow-md`}
ref={dropdownRef}
className={classnames(
'z-[149] w-full bg-white dark:bg-slate-900 transform transition-transform duration-300 ease-in-out will-change-transform',
{
'-translate-x-full lg:translate-x-0 fixed left-0 right-0': !open,
'translate-x-0 fixed left-0 right-0 lg:relative': open,
'shadow-lg': open,
},
)}
style={panelStyle}
>
<div className='flex flex-col dark:bg-slate-900'>
<div
className={classnames('flex flex-col dark:bg-slate-900', {
'h-full overflow-y-auto': open,
'h-0 overflow-hidden': !open,
})}
>
<DocsNav open={open} setOpen={setOpen} />
</div>
</div>
<div className='dark:bg-slate-800 max-w-[1400px] grid grid-cols-1 lg:grid-cols-4 mx-4 md:mx-12'>
<div
className={classnames(
'dark:bg-slate-800 max-w-[1400px] grid grid-cols-1 lg:grid-cols-4 mx-4 md:mx-12 transition-all duration-300',
{
'lg:pt-12': true,
'pt-0': open,
},
)}
style={{
marginTop: open ? topOffset : '0',
}}
>
{!shouldHideSidebar && (
<div className='hidden lg:block mt-24 sticky top-24 h-[calc(100vh-6rem)] overflow-hidden'>
<div className='hidden lg:block sticky top-24 h-[calc(100vh-6rem)] overflow-hidden'>
<div className='h-full overflow-y-auto scrollbar-hidden'>
<DocsNav open={open} setOpen={setOpen} />
<CarbonAds
Expand All @@ -289,7 +375,10 @@ export const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
</div>
)}
<div
className={`lg:mt-20 mx-4 md:mx-0 ${shouldHideSidebar ? 'col-span-4 w-full' : 'col-span-4 md:col-span-3 lg:w-5/6'}`}
className={classnames('mx-4 md:mx-0 transition-all duration-300', {
'col-span-4 w-full': shouldHideSidebar,
'col-span-4 md:col-span-3 lg:w-5/6': !shouldHideSidebar,
})}
>
{children}
</div>
Expand Down
5 changes: 2 additions & 3 deletions cypress/components/Sidebar.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ describe('Sidebar Component', () => {
// Click on mobile menu button (the div with onClick handler)
cy.get('.lg\\:hidden > div').first().click();

// Menu should be open
cy.get('.transform.-translate-x-0').should('exist');
cy.get('.transform.translate-x-0').should('exist');
});

it('should show correct section title based on current path', () => {
Expand Down Expand Up @@ -124,7 +123,7 @@ describe('Sidebar Component', () => {

// Open mobile menu
cy.get('.lg\\:hidden > div').first().click();
cy.get('.transform.-translate-x-0').should('exist');
cy.get('.transform.translate-x-0').should('exist');

// Resize to desktop
cy.viewport(1025, 768);
Expand Down
Loading