Instantly share code, notes, and snippets.
Last active
August 1, 2018 21:05
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save grantglidewell/bf5518a64322afd96b165a41ba3799b5 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
Data: | |
const content = [ | |
{ | |
title: 'CONTENT TITLE', | |
icon: 'fa-icon', | |
children: [ | |
{ | |
name: 'CONTENT LINK', | |
path: 'nav/as4da-asdads-ds4a', | |
icon: 'fa-icon' | |
}, | |
{ | |
name: 'CONTENT LINK', | |
path: 'nav/asda-asda4ds-dsa', | |
icon: 'bars', | |
children: [...] | |
} | |
] | |
} | |
] | |
Code: | |
export class Nav extends Component { | |
render() { | |
return ( | |
<nav className={`${styles.Nav} ${this.props.className}`}> | |
{this.props.content.map(item => { | |
// create a Parent for each object in the array | |
// parents render children if present | |
return ( | |
<Parent {...item} key={item.title} selected={this.props.selected} /> | |
); | |
})} | |
</nav> | |
); | |
} | |
} | |
export class Parent extends Component { | |
state = { | |
active: false, | |
closed: [] | |
} | |
render() { | |
return ( | |
<article | |
onMouseEnter={() => { | |
this.setState({ active: true }) | |
}} | |
onMouseLeave={() => { | |
this.setState({ active: false }) | |
}} | |
className={styles.Parent}> | |
{/* if the item has a title, render it out */} | |
{this.props.title && ( | |
<span | |
className={`${styles.title} ${this.state.active && styles.active}`}> | |
<h2> | |
<i className={`fa fa-${this.props.icon} ${styles.titleIcon}`} />{' '} | |
{this.props.title} | |
</h2> | |
{Boolean(this.props.children.length) && ( | |
<i | |
className={ | |
this.state.closed.includes(this.props.title) | |
? 'fa fa-caret-left' | |
: 'fa fa-caret-down' | |
} | |
onClick={() => this.handleOpen(this.props.title)} | |
/> | |
)} | |
</span> | |
)} | |
<ul>{this.renderMenuItem(this.props)}</ul> | |
</article> | |
) | |
} | |
handleOpen = path => { | |
// add or remove path from closed state array | |
let replaceClosed = [...this.state.closed] | |
if (this.state.closed.includes(path)) { | |
replaceClosed = this.state.closed.filter(e => e !== path) | |
} else { | |
replaceClosed.push(path) | |
} | |
return this.setState({ closed: replaceClosed }) | |
} | |
renderMenuItem = item => { | |
// track recursion depth and pass it as a prop to the node component | |
const recursionDepth = item.depth + 1 || 1 | |
return ( | |
!this.state.closed.includes(item.title) && | |
item.children.map( | |
child => | |
child.children ? ( | |
// if the child has children | |
// render the child and then it's children | |
<React.Fragment key={child.path}> | |
<Node | |
{...child} | |
selected={item.selected} | |
active={this.state.active} | |
closed={item.closed} | |
collapsed={this.state.closed} | |
depth={recursionDepth} | |
handleOpen={this.handleOpen} | |
/> | |
<Parent | |
{...child} | |
depth={recursionDepth} | |
selected={item.selected} | |
active={this.state.active} | |
closed={this.state.closed.includes(child.path) || item.closed} | |
/> | |
</React.Fragment> | |
) : ( | |
<Node | |
{...child} | |
selected={item.selected} | |
depth={recursionDepth} | |
active={this.state.active} | |
closed={item.closed} | |
handleOpen={this.handleOpen} | |
key={child.path} | |
/> | |
) | |
) | |
) | |
} | |
} | |
export class Node extends PureComponent { | |
render() { | |
const { | |
active, | |
selected, | |
closed, | |
collapsed, | |
depth, | |
path, | |
icon, | |
name, | |
children, | |
handleOpen | |
} = this.props | |
// style if a node is active | |
const isActive = (active && styles.active) || '' | |
// style is a node is selected | |
const isSelected = (selected === path && styles.selected) || '' | |
// check if a parent node is collapsed | |
const isClosed = closed && styles.closed | |
const isCollapsed = | |
collapsed && collapsed.filter(collapsed => collapsed === path).length | |
return ( | |
<li | |
className={`${styles.item} ${isActive} ${ | |
styles[`depth${depth}`] | |
} ${isSelected} ${isClosed}`} | |
key={path}> | |
<a href={`/${path}`}> | |
<i className={`fa fa-${icon}`} /> | |
<span>{name}</span> | |
</a> | |
{children && ( | |
<i | |
className={isCollapsed ? 'fa fa-caret-left' : 'fa fa-caret-down'} | |
onClick={() => handleOpen(path)} | |
/> | |
)} | |
</li> | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment