From 2f9a777cb279e9f05d522d8bbac963087d577226 Mon Sep 17 00:00:00 2001 From: Caleb Ellis Date: Mon, 29 Jan 2018 14:22:26 +0000 Subject: [PATCH 1/5] 104: Add SideNav, SideNavBanner, SideNavGroup and SideNavLink components --- src/lib/components/SideNav/SideNav.js | 75 +++ src/lib/components/SideNav/SideNav.stories.js | 184 ++++++ src/lib/components/SideNav/SideNav.test.js | 88 +++ src/lib/components/SideNav/SideNavBanner.js | 70 +++ src/lib/components/SideNav/SideNavGroup.js | 71 +++ src/lib/components/SideNav/SideNavLink.js | 39 ++ .../__snapshots__/SideNav.test.js.snap | 533 ++++++++++++++++++ src/lib/index.js | 8 +- 8 files changed, 1066 insertions(+), 2 deletions(-) create mode 100644 src/lib/components/SideNav/SideNav.js create mode 100644 src/lib/components/SideNav/SideNav.stories.js create mode 100644 src/lib/components/SideNav/SideNav.test.js create mode 100644 src/lib/components/SideNav/SideNavBanner.js create mode 100644 src/lib/components/SideNav/SideNavGroup.js create mode 100644 src/lib/components/SideNav/SideNavLink.js create mode 100644 src/lib/components/SideNav/__snapshots__/SideNav.test.js.snap diff --git a/src/lib/components/SideNav/SideNav.js b/src/lib/components/SideNav/SideNav.js new file mode 100644 index 0000000..9605c80 --- /dev/null +++ b/src/lib/components/SideNav/SideNav.js @@ -0,0 +1,75 @@ +import React from 'react'; + +import SideNavBanner from './SideNavBanner'; + +class SideNav extends React.Component { + constructor() { + super(); + this.handleMenuClick = this.handleMenuClick.bind(this); + + this.state = { menuOpen: false }; + } + + handleMenuClick() { + const { menuOpen } = this.state; + this.setState({ menuOpen: !menuOpen }); + } + + render() { + const { children } = this.props; + const { menuOpen } = this.state; + const content = []; + let banner; + + React.Children.forEach(children, (child) => { + if (child.type === SideNavBanner) { + banner = React.cloneElement(child, { + ...this.props, + onClick: this.handleMenuClick, + open: this.state.menuOpen, + }); + } else { + content.push(child); + } + }); + + return ( +
+ { banner } +
+
    + { content } +
+
+
+ ); + } +} + +SideNav.defaultProps = { + children: null, +}; + +SideNav.propTypes = { + children: (props, propName, componentName) => { + const prop = props[propName]; + let error = null; + let count = 0; + + React.Children.forEach(prop, (child) => { + if (child.type === SideNavBanner) { + count += 1; + } + }); + + if (count !== 1) { + error = new Error(`${componentName} should have exactly one child of type "SideNavBanner".`); + } + + return error; + }, +}; + +SideNav.displayName = 'SideNav'; + +export default SideNav; diff --git a/src/lib/components/SideNav/SideNav.stories.js b/src/lib/components/SideNav/SideNav.stories.js new file mode 100644 index 0000000..1bd2dd6 --- /dev/null +++ b/src/lib/components/SideNav/SideNav.stories.js @@ -0,0 +1,184 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { number, text } from '@storybook/addon-knobs'; +import { withInfo } from '@storybook/addon-info'; + +import Link from '../Link/Link'; +import List from '../List/List'; +import ListItem from '../List/ListItem'; +import SideNav from './SideNav'; +import SideNavBanner from './SideNavBanner'; +import SideNavGroup from './SideNavGroup'; +import SideNavLink from './SideNavLink'; + +const options = { + range: true, + min: 3, + max: 12, + step: 1, +}; + +storiesOf('SideNav', module) + .add('Text Banner', + withInfo('The SideNav component allows for navigation from a collapsing menu on the side of the page. It requires a SideNavBanner which contains a title/logo, optional tagline, and a space for the small screen burger menu. SideNavLinks can be placed directly inside SideNav, or inside a SideNavGroup to keep them under section titles. This example will expand to fill the space available to it so it needs to be used in conjunction with the grid to set the layout.')(() => ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + Supplementary link 1 + + + Supplementary link 2 + + +
+
), + ), + ) + + .add('Logo Banner', + withInfo('A logo object prop can also be passed to SideNavBanner, which will replace a simple text banner.')(() => ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + vanillaframework.io + + +
+
), + ), + ); diff --git a/src/lib/components/SideNav/SideNav.test.js b/src/lib/components/SideNav/SideNav.test.js new file mode 100644 index 0000000..f06055d --- /dev/null +++ b/src/lib/components/SideNav/SideNav.test.js @@ -0,0 +1,88 @@ +import React from 'react'; +import ReactTestRenderer from 'react-test-renderer'; + +import SideNav from './SideNav'; +import SideNavBanner from './SideNavBanner'; +import SideNavGroup from './SideNavGroup'; +import SideNavLink from './SideNavLink'; + +describe('', () => { + it('renders with a text-based SideNavBanner correctly', () => { + const sidenav = ReactTestRenderer.create( + + + , + ); + const json = sidenav.toJSON(); + expect(json).toMatchSnapshot(); + }); + + it('renders with a logo-based SideNavBanner correctly', () => { + const sidenav = ReactTestRenderer.create( + + + , + ); + const json = sidenav.toJSON(); + expect(json).toMatchSnapshot(); + }); + + it('renders with a single SideNavLink correctly', () => { + const sidenav = ReactTestRenderer.create( + + + + , + ); + const json = sidenav.toJSON(); + expect(json).toMatchSnapshot(); + }); + + it('renders with multiple SideNavLinks correctly', () => { + const sidenav = ReactTestRenderer.create( + + + + + + , + ); + const json = sidenav.toJSON(); + expect(json).toMatchSnapshot(); + }); + + it('renders with a SideNavGroup correctly', () => { + const sidenav = ReactTestRenderer.create( + + + + + + , + ); + const json = sidenav.toJSON(); + expect(json).toMatchSnapshot(); + }); + + it('renders with multiple SideNavGroups correctly', () => { + const sidenav = ReactTestRenderer.create( + + + + + + + + + + + + , + ); + const json = sidenav.toJSON(); + expect(json).toMatchSnapshot(); + }); +}); diff --git a/src/lib/components/SideNav/SideNavBanner.js b/src/lib/components/SideNav/SideNavBanner.js new file mode 100644 index 0000000..9c94750 --- /dev/null +++ b/src/lib/components/SideNav/SideNavBanner.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +class SideNavBanner extends React.Component { + render() { + const { + href, logo, onClick, open, tagline, title, + } = this.props; + + const Tag = href ? 'a' : 'div'; + + return ( +