UNPKG

boldr-ui

Version:

UI components for Boldr

239 lines (217 loc) 6.99 kB
import React, { Children, Component } from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; export const NavIcon = () => { throw new Error('Should not render'); }; export const NavText = () => { throw new Error('Should not render'); }; const SubNavIndi = styled.div` position: absolute; right: 16px; bottom: 4px; `; const findComponent = ComponentToFind => children => { return Children.toArray(children).reduce((found, next) => { if (found === null && next !== null && next.type === ComponentToFind) { return next; } return found; }, null); }; const findIcon = findComponent(NavIcon); const findText = findComponent(NavText); const identity = () => {}; const NavItemStyled = styled.div` padding: 8px 12px; cursor: pointer; position: relative; background: ${props => (props.isHighlighted ? props.highlightBgColor : 'inherit')}; color: ${props => (props.isHighlighted ? props.highlightColor : 'inherit')}; &:hover { color: ${props => props.hoverColor || props.highlightColor || 'inherit'} !important; background: ${props => props.hoverBgColor || props.highlightBgColor || 'inherit'} !important; } `; const NavIconCont = styled.div` vertical-align: middle; display: inline-flex; width: 42px; `; const NavTextCont = styled.div` vertical-align: middle; display: inline-flex; padding-right: 16px; `; const hasChildNav = children => { return Children.toArray(children).reduce((partial, next) => { return partial === false ? next.type === Nav : partial; }, false); }; const CollapsedIndicator = styled.div` &:before { border-style: solid; border-width: 0.15em 0.15em 0 0; content: ''; display: inline-block; height: ${props => props.size}; left: 0; position: relative; top: 0.15em; transform: rotate(${props => (!props.collapsed ? '135deg' : '45deg')}); vertical-align: top; width: ${props => props.size}; } `; const collectStyleAndClsName = comp => { const { className = undefined, style = {} } = comp && comp.props ? comp.props : {}; return { className, style }; }; export class Nav extends Component { static contextTypes = { highlightColor: PropTypes.string, highlightBgColor: PropTypes.string, hoverBgColor: PropTypes.string, hoverColor: PropTypes.string, }; static propTypes = { children: PropTypes.node, highlightColor: PropTypes.string, highlightBgColor: PropTypes.string, isHighlighted: PropTypes.bool, id: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]), onClick: PropTypes.func, onNavClick: PropTypes.func, highlightedId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), renderSubNavIndicator: PropTypes.func, hoverBgColor: PropTypes.string, hoverColor: PropTypes.string, expanded: PropTypes.bool, collapseIndicatorSize: PropTypes.string, }; static defaultProps = { onNavClick: identity, collapseIndicatorSize: '0.25em', }; constructor(props) { super(props); this.state = { collapsed: !props.expanded, }; } onNavItemClicked = () => { const { onClick = identity, onNavClick } = this.props; this.setState( { collapsed: !this.state.collapsed, }, () => { onNavClick(this.props.id, null); onClick(this.props.id, null); if (this.subNavEl && !this.s) { this.subNavEl.style.maxHeight = !this.state.collapsed ? null : '0px'; } }, ); }; childClicked = childId => { const { onNavClick } = this.props; onNavClick(childId, this.props.id); this.props.onClick(childId, this.props.id); }; setSubNavRef = subNavEl => { this.subNavEl = subNavEl; }; renderSubNavIndicator = () => { const { renderSubNavIndicator } = this.props; if (renderSubNavIndicator) { const subNavInd = renderSubNavIndicator(this.state.collapsed); if (!subNavInd && typeof console !== 'undefined') { console.warn('subNavIndicator returned undefined or null'); } return subNavInd || null; } return ( <CollapsedIndicator collapsed={this.state.collapsed} size={this.props.collapseIndicatorSize} /> ); }; render() { const { hoverBgColor, hoverColor, highlightColor, highlightBgColor, children, highlightedId, onNavClick = identity, id, } = this.props; const icon = findIcon(children); const text = findText(children); const itemProps = { hoverBgColor: hoverBgColor || this.context.hoverBgColor, hoverColor: hoverColor || this.context.hoverColor, onClick: this.onNavItemClicked, onNavClick, isHighlighted: id === highlightedId, highlightColor: highlightColor || this.context.highlightColor, highlightBgColor: highlightBgColor || this.context.highlightBgColor, }; return ( <div> <NavItemStyled className="boldrui-sidenav___item" {...itemProps}> <NavIconCont {...collectStyleAndClsName(icon)}> {icon && icon.props ? icon.props.children : null} </NavIconCont> <NavTextCont {...collectStyleAndClsName(text)}> {text && text.props ? text.props.children : null} </NavTextCont> {hasChildNav(children) ? <SubNavIndi> {this.renderSubNavIndicator()}{' '} </SubNavIndi> : null} </NavItemStyled> <div ref={this.setSubNavRef} style={{ maxHeight: this.state.collapsed ? 0 : null, transition: 'all 0.2s ease-in-out', }} > {Children.toArray(children) .filter(child => child.type === Nav && !this.state.collapsed) .map((child, idx) => { const sicon = findIcon(child.props.children); const stext = findText(child.props.children); const isItemHighlighted = highlightedId === `${id}/${child.props.id}`; return ( <NavItemStyled {...itemProps} className={'boldrui-sidenav___item--child'} key={idx} onClick={() => { child.props.onNavClick(), this.childClicked(`${id}/${child.props.id}`); }} isHighlighted={isItemHighlighted} > <NavIconCont {...collectStyleAndClsName(sicon)}> {null} </NavIconCont> <NavTextCont {...collectStyleAndClsName(stext)}> {stext ? stext.props.children : null} </NavTextCont> </NavItemStyled> ); })} </div> </div> ); } } export default Nav;