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