Skip to content

Commit

Permalink
feat: new vertical navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
lightbringer1991 committed Jul 23, 2019
1 parent 82c08a7 commit aa16d7e
Show file tree
Hide file tree
Showing 11 changed files with 577 additions and 4 deletions.
123 changes: 123 additions & 0 deletions src/components/adslot-ui/VerticalNavigation/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import _ from 'lodash';
import classnames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import './styles.scss';

const MenuItem = ({ children }) => children;

class VerticalNavigation extends React.Component {
static propTypes = {
isCollapsed: PropTypes.bool,
onClick: PropTypes.func,
dts: PropTypes.string,
className: PropTypes.string,
};

static defaultProps = {
isCollapsed: false,
};

constructor(props) {
super(props);
this.menuList = this.renderMenu(props);
this.contentList = this.renderContent(props);
}

shouldComponentUpdate(nextProps) {
this.menuList = this.renderMenu(nextProps);

// only render collapse/expand
if (nextProps.isCollapsed !== this.props.isCollapsed) {
return true;
}

this.contentList = this.renderContent(nextProps);
return true;
}

getActiveTabIndex = children => {
const activeIndex = _.findIndex(children, 'props.isActive');
return activeIndex === -1 ? 0 : activeIndex;
};

renderContent = ({ children }) => {
const activeTabIndex = this.getActiveTabIndex(children);
const contentList = React.Children.map(children, (child, index) => {
if (!child.props.content) {
// eslint-disable-next-line no-console
console.warn('Navigation does not render MenuItem that have no content prop.');
return null;
}

const contentClassnames = classnames([
'aui--vertical-navigation-component__content-item',
{ 'aui--vertical-navigation-component__content-item-is-active': index === activeTabIndex },
]);

return (
<div className={contentClassnames} data-test-selector={child.props.dts}>
{child}
</div>
);
});

return _.compact(contentList);
};

renderMenu = ({ children, isCollapsed }) => {
const menuList = [];
const activeTabIndex = this.getActiveTabIndex(children);

React.Children.forEach(children, (child, index) => {
if (!child.props.content) {
// eslint-disable-next-line no-console
console.warn('Navigation does not render MenuItem that have no content prop.');
return;
}

const classNames = classnames([
'aui--vertical-navigation-component__menu-item',
{ 'aui--vertical-navigation-component__menu-item-is-active': index === activeTabIndex },
]);
menuList.push(
<div key={`menu-item-${index}`} className={classNames} onClick={child.props.onClick}>
{child.props.content({ isCollapsed })}
</div>
);
});

return menuList;
};

render() {
const { className, dts, isCollapsed } = this.props;
const componentClasses = classnames('aui--vertical-navigation-component', className);

const menuClasses = classnames([
'aui--vertical-navigation-component__menu',
'aui--vertical-navigation-component__menu-is-animated',
{
'aui--vertical-navigation-component__menu-is-collapsed': isCollapsed,
},
]);

return (
<div className={componentClasses} data-test-selector={dts}>
<div className={menuClasses}>
<div className="aui--vertical-navigation-component__menu-item" onClick={this.props.onClick}>
<div className="aui--vertical-navigation-component__menu-item-collapse">
<div className="aui--vertical-navigation-component__menu-item-collapse-icon" />
</div>
</div>
{this.menuList}
</div>
<div className="aui--vertical-navigation-component__content">{this.contentList}</div>
</div>
);
}
}

VerticalNavigation.MenuItem = MenuItem;

export default VerticalNavigation;
111 changes: 111 additions & 0 deletions src/components/adslot-ui/VerticalNavigation/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import VerticalNav from 'adslot-ui/VerticalNavigation';
import { mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';

describe('VerticalNavComponent', () => {
const makeProps = override => ({
isCollapsed: false,
onClick: sinon.spy(),
dts: 'test-dts',
className: 'custom-class',
...override,
});
const makeMenuItemProps = override => ({
isActive: false,
dts: 'menu-item-dts',
content: sinon.spy(),
onClick: sinon.spy(),
...override,
});

let sandbox;

before(() => {
sandbox = sinon.createSandbox();
});

afterEach(() => sandbox.restore());

it('should render with props', () => {
const menuLabel1 = () => <div>Tab 1</div>;
const menuLabel2 = () => <div>Tab 2</div>;

const wrapper = mount(
<VerticalNav {...makeProps()}>
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel1 })}>Content 1</VerticalNav.MenuItem>
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel2 })}>Content 1</VerticalNav.MenuItem>
</VerticalNav>
);

expect(wrapper.find('.aui--vertical-navigation-component.custom-class')).to.have.length(1);

const menuItems = wrapper.find('.aui--vertical-navigation-component__menu-item');
expect(menuItems).to.have.length(3); // 1 collapse, 2 menu items
expect(menuItems.at(0).find('.aui--vertical-navigation-component__menu-item-collapse')).to.have.length(1);
expect(menuItems.at(1).text()).to.equal('Tab 1');
expect(menuItems.at(2).text()).to.equal('Tab 2');
});

