import React from 'react' import _ from 'lodash' import { faEdit } from '@fortawesome/free-solid-svg-icons' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { multiSelectTheme } from './multiselect.theme' /* Import components here */ import { AddChip, StyledChip as Chip, Display, HiddenSelect, StyledDialogBox, StyledMultiSelect, } from './multiselect.styles' import { Button } from '../button' import { List } from '../../list' /* Import interfaces here */ import { MultiSelectProps } from './multiselect.interfaces' import { useConfirmation } from '../../confirm-modal' import { Writeable, isRefObject } from '../../utils' /* Import utilities here */ // Noop, handler when selecting a value directly in Select, to prevent React error const noop = (): void => undefined /** * Multiselect input widget, renders selected values as chips */ export const MultiSelect = (props: MultiSelectProps): JSX.Element => { const { name, showAddButtonWhenDisabled = false, disabled = false, addButtonText = '', options, inputRef } = props const selectRef = useRef() const [selectedValues, setSelectedValues] = useState([]) const [show, setShow] = useState(false) const [confirmRemove, ConfirmRemoveDialog] = useConfirmation(`Do you want to remove ${name}?`, [ // TODO: Fix types // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore { text: 'No', value: false }, // TODO: Fix types // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore { text: 'Yes', value: true }, ]) // Initialize values useEffect(() => { if (selectRef.current === undefined) return const options = Array.from(selectRef.current.options) const values = _.reduce( options, (acc, cur) => (cur.selected ? [...acc, cur.value] : acc), [], ) setSelectedValues(values) }, [options]) const labelMap: Record = _.reduce(options, (acc, cur) => ({ ...acc, [cur.value]: cur.label }), {}) const isChecked = useMemo(() => { const valueMap: { [key: string]: true } = _.reduce(selectedValues, (acc, cur) => ({ ...acc, [cur]: true }), {}) return (key: string): boolean => valueMap[key] || false }, [selectedValues]) const fireChangeEvent = useCallback((): void => { setTimeout(() => { const change = new Event('input', { bubbles: true, cancelable: true }) const blur = new Event('blur', { bubbles: true, cancelable: true }) selectRef.current?.dispatchEvent(change) selectRef.current?.dispatchEvent(blur) }, 0) }, []) /** * setValue marks the value as selected */ const setValue = useCallback( (value: string) => { const values = _.uniq([...selectedValues, value]) setSelectedValues(values) fireChangeEvent() }, [fireChangeEvent, selectedValues], ) /** * resetValue deselectes the value */ const resetValue = useCallback( (value: string) => { const values = _.filter(selectedValues, v => v !== value) setSelectedValues(values) fireChangeEvent() }, [fireChangeEvent, selectedValues], ) const handleClickCheck = (e: React.ChangeEvent): void => { const value = e.target.dataset.val || '' if (e.target.checked) { setValue(value) } else { resetValue(value) } } const refCallback = useCallback( (ref: HTMLSelectElement): void => { selectRef.current = ref // Handle inputRef if (inputRef === undefined) return // TODO: Fix correct types and remove disabled rules // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (isRefObject(inputRef)) { // TODO: Fix correct types and remove disabled rules // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // eslint-disable-next-line no-extra-semi ;(inputRef as Writeable).current = ref } else { // TODO: Fix correct types and remove disabled rules // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore inputRef(ref) } }, [inputRef], ) // Handles show/hide of selection box const handleClick = useCallback((): void => { setShow(true) }, []) const handleClose = useCallback((): void => { setShow(false) }, []) const handleClickChip = async (value: string): Promise => { if (await confirmRemove({ name: labelMap[value] })) resetValue(value) } return ( {selectedValues.map(v => ( ))} {!disabled || (disabled && showAddButtonWhenDisabled) ? ( ) : null} {options.map(({ value, label }, i) => ( ))} {options.map(({ value, label }, i) => ( ))} {ConfirmRemoveDialog} ) } MultiSelect.defaultTheme = multiSelectTheme