157 lines
4.5 KiB
TypeScript
157 lines
4.5 KiB
TypeScript
import React, { useCallback, useState } from 'react';
|
|
import cx from 'classnames';
|
|
import Link from 'next/link';
|
|
import {
|
|
Menu,
|
|
MenuItem,
|
|
MenuSection,
|
|
} from '@nrwl/nx-dev/data-access-documents';
|
|
import { useRouter } from 'next/router';
|
|
|
|
export interface SidebarProps {
|
|
menu: Menu;
|
|
navIsOpen?: boolean;
|
|
}
|
|
|
|
export function Sidebar({ menu, navIsOpen }: SidebarProps) {
|
|
return (
|
|
<div
|
|
data-testid="sidebar"
|
|
className={cx(
|
|
'fixed z-20 inset-0 flex-none h-full bg-black bg-opacity-25 w-full lg:bg-white lg:static lg:h-auto lg:overflow-y-visible lg:pt-o lg:w-64 lg:block border-r border-gray-50',
|
|
!navIsOpen && 'hidden',
|
|
navIsOpen && 'block'
|
|
)}
|
|
>
|
|
<div
|
|
data-testid="navigation-wrapper"
|
|
className="h-full overflow-y-auto scrolling-touch lg:h-auto lg:block lg:relative lg:sticky lg:bg-transparent overflow-auto lg:top-18 bg-white mr-24 lg:mr-0 px-2 sm:pr-4 xl:pr-6"
|
|
>
|
|
<div className="hidden lg:block h-12 pointer-events-none absolute inset-x-0 z-10 bg-gradient-to-b from-white" />
|
|
|
|
<nav
|
|
id="nav"
|
|
data-testid="navigation"
|
|
className="px-1 pt-16 font-medium text-base sm:px-3 xl:px-5 lg:text-sm pb-10 lg:pb-14 sticky?lg:h-(screen-18)"
|
|
>
|
|
{menu.sections.map((section, index) => (
|
|
<SidebarSection key={section.id + '-' + index} section={section} />
|
|
))}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SidebarSection({ section }: { section: MenuSection }) {
|
|
return (
|
|
<>
|
|
{section.hideSectionHeader ? null : (
|
|
<h4
|
|
data-testid={`section-h4:${section.id}`}
|
|
className="mt-8 text-lg font-bold border-b border-gray-50 border-solid"
|
|
>
|
|
{section.name}
|
|
</h4>
|
|
)}
|
|
<ul>
|
|
<li className="mt-2">
|
|
{section.itemList.map((item, index) => (
|
|
<SidebarSectionItems key={item.id + '-' + index} item={item} />
|
|
))}
|
|
</li>
|
|
</ul>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function SidebarSectionItems({ item }: { item: MenuItem }) {
|
|
const router = useRouter();
|
|
const [collapsed, setCollapsed] = useState(!item.disableCollapsible);
|
|
|
|
const handleCollapseToggle = useCallback(() => {
|
|
if (!item.disableCollapsible) {
|
|
setCollapsed(!collapsed);
|
|
}
|
|
}, [collapsed, setCollapsed, item]);
|
|
|
|
function withoutAnchors(linkText: string): string {
|
|
return linkText?.includes('#')
|
|
? linkText.substring(0, linkText.indexOf('#'))
|
|
: linkText;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<h5
|
|
data-testid={`section-h5:${item.id}`}
|
|
className={cx(
|
|
'flex py-2',
|
|
'uppercase tracking-wide font-semibold text-sm lg:text-xs text-gray-900',
|
|
item.disableCollapsible ? 'cursor-text' : 'cursor-pointer'
|
|
)}
|
|
onClick={handleCollapseToggle}
|
|
>
|
|
{item.name}
|
|
{item.disableCollapsible ? null : (
|
|
<CollapsibleIcon isCollapsed={collapsed} />
|
|
)}
|
|
</h5>
|
|
<ul className={cx('mb-6', collapsed ? 'hidden' : '')}>
|
|
{(item.itemList as MenuItem[]).map((item, index) => {
|
|
const isActiveLink = item.url === withoutAnchors(router?.asPath);
|
|
return (
|
|
<li
|
|
key={item.id + '-' + index}
|
|
data-testid={`section-li:${item.id}`}
|
|
>
|
|
<Link href={item.url as string}>
|
|
<a
|
|
className={cx(
|
|
'py-1 transition-colors duration-200 relative block text-gray-500 hover:text-gray-900'
|
|
)}
|
|
>
|
|
{isActiveLink ? (
|
|
<span className="rounded-md absolute h-full w-1 -right-2 sm:-right-4 top-0 bg-blue-nx-base" />
|
|
) : null}
|
|
<span
|
|
className={cx('relative', {
|
|
'text-gray-900': isActiveLink,
|
|
})}
|
|
>
|
|
{item.name}
|
|
</span>
|
|
</a>
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function CollapsibleIcon({ isCollapsed }: { isCollapsed: boolean }) {
|
|
return (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className={cx(
|
|
'transition-all h-3.5 w-3.5 text-gray-500',
|
|
!isCollapsed && 'transform rotate-90'
|
|
)}
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d={'M9 5l7 7-7 7'}
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export default Sidebar;
|