import React, { InputHTMLAttributes, MouseEventHandler } from 'react'; import ReactDOM from 'react-dom'; import { MessageDescriptor } from 'react-intl'; import clsx from 'clsx'; import { COLOR_GREEN, Color } from 'app/components/ui'; import styles from './dropdown.scss'; import FormInputComponent from './FormInputComponent'; type I18nString = string | MessageDescriptor; type ItemLabel = I18nString | React.ReactElement; interface Props extends InputHTMLAttributes { label: I18nString; items: { [value: string]: ItemLabel }; block?: boolean; color: Color; } interface OptionItem { label: ItemLabel; value: string; } interface State { isActive: boolean; activeItem: OptionItem | null; } export default class Dropdown extends FormInputComponent { static defaultProps: Partial = { color: COLOR_GREEN, }; state: State = { isActive: false, activeItem: null, }; componentDidMount() { // listen to capturing phase to ensure, that our event handler will be // called before all other // @ts-ignore document.addEventListener('click', this.onBodyClick, true); } componentWillUnmount() { // @ts-ignore document.removeEventListener('click', this.onBodyClick); } render() { const { color, block, items, ...restProps } = this.props; const { isActive } = this.state; delete restProps.label; const activeItem = this.getActiveItem(); const label = React.isValidElement(activeItem.label) ? activeItem.label : this.formatMessage(activeItem.label); return ( {label} {Object.entries(items).map(([value, label]) => ( {label} ))} {this.renderError()} ); } toggle() { this.setState({ isActive: !this.state.isActive, }); } onSelectItem(item: OptionItem): MouseEventHandler { return (event) => { event.preventDefault(); this.setState({ activeItem: item, }); }; } getActiveItem(): OptionItem { const { items } = this.props; let { activeItem } = this.state; if (!activeItem) { activeItem = { label: this.props.label, value: '', }; if (!activeItem.label) { const [[value, label]] = Object.entries(items); activeItem = { label, value, }; } } return activeItem; } getValue() { return this.getActiveItem()?.value; } onToggle = (event: React.MouseEvent) => { event.preventDefault(); this.toggle(); }; onBodyClick: MouseEventHandler = (event) => { if (this.state.isActive) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const el = ReactDOM.findDOMNode(this)!; if (!el.contains(event.target as HTMLElement) && el !== event.target) { event.preventDefault(); event.stopPropagation(); this.toggle(); } } }; }