Skip to content

Instantly share code, notes, and snippets.

@grantglidewell
Last active August 1, 2018 21:05
Show Gist options
  • Save grantglidewell/bf5518a64322afd96b165a41ba3799b5 to your computer and use it in GitHub Desktop.
Save grantglidewell/bf5518a64322afd96b165a41ba3799b5 to your computer and use it in GitHub Desktop.
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