Skip to content

Instantly share code, notes, and snippets.

@isabellachen
Last active February 17, 2020 10:54
Show Gist options
  • Save isabellachen/9dfb805a62df0af43690f3ea94919ba4 to your computer and use it in GitHub Desktop.
Save isabellachen/9dfb805a62df0af43690f3ea94919ba4 to your computer and use it in GitHub Desktop.
Nested Menu with React
import React, { useEffect, useMemo, useState } from 'react';
import Scrollbars from 'react-custom-scrollbars';
import { useLocation } from 'react-router-dom';
import {
menuFactory,
MenuLink,
recursiveCloseMenuLinksAndOpenActive,
recursiveCloseMenuLinksAndOpenActiveByPathName,
recursiveCloseMenuLinksAndOpenActiveFromParent,
} from '~/core/configuration/menu';
import './aside-menu.scss';
import { AsideLink } from './AsideLink';
type AsideMenuProps = {
isMenuOpen: boolean;
toggleMenu: () => void;
};
export function AsideMenu(props: AsideMenuProps) {
const { isMenuOpen, toggleMenu } = props;
const location = useLocation();
const [links, setLinks] = useState<MenuLink[]>([]);
useEffect(() => {
markDefaultMenuOpen();
}, [location]);
const menuOpenClassName = useMemo(() => `icon-ic-hamburger-${isMenuOpen ? 'expanded' : 'collased'}`, [isMenuOpen]);
function toggleCurrentNestedLinkAndCloseOthers(menuLink: MenuLink, parent?: MenuLink): void {
let menuLinks: MenuLink[];
if (parent) {
menuLinks = recursiveCloseMenuLinksAndOpenActiveFromParent(links, menuLink, parent);
} else {
menuLinks = recursiveCloseMenuLinksAndOpenActive(links, menuLink);
}
setLinks(menuLinks);
}
function markDefaultMenuOpen(): void {
const updatedLinks = recursiveCloseMenuLinksAndOpenActiveByPathName(menuFactory(), location.pathname);
setLinks(updatedLinks);
}
const onClickMenuToggler = () => toggleMenu();
const onClickMenuLink = (menuLink: MenuLink, parent?: MenuLink) => {
toggleCurrentNestedLinkAndCloseOthers(menuLink, parent);
};
function renderNestedLinks(menuLink: MenuLink): JSX.Element[] | undefined {
if (!menuLink || !menuLink.nested || menuLink.nested.length < 1) {
return;
}
return menuLink.nested.map((nestedMenuLink: MenuLink) => (
<AsideLink
{...nestedMenuLink}
key={nestedMenuLink.label}
related={nestedMenuLink.related}
onClick={() => onClickMenuLink(nestedMenuLink, menuLink)}
isOpen={nestedMenuLink.isOpen}
isActive={nestedMenuLink.isActive}
/>
));
}
function renderMenuLinks(): JSX.Element[] {
return links.map((menuLink: MenuLink) => (
<AsideLink
{...menuLink}
key={menuLink.label}
related={menuLink.related}
onClick={() => onClickMenuLink(menuLink)}
isOpen={menuLink.isOpen}
isActive={menuLink.isActive}
>
{menuLink.nested && menuLink.nested.length > 0 ? renderNestedLinks(menuLink) : null}
</AsideLink>
));
}
return (
<div className="aside-menu__wrapper">
<Scrollbars className="aside-menu__content">
<div className="aside-menu__header" onClick={onClickMenuToggler}>
<span className={`aside-menu__list__icon ${menuOpenClassName}`} />
</div>
{renderMenuLinks()}
</Scrollbars>
<div className="aside-menu__footer">
<div className="aside-menu__list">
<span className="icon-sidra-simbolo" />
</div>
</div>
</div>
);
}
import cloneDeep from 'clone-deep';
import { MenuNamingRoutes } from '~/core/constants/Constants';
import { MenuLink } from './menu-link';
const menuLinks: MenuLink[] = [
{
icon: 'icon-ic-home',
label: 'Home',
isOpen: true,
isActive: true,
nested: [
{
directTo: MenuNamingRoutes.dashboard,
icon: 'icon-ic-dashboard',
label: 'Dashboard',
isActive: true,
related: [MenuNamingRoutes.default],
},
{
directTo: MenuNamingRoutes.logs,
icon: 'icon-ic-logs',
label: 'Logs',
},
{
directTo: MenuNamingRoutes.notifications,
icon: 'icon-ic-bell',
label: 'Notifications',
},
{
directTo: MenuNamingRoutes.releaseNotes,
icon: 'icon-ic-release-notes',
label: 'ReleaseNotes',
},
],
},
{
icon: 'icon-ic-app',
label: 'Apps',
nested: [
{
directTo: MenuNamingRoutes.registeredApps,
icon: 'icon-ic-app-2',
label: 'RegisteredApps',
},
],
},
{
icon: 'icon-ic-providers',
label: 'Data',
nested: [
{
directTo: MenuNamingRoutes.providers,
icon: 'icon-ic-providers',
label: 'Providers',
related: [MenuNamingRoutes.newProvider, MenuNamingRoutes.providersDetail],
},
{
directTo: '/datacatalog',
icon: 'icon-ic-data-source',
label: 'DataCatalog',
related: [MenuNamingRoutes.datacatalogSearch, MenuNamingRoutes.datacatalogProviderEntities],
},
],
},
];
export function menuFactory(): MenuLink[] {
return cloneDeep(menuLinks);
}
function getParentMenuLinkPath(path: string): string {
return path.split('/')[1];
}
export function recursiveCloseMenuLinksAndOpenActive(links: MenuLink[], link: MenuLink): MenuLink[] {
return links.map((menuLink: MenuLink) => {
menuLink.isOpen = link.label === menuLink.label ? !menuLink.isOpen : false;
return menuLink;
});
}
export function recursiveCloseMenuLinksAndOpenActiveFromParent(
links: MenuLink[],
link: MenuLink,
parent: MenuLink,
): MenuLink[] {
return links.map((menuLink: MenuLink) => {
menuLink.isOpen = parent.label === menuLink.label;
menuLink.isActive = menuLink.isOpen;
if (menuLink.nested && menuLink.nested.length > 0) {
menuLink.nested = menuLink.nested.map(nestedLink => {
nestedLink.isActive = nestedLink.label === link.label;
return nestedLink;
});
}
return menuLink;
});
}
export function recursiveCloseMenuLinksAndOpenActiveByPathName(links: MenuLink[], pathName: string): MenuLink[] {
if (pathName === MenuNamingRoutes.signinCallback || pathName === MenuNamingRoutes.default) {
return links;
}
return links.map((menuLink: MenuLink) => {
let nestedLinkOpened = false;
if (menuLink.nested) {
menuLink.nested = menuLink.nested.map((nestedLink: MenuLink) => {
const isNestedActive = getParentMenuLinkPath(pathName) === getParentMenuLinkPath(nestedLink.directTo || '/');
const isRelatedActive = nestedLink.related
? nestedLink.related.some(x => getParentMenuLinkPath(x) === getParentMenuLinkPath(pathName))
: false;
nestedLink.isOpen = isNestedActive || isRelatedActive;
nestedLink.isActive = nestedLink.isOpen;
nestedLinkOpened = nestedLinkOpened || nestedLink.isOpen;
return nestedLink;
});
}
menuLink.isOpen = nestedLinkOpened;
menuLink.isActive = nestedLinkOpened;
return menuLink;
});
}
@isabellachen
Copy link
Author

sidra-navigation-normal-2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment