UNPKG

@muvehealth/fixins

Version:

Component library for Muvehealth

230 lines (209 loc) 6.28 kB
// @flow import Downshift from 'downshift' import React, { PureComponent } from 'react' import { contains, map, pluck } from 'ramda' import ErrorMessage from '../ErrorMessage' import InputWrapper from '../MSInputWrapper' import Item from '../MSItem' import Label from '../Label' import Menu from '../MSMenu' import TagItem from '../MSTagItem' import Relative from '../Relative' import { mapIndexed } from '../../utils' import { type SelectedItemType, type EventType, type ChangesType } from '../../types' type Props = { handleChangeValue: (Array<SelectedItemType>, SelectedItemType | Array<SelectedItemType>) => void | () => void, input: { value: Array<string>, }, label: string, meta: { touched: boolean, error: string, dispatch: (() => void) => void, }, name: string, options: Array<{ value: string, label: string, }>, } type State = { selectedItems: Array<SelectedItemType>, isOpen: boolean, } const getSelectedItems = (props) => { let items = [] const { input: { value } } = props if (value) { items = map(val => ({ label: val, tag: val, value: val }))(value) } return items } class Multiselect extends PureComponent<Props, State> { input = null inputWrapper = null constructor(props: Props) { super() this.state = { selectedItems: getSelectedItems(props), isOpen: false, } } onChange = ( selectedItem: SelectedItemType, other: { selectedItem: SelectedItemType | Array<SelectedItemType> }, ) => { const { selectedItems } = this.state if (selectedItems.includes(selectedItem)) { this.removeItem(selectedItem, other.selectedItem) } else { this.addItem(selectedItem, other.selectedItem) } } onWrapperClick = (e: EventType) => { if (this.inputWrapper === e.target || this.input === e.target) { this.focusOnInput() e.stopPropagation() e.preventDefault() } } addItem = (value: SelectedItemType, prevState: SelectedItemType | Array<SelectedItemType>) => { this.setState(({ selectedItems }, props) => { const newValues = [...selectedItems, value] props.handleChangeValue(newValues, prevState) return { selectedItems: newValues } }) } removeItem = (value: SelectedItemType, prevState: SelectedItemType | Array<SelectedItemType>) => { this.setState(({ selectedItems }, props) => { const newValues = selectedItems.filter(i => i !== value) props.handleChangeValue(newValues, prevState) return { selectedItems: newValues } }) } handleToggleMenu = () => { this.setState(({ isOpen }) => ({ isOpen: !isOpen })) } handleStateChange = (changes: ChangesType) => { const { isOpen, type } = changes const { isOpen: stateIsOpen } = this.state if (type === Downshift.stateChangeTypes.mouseUp && isOpen !== stateIsOpen) { this.setState(() => ({ isOpen })) } } itemToString = (value: { value: string }) => pluck('value', value) inputRef = (c: HTMLElement) => { this.input = c } inputWrapperRef = (c: HTMLElement) => { this.inputWrapper = c } focusOnInput() { if (this.input != null) { this.input.focus() } // $FlowFixMe if (this.input && typeof this.input.getInput === 'function') { this.input.getInput().focus() } } render() { const { name, options, label, meta, } = this.props const { selectedItems, isOpen } = this.state return ( <div> <Downshift isOpen={isOpen} onChange={this.onChange} selectedItem={selectedItems} onStateChange={this.handleStateChange} onToggleMenu={this.handleToggleMenu} onRemoveItem={this.removeItem} itemToString={this.itemToString} > {({ getLabelProps, getInputProps, getItemProps, isOpen: isMenuOpen, selectedItem, highlightedIndex, }) => ( <div> <Label {...getLabelProps()} htmlFor={name} textStyle="caps" > {label} </Label> <Relative> <InputWrapper innerRef={this.inputWrapperRef} onClick={this.onWrapperClick} tabIndex="-1" {...getInputProps({ onClick: this.handleToggleMenu })} > {(selectedItem.length > 0) && map(tag => ( <TagItem key={`Tag-${tag.value}`} tabIndex={tag.value} onKeyPress={(e) => { e.stopPropagation() this.onChange(tag, { selectedItem: selectedItems }) }} onClick={(e) => { e.stopPropagation() this.onChange(tag, { selectedItem: selectedItems }) }} > {tag.tag} </TagItem> ), selectedItems) } </InputWrapper> </Relative> {!isMenuOpen ? null : ( <Relative> <Menu> { mapIndexed((item, index) => ( <Item key={item.label} {...getItemProps({ item, index, isActive: highlightedIndex === index, isSelected: contains(item, selectedItem), })} > {item.label} </Item> ), options) } </Menu> </Relative> )} </div> )} </Downshift> {meta.touched && meta.error && ( <ErrorMessage message={meta.error} /> ) } </div> ) } } export default Multiselect