/** * @file AsideNav * @description 左侧导航。 * @author fex */ import React from 'react'; import {mapTree} from '../utils/helper'; import {ClassNamesFn, themeable} from '../theme'; export type LinkItem = LinkItemProps; interface LinkItemProps { id?: number; label: string; hidden?: boolean; open?: boolean; active?: boolean; className?: string; children?: Array; path?: string; icon?: string; component?: React.ReactType; } export interface Navigation { label: string; children: Array; prefix?: JSX.Element; affix?: JSX.Element; className?: string; [propName: string]: any; } export interface AsideNavProps { id?: string; className?: string; classPrefix: string; classnames: ClassNamesFn; renderLink: Function; isActive: Function; isOpen: (link: LinkItemProps) => boolean; navigations: Array; renderSubLinks: ( link: LinkItemProps, renderLink: Function, depth: number, props: AsideNavProps ) => React.ReactNode; } interface AsideNavState { navigations: Array; } export class AsideNav extends React.Component { static defaultProps = { renderLink: (item: LinkItemProps) => {item.label}, renderSubLinks: ( link: LinkItemProps, renderLink: Function, depth: number, {classnames: cx}: AsideNavProps ) => link.children && link.children.length ? (
    {link.label ? (
  • {renderLink( { ...link, children: undefined }, 'subHeader', {}, depth )}
  • ) : null} {link.children.map((link, key) => renderLink(link, key, {}, depth + 1) )}
) : link.label && depth === 1 ? (
{link.label}
) : null, isActive: (link: LinkItem) => link.open, isOpen: (item: LinkItemProps) => item.children ? item.children.some(item => item.open) : false }; constructor(props: AsideNavProps) { super(props); const isOpen = props.isOpen; let id = 1; this.state = { navigations: mapTree( props.navigations, (item: Navigation) => { const isActive = typeof item.active === 'undefined' ? (props.isActive as Function)(item) : item.active; return { ...item, id: id++, active: isActive, open: isActive || isOpen(item as LinkItemProps) }; }, 1, true ) }; this.renderLink = this.renderLink.bind(this); this.toggleExpand = this.toggleExpand.bind(this); } componentWillReceiveProps(nextProps: AsideNavProps) { const props = this.props; const isOpen = props.isOpen; if ( props.navigations !== nextProps.navigations || props.isActive !== nextProps.isActive ) { let id = 1; this.setState({ navigations: mapTree( nextProps.navigations, (item: Navigation) => { const isActive = typeof item.active === 'undefined' ? (nextProps.isActive as Function)(item) : item.active; return { ...item, id: id++, active: isActive, open: isActive || isOpen(item as LinkItemProps) }; }, 1, true ) }); } } toggleExpand(link: LinkItemProps, e?: React.MouseEvent) { if (e) { e.stopPropagation(); e.preventDefault(); } this.setState({ navigations: mapTree( this.state.navigations, (item: Navigation) => ({ ...item, open: link.id === item.id ? !item.open : item.open }), 1, true ) }); } renderLink( link: LinkItemProps, key: any, props: Partial = {}, depth = 1 ): React.ReactNode { const { renderLink, isActive, renderSubLinks, classnames: cx, ...others } = this.props; const dom = (renderLink as Function)({ link, active: link.active, open: link.open, toggleExpand: this.toggleExpand, depth, classnames: cx, subHeader: key === 'subHeader', ...others }); if (!dom) { return; } else if (key === 'subHeader') { return React.cloneElement(dom, { key }); } return (
  • {dom} {renderSubLinks(link, this.renderLink, depth, this.props)}
  • ); } render() { const navigations = this.state.navigations; let links: Array = []; const {className, classnames: cx} = this.props; navigations.forEach((navigation, index) => { if (!Array.isArray(navigation.children)) { return; } if (navigation.prefix) { const prefix: JSX.Element = typeof navigation.prefix === 'function' ? (navigation.prefix as any)(this.props) : navigation.prefix; links.push( React.cloneElement(prefix, { ...prefix.props, key: `${index}-prefix` }) ); } navigation.label && links.push(
  • {navigation.label}
  • ); navigation.children.forEach((item, key) => { const link = this.renderLink(item, `${index}-${key}`); link && links.push(link); }); if (navigation.affix) { const affix: JSX.Element = typeof navigation.affix === 'function' ? (navigation.affix as any)(this.props) : navigation.affix; links.push( React.cloneElement(affix, { ...affix.props, key: `${index}-affix` }) ); } }); return ( ); } } export default themeable(AsideNav);