import React, { useCallback, useEffect, useMemo } from 'react'; import classNames from 'classnames'; import ChevronDownIcon from '@moda/icons/chevron-down-12'; import ChevronUpIcon from '@moda/icons/chevron-up-12'; import { colors } from '@moda/tokens'; import { Clickable } from '../Clickable'; import { TextColor } from '../Text'; import { useFormControlContext } from '../Field/FormControlContext'; import { SelectOptions } from './SelectOptions'; import { SelectLabel } from './SelectLabel'; import { useSelect } from './useSelect'; import './Select.scss'; export type SelectExtraOption = { callback: (event?: React.MouseEvent) => void; disabled?: boolean; label: string; closeOnClick?: boolean; }; export type SelectableOption = { value: string; label: string; disabled?: boolean; colorSwatch?: TextColor; }; export type SelectProps = Omit< React.HTMLAttributes, 'defaultValue' | 'value' | 'onChange' > & { allowAutoFill?: boolean; autoSelect?: boolean; defaultValue?: string | undefined; disabled?: boolean; dropDirection?: 'down' | 'up'; error?: boolean | string; idRef?: string; label?: string; name?: string; onChange?: (value: string) => void; options: SelectableOption[]; placeholder?: string; searchable?: boolean; shiftIconLeftwards?: boolean; value?: string | undefined; dataTestId?: string; colorSwatch?: TextColor; extraOption?: SelectExtraOption; paddingRight?: number; smallMobileText?: boolean; }; export const Select: React.FC = ({ allowAutoFill = false, autoSelect = true, className, defaultValue, disabled, dropDirection = 'down', error, idRef, label = '', name, onChange, options, placeholder, searchable, shiftIconLeftwards = false, value, dataTestId, colorSwatch, extraOption, paddingRight, smallMobileText = false, ...rest }) => { const { displaysError: errorDisplayedByParent } = useFormControlContext(); const { state, dispatch, Mode, selectRef, buttonRef } = useSelect({ value, defaultValue }); const handleSelect = useCallback( (option: SelectableOption) => { dispatch({ type: 'SELECT', payload: { value: autoSelect ? option.value : (value ?? option.value) } }); onChange?.(option.value); }, [autoSelect, value, dispatch, onChange] ); const handleFocus = useCallback( (option: SelectableOption) => dispatch({ type: 'FOCUS', payload: { value: option.value } }), [dispatch] ); const handleToggle = useCallback( () => dispatch({ type: state.mode === Mode.Resting ? 'OPEN' : 'CLOSE' }), [Mode, dispatch, state] ); const handleAutofill = useCallback( (value: string) => { if (value) dispatch({ type: 'SELECT', payload: { value } }); }, [dispatch] ); const selected = useMemo( () => options.find(option => state.value === option.value), [options, state.value] ); const focused = useMemo( () => options.find(option => state.focused === option.value), [options, state.focused] ); useEffect(() => { if (disabled && state.mode === Mode.Open) { dispatch({ type: 'CLOSE' }); } }, [disabled, state.mode, Mode, dispatch]); return ( <>
{!allowAutoFill && name && ( )} {colorSwatch && (
)} {(focused ?? selected)?.label} {state.mode === Mode.Open ? ( {state.mode === Mode.Open && ( dispatch({ type: 'CLOSE' })} /> )}
{typeof error === 'string' && !errorDisplayedByParent && (
{error}
)} {allowAutoFill && ( handleAutofill(target.value)} value={value} aria-hidden='true' tabIndex={-1} /> )} ); };