it('should dispaly warnings if child element does not have `content` prop', () => {
sandbox.stub(console, 'warn');
const wrapper = mount(
<VerticalNav {...makeProps()}>
<div>Some random element</div>
</VerticalNav>
);

// only renders collapse item
expect(wrapper.find('.aui--vertical-navigation-component__menu-item')).to.have.length(1);
/* eslint-disable no-console */
expect(console.warn.calledTwice).to.equal(true);
expect(console.warn.args[0]).to.eql(['Navigation does not render MenuItem that have no content prop.']);
/* eslint-enable no-console */
});

it('should only update menu if props.isCollapsed is changed', () => {
const menuLabel1 = () => <div>Tab 1</div>;
const menuLabel2 = () => <div>Tab 2</div>;
const wrapper = mount(
<VerticalNav {...makeProps()}>
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel1, isActive: true })}>
Content 1
</VerticalNav.MenuItem>
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel2 })}>Content 1</VerticalNav.MenuItem>
</VerticalNav>
);

const renderMenuSpy = sandbox.spy(wrapper.instance(), 'renderMenu');
const renderContentSpy = sandbox.spy(wrapper.instance(), 'renderContent');
wrapper.setProps({ isCollapsed: true });

expect(renderMenuSpy.called).to.equal(true);
expect(renderContentSpy.called).to.equal(false);
});

it('should render both menu and content if active tab changes', () => {
const menuLabel1 = () => <div>Tab 1</div>;
const menuLabel2 = () => <div>Tab 2</div>;
const wrapper = mount(
<VerticalNav {...makeProps()}>
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel1, isActive: true })}>
Content 1
</VerticalNav.MenuItem>
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel2 })}>Content 1</VerticalNav.MenuItem>
</VerticalNav>
);
const renderMenuSpy = sandbox.spy(wrapper.instance(), 'renderMenu');
const renderContentSpy = sandbox.spy(wrapper.instance(), 'renderContent');
wrapper.setProps({
children: [
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel1 })}>Content 1</VerticalNav.MenuItem>,
<VerticalNav.MenuItem {...makeMenuItemProps({ content: menuLabel2, isActive: true })}>
Content 1
</VerticalNav.MenuItem>,
],
});

expect(renderMenuSpy.called).to.equal(true);
expect(renderContentSpy.called).to.equal(true);
});
});
67 changes: 67 additions & 0 deletions src/components/adslot-ui/VerticalNavigation/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@import '~styles/variable';
@import '~styles/mixin';

$menu-min-width: 60px;
$menu-max-width: 260px;

.aui--vertical-navigation-component {
display: flex;
flex-direction: row;

.aui--vertical-navigation-component__menu {
width: $menu-max-width;
font-size: $font-size-subheader;
font-family: $font-family-sans-serif;
font-weight: $font-weight-bold;

&.aui--vertical-navigation-component__menu-is-collapsed {
width: $menu-min-width;
}

&.aui--vertical-navigation-component__menu-is-animated {
transition: width 1s ease-in-out;
}

.aui--vertical-navigation-component__menu-item {
height: 60px;
padding: 10px;
margin-right: 6px;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: center;
white-space: nowrap;
overflow: hidden;

.aui--vertical-navigation-component__menu-item-collapse {
display: flex;
width: 40px;
height: 40px;
flex-direction: column;
justify-content: center;
padding: 8px;

&:hover {
border-radius: 50%;
background-color: $color-gray-lighter;
}
}

.aui--vertical-navigation-component__menu-item-collapse-icon {
@include svg-icon('~styles/icons/burger.svg', 24px, 24px);
}
}
}

.aui--vertical-navigation-component__content {
flex-grow: 1;

.aui--vertical-navigation-component__content-item {
display: none;

&.aui--vertical-navigation-component__content-item-is-active {
display: block;
}
}
}
}
4 changes: 3 additions & 1 deletion src/components/adslot-ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import HoverDropdownMenu from 'adslot-ui/HoverDropdownMenu';
import fastStatelessWrapper from 'adslot-ui/fastStatelessWrapper';
import InformationBox from 'adslot-ui/InformationBox';
import Nav from 'adslot-ui/Navigation';
import VerticalNav from 'adslot-ui/VerticalNavigation';
import OverlayLoader from 'adslot-ui/OverlayLoader';
import ActionPanel from 'adslot-ui/ActionPanel';

export {
Accordion,
ActionPanel,
AlertInput,
ButtonGroup,
Carousel,
Expand Down Expand Up @@ -69,5 +71,5 @@ export {
InformationBox,
HoverDropdownMenu,
OverlayLoader,
ActionPanel,
VerticalNav,
};
6 changes: 4 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {

import {
Accordion,
ActionPanel,
AlertInput,
ButtonGroup,
Carousel,
Expand Down Expand Up @@ -77,11 +78,12 @@ import {
InformationBox,
HoverDropdownMenu,
OverlayLoader,
ActionPanel,
VerticalNav,
} from 'adslot-ui';

export {
Accordion,
ActionPanel,
Alert,
AlertInput,
Avatar,
Expand Down Expand Up @@ -146,5 +148,5 @@ export {
InformationBox,
HoverDropdownMenu,
OverlayLoader,
ActionPanel,
VerticalNav,
};
39 changes: 39 additions & 0 deletions src/styles/icons/burger.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit aa16d7e

Please sign in to comment.