/** * Drop-down list component * * @author Brauer Ilya * @date 28-02-2020 */ import * as React from 'react'; import {PLACEMENT, Popover, TRIGGER} from '../../index'; import {Header} from './Header'; import {Item} from './Item'; import {Splitter} from './Splitter'; import {IProps as PopoverProps} from '../popover/Popover.types'; import * as styles from './dropdown.m.scss'; type IProps = PopoverProps & { isDisabled?: boolean; 'data-qaid'?: string; forwardRef?: React.RefObject; hasNavigation?: boolean; } interface IState { navigateIndex: number | undefined; childrenCount: number; } export class DropDown extends React.Component { static defaultProps = { trigger: TRIGGER.CLICK, placement: PLACEMENT.BOTTOM_START, hasShadow: true, hasArrow: true, hasPaddings: false, mouseEnterDelay: 10, mouseLeaveDelay: 10 }; static Header = Header; static Item = Item; static Splitter = Splitter; override state: IState = { navigateIndex: undefined, childrenCount: React.Children.count(this.props.children) } navigateIndexes: number[] = []; override componentDidMount () { if (this.props.children && Boolean(this.props.hasNavigation)) { this.initNavigation(); } } override componentDidUpdate (prevProps: Readonly, prevState: Readonly, snapshot?: any) { /* * For correct dropdown navigation in case, if data doesn't available at first render, need to pass null. */ if (Boolean(this.props.hasNavigation) && prevProps.isOpen === false && this.props.isOpen === true && this.navigateIndexes.length > 0 ) { this.setState({navigateIndex: undefined}); } const childrenCount = React.Children.count(this.props.children); if (Boolean(this.props.hasNavigation) && (childrenCount !== this.state.childrenCount)) { this.initNavigation(); this.setState({childrenCount}); } if (prevProps.children && !this.props.children) { document.removeEventListener('keydown', this.onKeyDown); } } override componentWillUnmount () { document.removeEventListener('keydown', this.onKeyDown); } initNavigation = () => { const navigateIndexes: number[] = []; React.Children.forEach(this.props.children, (child, index) => { if (child && typeof child === 'object' && 'type' in child && child.type === DropDown.Item) { navigateIndexes.push(index); } }); this.navigateIndexes = navigateIndexes; document.addEventListener('keydown', this.onKeyDown); } onKeyDown = (e: KeyboardEvent): void => { if (this.props.isOpen && Boolean(this.props.hasNavigation)) { if (e.key === 'ArrowUp') { this.setState((prevState) => { if (prevState.navigateIndex !== undefined) { return { navigateIndex: Math.max(prevState.navigateIndex - 1, 0), childrenCount: prevState.childrenCount }; } return prevState; }); } else if (e.key === 'ArrowDown') { this.setState((prevState) => { if (prevState.navigateIndex !== undefined) { return { navigateIndex: Math.min(prevState.navigateIndex + 1, this.navigateIndexes.length - 1), childrenCount: prevState.childrenCount }; } else if (this.navigateIndexes.length > 0) { return { navigateIndex: 0, childrenCount: prevState.childrenCount }; } return prevState; }); } } } override render () { const {isDisabled, isOpen, onVisibleChange, forwardRef, hasNavigation, ...otherProps} = this.props; const navigateIndex = this.state.navigateIndex; return (
{ navigateIndex === undefined || !hasNavigation ? ( this.props.children ) : ( React.Children.map(this.props.children, (child, index) => { if (index === this.navigateIndexes[navigateIndex] && child && typeof child === 'object' && 'type' in child && child.type === DropDown.Item ) { const childClone = React.cloneElement(child, {...child.props, isSelected: true}); return childClone; } return child; }) ) }
); } }