HBM/md-components

View on GitHub
src/js/navigation/index.js

Summary

Maintainability
A
1 hr
Test Coverage

/**
 * Spec
 * http://www.google.com/design/spec/patterns/navigation-drawer.html#
 * http://www.google.com/design/spec/patterns/app-structure.html#app-structure-top-level-navigation-strategies
 * http://www.google.com/design/spec/layout/structure.html#structure-side-nav
 */

import React from 'react'
import classnames from 'classnames'
import {Logo, Button, Menu, ChevronRight} from '../icon/'

// height is menu item height.
// we need it in JS instead CSS to calculate overall height of submenu.
// e.g. 10 items in submenu => height = 10 * 48 = 480px.
// we have to know the overall height to animate between height 0 and height 480.
// unfortunately we cannot animate between height 0 and height: auto.
// that's why we cannot set the height in css.
const height = 44

class NavigationGroup extends React.Component {
  state = {
    isOpen: false
  }

  toggleOpen = event => {
    event.stopPropagation()
    this.setState({
      isOpen: !this.state.isOpen
    })
  }

  componentDidMount () {
    const children = Array.prototype.slice.call(this.listNode.children)
    this.setState({
      isOpen: children.find(child => child.classList.contains('active'))
    })
  }

  render () {
    const {isOpen} = this.state
    const {children} = this.props
    const maxHeight = isOpen ? React.Children.count(children) * height : 0
    return (
      <li className='mdc-Navigation-group'>
        <span className={classnames('mdc-Navigation-group-title', {'mdc-Navigation-group--opened': isOpen})} onClick={this.toggleOpen} >
          {this.props.title}
          <ChevronRight className='mdc-Navigation-group-icon' />
        </span>
        <ul className='mdc-Navigation-group-links' ref={node => { this.listNode = node }} style={{maxHeight}}>
          {children}
        </ul>
      </li>
    )
  }
}

/**
 * Navigation
 */
class Navigation extends React.Component {
  state = {
    visible: false
  }

  /**
   * Hide overlay
   */
  close = () => {
    this.setState({
      visible: false
    })
  }

  closeOverlay = (event) => {
    this.close()
    event.preventDefault()
  }

  /**
   * Open overlay
   */
  open = () => {
    this.setState({
      visible: true
    })
  }

  render () {
    return (
      <div className='mdc-Navigation'>
        <nav className={classnames('mdc-Navigation-sidebar', {'is-visible': this.state.visible})}>
          <div className='mdc-Navigation-logo'>
            <a href='#' onClick={this.close}>
              <Logo fill='#A7A5A5' />
            </a>
          </div>
          <ul className='mdc-Navigation-links' onClick={this.close} >
            {this.props.children}
          </ul>
        </nav>
        <div className='mdc-Navigation-hamburger'>
          <Button onClick={this.open}>
            <Menu />
          </Button>
        </div>
        <div
          className={classnames('mdc-Navigation-overlay', {
            'is-visible': this.state.visible
          })}
          onClick={this.closeOverlay}
          onTouchEnd={this.closeOverlay}
        />
      </div>
    )
  }
}

Navigation.Group = NavigationGroup

export default Navigation