import { useCallback, useEffect, useState } from 'react' import IVInputField from '~/components/IVInputField' import { RCTResponderProps } from '~/components/RenderIOCall' import { IOComponentError } from '~/components/RenderIOCall/ComponentError' import Select, { InputProps, OptionProps, SingleValueProps, components, } from 'react-select' import classNames from 'classnames' import useDebounce from '~/utils/useDebounce' import usePrevious from '~/utils/usePrevious' import { StateManagerProps } from 'react-select/dist/declarations/src/useStateManager' import useInput from '~/utils/useInput' import TailwindChevronDown from '~/icons/compiled/TailwindChevronDown' import RenderValue from '~/components/RenderValue' import { dateTimeFormatter } from '~/utils/formatters' import { useMenuPlacement } from '../Search' import { preventDefaultInputEnterKey } from '~/utils/preventDefaultInputEnter' type IV_Option = RCTResponderProps<'SELECT_SINGLE'>['options'][0] const useSelectState = (props: RCTResponderProps<'SELECT_SINGLE'>) => { const [searchVal, setSearchVal] = useState('') const [value, setValue] = useState( (!(props.value instanceof IOComponentError) ? props.value : null) ?? null ) const [isLoading, setIsLoading] = useState(false) const debouncedSearchVal = useDebounce(searchVal, 500) const previousDebouncedSearchVal = usePrevious(debouncedSearchVal) const { isStateful, onStateChange } = props useEffect(() => { // no loading states or remote calls if not stateful if (!isStateful) return if (debouncedSearchVal === '') return if (previousDebouncedSearchVal === undefined) return if (debouncedSearchVal === previousDebouncedSearchVal) return setIsLoading(true) onStateChange({ queryTerm: debouncedSearchVal }) }, [ debouncedSearchVal, previousDebouncedSearchVal, onStateChange, isStateful, ]) // immediately show loading indicator when user starts typing while we wait for the debounced search term useEffect(() => { if (searchVal && isStateful) { setIsLoading(true) } }, [searchVal, isStateful]) useEffect(() => { setIsLoading(false) }, [props.options]) const onChange = (opt: IV_Option | null) => { setValue(opt) props.onUpdatePendingReturnValue(opt || undefined) } const { menuPlacement, maxMenuHeight, selectRef } = useMenuPlacement( props.options ) const selectProps: StateManagerProps = { value, onChange, isLoading, inputId: props.id, options: props.options, onInputChange: setSearchVal, autoFocus: props.autoFocus, className: 'iv-select-container', classNamePrefix: 'iv-select', isClearable: true, menuPlacement, maxMenuHeight, components: { Input: CustomInputRenderer, Option: CustomOptionRenderer, SingleValue: CustomSingleValueRenderer, DropdownIndicator: () => , IndicatorSeparator: ({ hasValue, isDisabled, ...props }) => { if (hasValue && !isDisabled) { return ( ) } return null }, }, filterOption: isStateful && !isLoading ? () => true : undefined, } return { value, options: props.options, onChange, isLoading, selectProps, selectRef, } } export default function SelectSingle( props: RCTResponderProps<'SELECT_SINGLE'> ) { const [isMenuOpen, setIsMenuOpen] = useState(false) const state = useSelectState(props) const { errorMessage } = useInput(props) const handleKeyDown = useCallback( (event: React.KeyboardEvent) => { if (!isMenuOpen) { preventDefaultInputEnterKey(event) } }, [isMenuOpen] ) return (
{...state.selectProps} aria-autocomplete="none" isSearchable={props.searchable} placeholder={props.isCurrentCall ? 'Select...' : ''} isDisabled={props.disabled || props.isSubmitting} ref={state.selectRef as never} onMenuOpen={() => { setIsMenuOpen(true) }} onMenuClose={() => { setIsMenuOpen(false) }} onKeyDown={handleKeyDown} />
) } function CustomOptionRenderer( option: OptionProps ) { const imageWidth = option.data.image?.size ?? 'thumbnail' return (
option.selectOption(option.data)} className={classNames('cursor-pointer flex items-center text-sm p-2', { 'bg-primary-400 text-white': option.isSelected, 'text-gray-700 hover:bg-gray-100': !option.isSelected, 'bg-blue-100': option.isFocused && !option.isSelected, })} > {(option.data.image?.url || option.data.imageUrl) && ( { )}

{option.data.description && ( {option.data.description || ''} )}

) } const CustomSingleValueRenderer = ( option: SingleValueProps ) => { return (
{(option.data.image?.url || option.data.imageUrl) && ( { )}

) } const CustomInputRenderer = (props: InputProps) => { return ( ) }