Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add nested accordion menu #5

Merged
merged 8 commits into from
Nov 28, 2019
Merged
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
95 changes: 95 additions & 0 deletions src/components/NestedAccordionMenu/MenuItems/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { Fragment, useState } from "react";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ExpandLessIcon from "@material-ui/icons/ExpandLess";
import Collapse from "@material-ui/core/Collapse";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import classNames from "classnames";
import useStyles from "./styles";
function MenuItem(props) {
const { to, name, items, Icon, depth, expanded, currentPath } = props;
const classes = useStyles({ depth: depth });
const [collapsed, setCollapsed] = useState(true);
const hasSubItems = Array.isArray(items);
function toggleCollapse() {
setCollapsed(prevValue => !prevValue);
}

let found = false;
function isChildrenActive(subItems) {
subItems.forEach(element => {
if (element.to === currentPath) {
found = true;
}
if (element.items) {
isChildrenActive(element.items);
}
});
return found;
}

const active = to === currentPath && !hasSubItems;
const itemsTofilt = items;
const activeSubParent = items && isChildrenActive(items);
let expandIcon;

if (hasSubItems && items.length) {
expandIcon = !collapsed ? (
<ExpandLessIcon className={classes.sidebarItemExpandArrowAxpanded} />
) : (
<ExpandMoreIcon className={classes.sidebarItemExpandArrow} />
);
}
const LinkComponent = props.link;
return (
<Fragment>
<ListItem
className={classes.sidebarItem}
onClick={hasSubItems ? toggleCollapse : undefined}
button
to={to}
component={hasSubItems ? undefined : LinkComponent}
classes={{
button: classNames({
[classes.activeColor]: active,
[classes.activeBackground]:
(!hasSubItems && !depth) ||
(hasSubItems && collapsed && activeSubParent),
}),
}}
>
<div className={classes.sidebarItemContent}>
<ListItemIcon>
{Icon && <Icon className={classes.sidebarItemIcon} />}
</ListItemIcon>
<ListItemText primary={name} />
</div>
{expandIcon}
</ListItem>
<Collapse
className={classes.collapse}
in={!collapsed}
timeout="auto"
unmountOnExit
>
{hasSubItems ? (
<List component="div">
{items &&
items.map((subItem, index) => (
<MenuItem
key={`${subItem.name}${index}`}
depth={depth + 1}
{...subItem}
currentPath={currentPath}
/>
))}
</List>
) : null}
</Collapse>
</Fragment>
);
}
MenuItem.defaultProps = { depth: 0 };
export default MenuItem;
27 changes: 27 additions & 0 deletions src/components/NestedAccordionMenu/MenuItems/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { makeStyles } from "@material-ui/styles";

export default makeStyles(theme => ({
sidebarItem: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
},
sidebarItemContent: props => ({
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
display: "flex",
alignItems: "center",
width: "100%",
paddingLeft: props.depth * theme.spacing(4),
}),
collapse: {
background: "#f7f7f7",
},
activeColor: {
color: theme.palette.primary.main,
},
activeBackground: {
background: "#efefef",
},
}));
24 changes: 24 additions & 0 deletions src/components/NestedAccordionMenu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import List from "@material-ui/core/List";
import MenuItem from "./MenuItems";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(theme => ({
sidebar: {
width: "100%",
backgroundColor: theme.palette.background.paper,
},
}));

function Menu({ items, ...rest }) {
const classes = useStyles();
return (
<List component="div" className={classes.sidebar}>
{items &&
items.map((menuItem, index) => (
<MenuItem key={`${menuItem.name}${index}`} {...menuItem} {...rest} />
))}
</List>
);
}

export default Menu;
123 changes: 123 additions & 0 deletions src/components/NestedAccordionMenu/index.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React from "react";
import theme from "../../theme";
import { NestedAccordionMenu } from "../../index";
import { storiesOf } from "@storybook/react";
import { withKnobs, boolean } from "@storybook/addon-knobs";
import { jsxDecorator } from "storybook-addon-jsx";
import { muiTheme } from "storybook-addon-material-ui";
import ListIcon from "@material-ui/icons/List";
import ScheduleIcon from "@material-ui/icons/Schedule";
import SettingsIcon from "@material-ui/icons/Settings";
import ShuffleIcon from "@material-ui/icons/Shuffle";
import InboxIcon from "@material-ui/icons/Inbox";
import { makeStyles } from "@material-ui/core/styles";

