import React, { FocusEvent, HTMLProps } from 'react'
import Downshift from 'downshift'
import styled from 'styled-components'
import * as O from 'fp-ts/lib/Option'
import { pipe } from 'fp-ts/lib/pipeable'
import { flexFlow } from '@monorail/helpers/flex'
import { isNonEmptyString } from '@monorail/sharedHelpers/typeGuards'
import { DisplayType } from '@monorail/visualComponents/inputs/inputTypes'
import { Label } from '@monorail/visualComponents/inputs/Label'
import { useFormMultiSelectInput } from './FormMultiSelectInput.hooks'
import { FormMultiSelectInputProps } from './FormMultiSelectInput.types'
const ENTER_KEY_VALUE = 'Enter'
const ESCAPE_KEY_VALUE = 'Escape'
const FlexColumn = styled.div`
${flexFlow('column')};
margin-bottom: 24px;
width: 100%;
`
/**
* Provides the functionality behind creating an input of some kind that will
* filter a set of suggestions or can be used to create new values by pressing
* the 'Enter' key. Multiple UIs have been mentioned by design, so this
* defers UI concerns to render props to leave it open for extension.
*
* It has been attempted to try to satisfy the Open-Closed Principle - the "O" in SOLID -
* to make it easy enough to build forms with multiple UI variants with the same
* multi-select functionality.
*
* @example
*
* --------------------------
* | renderSelectedOptions |
* --------------------------
* | renderInput |
* --------------------------
* | renderSuggestions |
* --------------------------
*
*/
export function FormMultiSelectInput(props: FormMultiSelectInputProps) {
const {
containerCss,
defaultHighlightedIndex,
disabled,
display,
label,
renderInput,
renderSelectedOptions,
renderSuggestions,
required = false,
searchValueToItem,
selectedOptions,
} = props
const {
addItem,
checkIsHighlighted,
removeOption,
searchValue,
setSearchValue,
suggestions,
} = useFormMultiSelectInput(props)
return (
{({
getInputProps,
getItemProps,
getRootProps,
highlightedIndex,
isOpen,
toggleMenu,
}) => {
const defaultInputProps = {
disabled,
onFocus: () => toggleMenu({ isOpen: true }),
onBlur: (ev: FocusEvent) => {
toggleMenu({ isOpen: false })
setSearchValue('')
ev.target.value = ''
},
onChange: (ev: { currentTarget: HTMLInputElement }) =>
setSearchValue(ev.currentTarget.value),
onKeyDown: (
ev: KeyboardEvent & { currentTarget: HTMLInputElement },
) => {
const notSelectingHighlightedOption = highlightedIndex === null
const enterKeyWasPressed = ev.key === ENTER_KEY_VALUE
if (notSelectingHighlightedOption && enterKeyWasPressed) {
// Don't trigger a form submit
ev.preventDefault()
pipe(
ev.currentTarget.value,
val => val.replace(/\s+/g, ' ').trim(), // remove excess whitespace
O.fromPredicate(isNonEmptyString), // confirm searchValue is non-empty
O.chain(searchValueToItem),
O.fold(
() => {},
v => {
addItem(v)
ev.currentTarget.value = ''
},
),
)
}
},
placeholder: 'Type any tag...',
}
const suggestionInfo = {
getSuggestionProps: getItemProps,
isHighlighted: (option: A) =>
checkIsHighlighted(option, highlightedIndex),
isOpen: isOpen && suggestions.length > 0 && searchValue.length > 0,
searchValue,
isFocused: isOpen,
selectedOptions,
}
const sectionProps = getRootProps() as HTMLProps
return (
{renderSelectedOptions(selectedOptions, removeOption)}
{display === DisplayType.Edit && (
<>
{renderInput(getInputProps(defaultInputProps))}
{renderSuggestions(suggestions, suggestionInfo)}
>
)}
)
}}
)
}