import React from 'react'; import PropTypes from 'prop-types'; import styles from './Dropdown.css'; import Icon from './Icon'; import cn from 'classnames'; import ClickOutside from 'coral-framework/components/ClickOutside'; class Dropdown extends React.Component { toggleRef = null; optionsRef = []; state = { isOpen: false }; componentDidUpdate(_, prevState) { if (!this.state.isOpen && prevState.isOpen) { // Refocus on the toggle element when menu closes. this.toggleRef.focus(); } } goUp = () => { const index = this.optionsRef.findIndex((ref) => ref.hasFocus()); if (index > 0) { this.optionsRef[index - 1].focus(); } } goDown = () => { const index = this.optionsRef.findIndex((ref) => ref.hasFocus()); if (index < this.optionsRef.length - 1) { this.optionsRef[index + 1].focus(); } } handleOptionKeyDown = (value, e) => { const code = e.which; switch (code) { case 13: // 13 = Return case 32: // 32 = Space e.preventDefault(); this.setValue(value); break; case 38: // 38 = Arrow Up e.preventDefault(); this.goUp(); break; case 40: // 40 = Arrow Down e.preventDefault(); this.goDown(); break; } } handleOptionClick = (value) => { this.setValue(value); } setValue = (value) => { if (this.props.onChange) { this.props.onChange(value); } this.setState({ isOpen: false }); } toggle = () => { this.setState({ isOpen: !this.state.isOpen }); } handleClick = () => { this.toggle(); } handleKeyDown = (e) => { const code = e.which; // 13 = Return, 32 = Space if ((code === 13) || (code === 32)) { e.preventDefault(); this.toggle(); } } hideMenu = () => { this.setState({ isOpen: false }); } handleToggleRef = (ref) => this.toggleRef = ref; handleOptionsRef = (ref, index) => { this.optionsRef[index] = ref; // Focus on current value when menu opens. if (ref) { if (ref.props.value === this.props.value || index === 0 && !this.props.value) { ref.focus(); return; } } } // Trap keyboard focus inside the dropdown until a value has been chosen. trapFocusBegin = () => this.optionsRef[this.optionsRef.length - 1].focus(); trapFocusEnd = () => this.optionsRef[0].focus(); renderLabel() { const options = React.Children.toArray(this.props.children); const option = options.find((option) => option.props.value === this.props.value); if (option) { return option.props.label ? option.props.label : option.props.value; } else if (this.props.value) { return this.props.value; } else { return this.props.placeholder; } } render() { const {containerClassName, toggleClassName, toggleOpenClassName} = this.props; return (
{this.props.icon &&
{this.state.isOpen &&
    {React.Children.toArray(this.props.children) .map((child, i) => React.cloneElement(child, { key: child.props.value, ref: (ref) => this.handleOptionsRef(ref, i), index: i, onClick: () => this.handleOptionClick(child.props.value), onKeyDown: (e) => this.handleOptionKeyDown(child.props.value, e) }))}
}
); } } Dropdown.propTypes = { containerClassName: PropTypes.string, toggleClassName: PropTypes.string, toggleOpenClassName: PropTypes.string, placeholder: PropTypes.string, icon: PropTypes.string, onChange: PropTypes.func.isRequired, children: PropTypes.node.isRequired, value: PropTypes.oneOfType([ PropTypes.number, PropTypes.string, PropTypes.bool ]), }; export default Dropdown;