const stories = storiesOf("Nested Accordion Menu", NestedAccordionMenu);
function onClick(e, item) {}

// Add the `withKnobs` decorator to add knobs support to your stories.
// You can also configure `withKnobs` as a global decorator.
stories
.addDecorator(withKnobs)
.addDecorator(jsxDecorator)
.addDecorator(muiTheme([theme]));
function Link(props) {
const { to, children, ...rest } = props;
return (
<a href={to} {...props}>
{children}
</a>
);
}
const subItems = [
{
name: "Sub Journal one",
Icon: InboxIcon,
to: "project/6/journal/sub_journal_one",
},
{
name: "Sub Journal two",
Icon: InboxIcon,
to: "/sub_journal_one",
},
];
const nestedItems = [
{
name: "Journal",
Icon: ListIcon,
items: subItems,
},
{ name: "Schedulers", Icon: ScheduleIcon, to: "/schedulers" },
{ name: "General", Icon: SettingsIcon, to: "/General" },
{
name: "Org. Units",
Icon: ShuffleIcon,
to: "/organisationunits",
},
{ name: "Org. Unit Groups", Icon: ShuffleIcon, to: "/org_unit_groups" },
{ name: "Category Options", Icon: ShuffleIcon, to: "/category_options" },
{ name: "Categories", Icon: ShuffleIcon, to: "/categories" },
{ name: "Category Cobos", Icon: ShuffleIcon, to: "/category_combos" },
{ name: "Data Elements", Icon: ShuffleIcon, to: "/data_elements" },
{
name: "Data Element Groups",
Icon: ShuffleIcon,
to: "/data_element_groups",
},
{ name: "Data Sets", to: "/data_sets" },
];

const nestedItemsSecond = [
{ name: "Schedulers", Icon: ScheduleIcon, to: "/schedulers" },
{ name: "General", Icon: SettingsIcon, to: "/General" },
{
name: "Org. Units",
Icon: ShuffleIcon,
to: "/organisationunits",
},
{ name: "Org. Unit Groups", Icon: ShuffleIcon, to: "/org_unit_groups" },
{ name: "Category Options", Icon: ShuffleIcon, to: "/category_options" },
{ name: "Categories", Icon: ShuffleIcon, to: "/categories" },
{ name: "Category Cobos", Icon: ShuffleIcon, to: "/category_combos" },
{ name: "Data Elements", Icon: ShuffleIcon, to: "/data_elements" },
{
name: "Data Element Groups",
Icon: ShuffleIcon,
to: "/data_element_groups",
},
{ name: "Data Sets", to: "/data_sets" },
];

const itemsCoreModules = [
{
name: "CordaidSIS to HIVDR",
items: nestedItems,
Icon: InboxIcon,
},
{
name: "SNIS REPLICA to HIVDR",
items: nestedItems,
Icon: InboxIcon,
},

{
name: "DRC PROJECT",
Icon: InboxIcon,
to: "project/6/journal/sub_journal_one",
},
{
name: "SNIS REPLICA to HIVDR II",
items: nestedItemsSecond,
Icon: InboxIcon,
},
];

// Knobs for React props

stories.add("Default", () => (
<NestedAccordionMenu
items={itemsCoreModules}
currentPath="project/6/journal/sub_journal_one"
link={Link}
/>
));
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import InfoBoxComponent from "./components/InfoBox";
import KeyNumberBlockComponent from "./components/KeyNumberBlock";
import ProgressButtonComponent from "./components/ProgressButton";
import TransferListComponent from "./components/TransferList";
import NestedAccordionMenuComponent from "./components/NestedAccordionMenu";

export const BluesquareLogo = BluesquareLogoComponent;
export const D2dLogo = D2dLogoComponent;
Expand All @@ -25,3 +26,4 @@ export const InfoBox = InfoBoxComponent;
export const KeyNumberBlock = KeyNumberBlockComponent;
export const ProgressButton = ProgressButtonComponent;
export const TransferList = TransferListComponent;
export const NestedAccordionMenu = NestedAccordionMenuComponent;