-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
82c08a7
commit aa16d7e
Showing
11 changed files
with
577 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
111
src/components/adslot-ui/VerticalNavigation/index.spec.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.