import React, { ReactElement } from 'react' import Downshift, { ControllerStateAndHelpers, DownshiftState, StateChangeOptions, } from 'downshift' import styled, { css } from 'styled-components' import { pipe } from 'fp-ts/lib/function' import { fromNullable, Option, toNullable } from 'fp-ts/lib/Option' import { flexFlow, FontSizes, typographyFont } from '@monorail/helpers/exports' import { CssOverrides } from '@monorail/StyleHelpers' import { CommonComponentType } from '@monorail/types' import { DownshiftRootPropsGetter, DropdownItemValue, DropdownType, } from '@monorail/visualComponents/dropdown/helpers' import { DisplayType } from '@monorail/visualComponents/inputs/inputTypes' import { BehaviorControllerHook, StateReducer, useAsFilter, useControlledDropdown, } from './behavior' import { createKeyboardInteraction, InteractionController, KeyboardInteractionHook, } from './interaction' import { createDropdownTypeParser, DropdownParser, DropdownParserHook, } from './parsers' import { DropdownSkinCommonType, DropdownSkinComponent, useDropdownSkin, } from './skin' export type DropdownChangeHandler = ( item?: D, downshiftProps?: ControllerStateAndHelpers, ) => void export type DropdownHooks = { behavior?: BehaviorControllerHook interaction?: KeyboardInteractionHook parser?: DropdownParserHook skin?: DropdownSkinComponent } export type DropdownProps = CommonComponentType & DropdownHooks & DropdownSkinCommonType & { items: Array value?: D | DropdownItemValue onChange?: DropdownChangeHandler triggerOnAllSelections?: boolean // false triggers on value CHANGES, true triggers no matter which value is chosen onBlur?: VoidFunction error?: Option required?: boolean cssOverrides?: CssOverrides } export const DropdownContainer = styled.div( ({ cssOverrides }: { cssOverrides?: CssOverrides }) => css` ${flexFlow('column')}; ${typographyFont(400, FontSizes.Title5)}; position: relative; width: 256px; max-width: 100%; ${cssOverrides} `, ) const createKeyboardInteractionDefault = ( parser: DropdownParser, ): InteractionController => createKeyboardInteraction()(parser) export const Dropdown = ({ label, placeholder = 'Select', disabled = false, clearable = true, extraWidth = 0, items: collection, value, onChange, triggerOnAllSelections = false, behavior = useAsFilter, skin = useDropdownSkin, parser = createDropdownTypeParser, interaction = createKeyboardInteractionDefault, error, required, display = DisplayType.Edit, cssOverrides, ...domProps }: DropdownProps): ReactElement> => { /** Controllers **/ const parserController = parser() const interactionController = interaction(parserController) const behaviorController = behavior(collection, parserController) /** Selected Dropdown Item **/ const [ selectedDropdownItem, setSelectedDropdownItem, selectedItemChanged, ] = useControlledDropdown({ value, collection, parser: parserController }) const skinController = skin({ parser: parserController, interaction: interactionController, disabled, clearable, placeholder, error, required, label, display, extraWidth, }) const onChangeHandler: DropdownChangeHandler = (item, downshiftProps) => { setSelectedDropdownItem(fromNullable(item)) onChange && onChange(item, downshiftProps) } // Base Downshift state reducer const reduceStateBase: StateReducer = () => changes => { switch (changes.type) { case Downshift.stateChangeTypes.clickButton: case Downshift.stateChangeTypes.clickItem: return { ...changes, inputValue: '', } case Downshift.stateChangeTypes.mouseUp: return { type: changes.type, inputValue: '', isOpen: false } default: return changes } } const stateReducer = ( state: DownshiftState, changes: StateChangeOptions, ) => pipe( changes, reduceStateBase(state), behaviorController.stateReducer(state), interactionController.stateReducer(state), ) return ( {(downshiftProps: ControllerStateAndHelpers) => { const { getRootProps, inputValue } = downshiftProps const rootProps = getRootProps() as DownshiftRootPropsGetter return ( {skinController({ items: behaviorController.getItems(inputValue || ''), downshiftProps, })} ) }} ) }