Skip to content
This repository was archived by the owner on Sep 19, 2021. It is now read-only.

DRY the navigation components #430

Merged
merged 12 commits into from
Jun 18, 2018
77 changes: 24 additions & 53 deletions src/components/Navigation/Navigation.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import AuthenticatedView from '../../views/AuthenticatedView'
import { updateSection } from '../../actions/SectionActions'
import { navigation, env } from '../../config'
import { isActive, isValid, hasErrors } from './navigation-helpers'
import Section from './Section'
import { ToggleItem } from './ToggleItem'

class Navigation extends React.Component {
Expand All @@ -15,7 +14,6 @@ class Navigation extends React.Component {
selected: navigation[0].name
}

this.clicked = this.clicked.bind(this)
this.onToggle = this.onToggle.bind(this)
this.location = null
this.uselocation = true
Expand All @@ -37,13 +35,6 @@ class Navigation extends React.Component {
this.setState({ selected: item.visible ? item.title : '' })
}

clicked (url, event) {
const parts = (url || '').replace('/form/', '').split('/')
const section = parts.shift()
const subsection = parts.join('/') || 'intro'
this.props.dispatch(updateSection(section, subsection))
}

/**
* Get the classes to be applied to a link. This includes the following:
* - active
Expand Down Expand Up @@ -90,15 +81,12 @@ class Navigation extends React.Component {
const locked = subsection.locked && subsection.locked(this.props.application)
if (locked) {
return (
<div key={subsection.name} className="subsection">
<a href="javascript:;;;" className={`${subClass} locked`}>
<span className="section-name">
{subsection.name}
</span>
<span className="mini eapp-status-icon-valid"></span>
<span className="mini eapp-status-icon-error"></span>
</a>
</div>
<Section key={subsection.name}
isSubSection={true}
locked={true}
name={subsection.name}
subUrl={subUrl}
sectionClass={subClass}/>
)
}

Expand All @@ -116,15 +104,11 @@ class Navigation extends React.Component {
}

return (
<div key={subsection.name} className="subsection">
<Link to={subUrl} className={subClass} onClick={this.clicked.bind(this, subUrl)}>
<span className="section-name">
{subsection.name}
</span>
<span className="mini eapp-status-icon-valid"></span>
<span className="mini eapp-status-icon-error"></span>
</Link>
</div>
<Section key={subsection.name}
isSubSection={true}
name={subsection.name}
subUrl={subUrl}
sectionClass={subClass}/>
)
})

Expand Down Expand Up @@ -154,23 +138,18 @@ class Navigation extends React.Component {
// Increment the section number
sectionNum++

const displayNum = section.showNumber ? sectionNum : null

// If the section is locked then the navigation item is displayed but
// nothing else.
const locked = section.locked && section.locked(this.props.application)
if (locked) {
return (
<div key={section.name} className="section">
<span className="section-title">
<a href="javascript:;;;" className={`${sectionClass} locked`}>
<span className="section-number">{section.showNumber ? sectionNum : ''}</span>
<span className="section-name">
{section.name}
</span>
<span className="eapp-status-icon-valid"></span>
<span className="eapp-status-icon-error"></span>
</a>
</span>
</div>
<Section key={section.name}
name={section.name}
sectionClass={sectionClass}
sectionNum={displayNum}
locked={true}/>
)
}

Expand All @@ -183,7 +162,7 @@ class Navigation extends React.Component {
<ToggleItem title={section.name}
key={url}
section={true}
number={section.showNumber ? sectionNum : null}
number={displayNum}
className={sectionClass}
visible={visible}
onToggle={this.onToggle}>
Expand All @@ -193,18 +172,10 @@ class Navigation extends React.Component {
}

return (
<div key={section.name} className="section">
<span className="section-title">
<Link to={url} className={sectionClass} onClick={this.clicked.bind(this, url)}>
<span className="section-number">{section.showNumber ? sectionNum : ''}</span>
<span className="section-name">
{section.name}
</span>
<span className="eapp-status-icon-valid"></span>
<span className="eapp-status-icon-error"></span>
</Link>
</span>
</div>
<Section key={section.name}
name={section.name}
sectionClass={sectionClass}
sectionNum={displayNum}/>
)
})

Expand Down
34 changes: 17 additions & 17 deletions src/components/Navigation/Navigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,6 @@
.eapp-status-icon-error {
background-image: url('../img/exclamation-point-white-bg.svg');
}

.mini {
background-image: none;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
top: 2.3rem;
right: 0.6rem;
}

.mini.eapp-status-icon-valid {
background-color: $eapp-green;
}

.mini.eapp-status-icon-error {
background-color: $eapp-red;
}
}

.active {
Expand Down Expand Up @@ -224,6 +207,23 @@
&.has-errors {
border-left-color: $eapp-red;
}

.eapp-status-icon {
background-image: none;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
top: 2.3rem;
right: 0.6rem;
}

.eapp-status-icon-valid {
background-color: $eapp-green;
}

.eapp-status-icon-error {
background-color: $eapp-red;
}
}

.subsection > * {
Expand Down
2 changes: 0 additions & 2 deletions src/components/Navigation/NavigationToggle.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react'
import { connect } from 'react-redux'
import { i18n } from '../../config'
import { updateApplication } from '../../actions/ApplicationActions'
import { logout } from '../../actions/AuthActions'
import AuthenticatedView from '../../views/AuthenticatedView'

export class NavigationToggle extends React.Component {
Expand Down
49 changes: 49 additions & 0 deletions src/components/Navigation/Section.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import AuthenticatedView from '../../views/AuthenticatedView'
import { connect } from 'react-redux'
import { parseFormUrl } from './navigation-helpers'
import React from 'react'
import SectionLink from './SectionLink'
import { updateSection } from '../../actions/SectionActions'

class Section extends React.Component {
clicked() {
const parts = parseFormUrl(this.props.subUrl)
this.props.dispatch(updateSection(parts.section, parts.subsection))
}

render() {
let url, onClick
let sectionClass = this.props.sectionClass
if (this.props.locked) {
url = 'javascript:;;;'
onClick = ''
sectionClass += ' locked'
} else {
url = this.props.subUrl
onClick = this.clicked.bind(this)
}

const topCls = this.props.isSubSection ? 'subsection' : 'section'

return (
<div className={topCls}>
<span className="section-title">
<SectionLink
className={sectionClass}
title={this.props.name}
onClick={onClick}
sectionNum={this.props.sectionNum}
to={url} />
</span>
</div>
)
}
}

Section.defaultProps = {
isSubSection: false,
locked: false,
sectionNum: null
}

export default connect()(AuthenticatedView(Section))
29 changes: 29 additions & 0 deletions src/components/Navigation/SectionLink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Link } from 'react-router-dom'
import React from 'react'
import { Show } from '../Form'

export default class SectionLink extends React.Component {
render() {
// https://codeburst.io/use-es2015-object-rest-operator-to-omit-properties-38a3ecffe90
const { sectionNum, ...passThroughProps } = this.props

return (
<Link {...passThroughProps}>
<Show when={sectionNum}>
<span className="section-number">{sectionNum}</span>
</Show>
<span className="section-name">
{this.props.title}
{this.props.children}
</span>
<span className="eapp-status-icon eapp-status-icon-valid"></span>
<span className="eapp-status-icon eapp-status-icon-error"></span>
</Link>
)
}
}

SectionLink.defaultProps = {
to: 'javascript:;;',
sectionNum: null
}
29 changes: 12 additions & 17 deletions src/components/Navigation/ToggleItem.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { Show } from '../Form'
import SectionLink from './SectionLink'

export class ToggleItem extends React.Component {
constructor (props) {
Expand Down Expand Up @@ -60,27 +61,21 @@ export class ToggleItem extends React.Component {
}

render () {
const validIcon = `${this.props.section ? '' : 'mini'} eapp-status-icon-valid`.trim()
const errorIcon = `${this.props.section ? '' : 'mini'} eapp-status-icon-error`.trim()
return (
<div ref="item" className={`${this.props.section ? 'section' : 'subsection'} ${this.state.visible ? 'open' : 'closed'}`}>
<span className="section-title">
<a href="javascript:;;;" title={this.props.title} className={this.props.className} onClick={this.toggle}>
<Show when={this.props.number}>
<span className="section-number">{this.props.number}</span>
<SectionLink
className={this.props.className}
title={this.props.title}
onClick={this.toggle}
sectionNum={this.props.number}>
<Show when={this.state.visible}>
<i className="fa fa-angle-up" aria-hidden="true"></i>
</Show>
<span className="section-name">
{this.props.title}
<Show when={this.state.visible}>
<i className="fa fa-angle-up" aria-hidden="true"></i>
</Show>
<Show when={!this.state.visible}>
<i className="fa fa-angle-down" aria-hidden="true"></i>
</Show>
</span>
<span className={validIcon}></span>
<span className={errorIcon}></span>
</a>
<Show when={!this.state.visible}>
<i className="fa fa-angle-down" aria-hidden="true"></i>
</Show>
</SectionLink>
<Show when={this.state.visible}>
<div className="section-content">
{this.props.children}
Expand Down
5 changes: 3 additions & 2 deletions src/components/Navigation/ToggleItem.test.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React from 'react'
import { MemoryRouter } from 'react-router'
import { mount } from 'enzyme'
import { ToggleItem } from './ToggleItem'

describe('The toggle item component', () => {
it('is closed by default', () => {
const props = {}
const component = mount(<ToggleItem {...props} />)
const component = mount(<MemoryRouter><ToggleItem {...props} /></MemoryRouter>)
expect(component.find('.closed').length).toBe(1)
})

it('can open', () => {
const props = {}
const component = mount(<ToggleItem {...props} />)
const component = mount(<MemoryRouter><ToggleItem {...props} /></MemoryRouter>)
component.find('a').simulate('click')
expect(component.find('.closed').length).toBe(0)
expect(component.find('.open').length).toBe(1)
Expand Down
Loading