import { default as ReactSelect } from 'react-select'; import classNames from 'classnames'; import { InjectedFieldProps } from '../Field/InjectedFieldProps'; import { FormDefaults } from '../FormDefaults'; import Group, { GroupProps } from '../Group'; export interface SelectOption { /** Value for the select. This will be sent to the API. */ value: (TValue extends Array ? P : TValue) | undefined; /** Label for the select. This is displayed to the user. */ label: string; } // types roughly like the useService (envoc-core) result interface OptionsApiResult { // we must apply Partial here because all results from template code gen are optional result?: Partial>[]; } interface OptionsUseServiceResult { loading?: boolean; // we must apply Partial here because all results from template code gen are optional resp?: Partial>[] | OptionsApiResult | null; error?: any; } export interface SelectGroupProps extends InjectedFieldProps, Omit { // allows for "useService" or other handles to control the data - including cache // TODO: do we still want a version of select that has a "url" or maybe a promise func or something? // eventually we can just add the shape of, say, useQuery (TanStack) to the union type /** Options for the dropdown. Includes the label and value. */ options: SelectOption[] | OptionsUseServiceResult; /** Whether the user should be able to have multiple values selected. */ multiple: TValue extends Array ? true : false; /** Text diplayed when no value is selected. */ placeholder?: string; } // TODO: we could also name this "ReactSelectGroup" or similar but the types are strongly defined now so kept the names consistent /** Generic select dropdown. Uses [react-select](https://react-select.com/home). */ export default function SelectGroup({ input, meta, className, required, disabled, options, multiple, placeholder, ...rest }: SelectGroupProps) { const effectiveOptions: Partial>[] = !options ? [] : Array.isArray(options) ? options : 'resp' in options && !!options.resp && 'result' in options.resp && !!options.resp.result ? options.resp.result : 'resp' in options && !!options.resp && Array.isArray(options.resp) ? options.resp : []; const isLoading = (options && 'loading' in options && options.loading) || false; return ( >, TValue extends Array ? true : false > inputId={input.id} isMulti={multiple} isDisabled={disabled} options={effectiveOptions} onBlur={input.onBlur} value={getValue()} onChange={(e: any) => { if (multiple === true) { input.onChange(e?.map((x: any) => x.value)); } else { input.onChange(e?.value as any); } }} getOptionLabel={(option) => option?.label ?? ''} className={classNames( className, FormDefaults.cssClassPrefix + 'select-group' )} classNamePrefix="react-select" menuPortalTarget={document.body} menuPlacement="auto" placeholder={placeholder} /> ); function getValue() { if (multiple) { return effectiveOptions.filter( (o) => Array.isArray(input.value) && !!input.value.find((x) => o.value === x) ); } return effectiveOptions.find((o) => o.value === input.value); } }