import React, { useCallback, useMemo, useRef, useState } from 'react'; import type { TextInputProps as NativeTextInputProps, SectionList, } from 'react-native'; import { View } from 'react-native'; import { deepCompareValue, useKeyboard } from '../../../utils/helpers'; import BottomSheet from '../../BottomSheet'; import type { TextInputProps } from '../../TextInput'; import TextInput from '../../TextInput'; import { getScrollParams, toFlatOptions, toSections } from '../helpers'; import { StyledSearchBar, StyledTouchableOpacity } from '../StyledSelect'; import type { SectionType, SelectOptionType, SelectProps } from '../types'; import OptionList from './OptionList'; export interface SingleSelectProps< V, T extends SelectOptionType = SelectOptionType > extends SelectProps { /** * Current selected value. */ value: V | null; /** * On select event handler */ onConfirm: (value: V | null) => void; /** * Customise the selected value rendering. */ renderSelectedValue?: ( selectedValue: V | null, inputProps: NativeTextInputProps ) => React.ReactNode; /** * Supported orientations for the Select modal, iOS only. */ supportedOrientations?: ('portrait' | 'landscape')[]; } // Add an internal prop type for TextInputComponent, not exported export interface InternalSingleSelectProps< V, T extends SelectOptionType = SelectOptionType > extends SingleSelectProps { TextInputComponent?: React.ComponentType; groupStyleEnabled?: boolean; } const SingleSelect = >({ label, loading = false, inputProps, onConfirm, onDismiss, onEndReached, onQueryChange, options, renderOption, renderSelectedValue, query, error, editable = true, disabled = false, required, style, testID, value, supportedOrientations = ['portrait'], bottomSheetConfig = {}, groupStyleEnabled = false, ...rest }: InternalSingleSelectProps) => { const { isKeyboardVisible, keyboardHeight } = useKeyboard(); const [open, setOpen] = useState(false); const sectionListRef = useRef>(null); const sections = toSections(options); const flatOptions = toFlatOptions(options); const displayedValue = flatOptions.find((opt) => deepCompareValue(opt.value, value) )?.text; const rawValue = value ? String(value) : undefined; const { variant: bottomSheetVariant, header: bottomSheetHeader } = bottomSheetConfig; const TextInputComponent = rest.TextInputComponent || TextInput; const onPress = useCallback(() => { setOpen(true); }, []); const selectSuffix: TextInputProps['suffix'] = useMemo(() => { if (editable && !disabled) { return open ? 'arrow-up' : 'arrow-down'; } return undefined; }, [editable, disabled, open]); return ( <> { // prevent users from focusing TextInput } renderSelectedValue(value, props) : undefined } /> { onDismiss?.(); setOpen(false); }} header={bottomSheetHeader || label} style={{ paddingBottom: isKeyboardVisible ? keyboardHeight : 0, }} onAnimationEnd={() => { if (open === true) { const scrollParams = getScrollParams(value, sections); sectionListRef.current?.scrollToLocation(scrollParams); } }} supportedOrientations={supportedOrientations} > {onQueryChange && ( )} { setOpen(false); onConfirm(selectedValue); }} sectionListRef={sectionListRef} onScrollToIndexFailed={(info) => { setTimeout(() => { const scrollParams = getScrollParams(value, sections); if (scrollParams.itemIndex < info.highestMeasuredFrameIndex) { sectionListRef.current?.scrollToLocation(scrollParams); } }, 500); }} /> ); }; export default SingleSelect;