autocomplete
unknown
typescript
4 years ago
9.3 kB
7
Indexable
import Colors from '@src/constants/Colors'; import Fonts from '@src/constants/Fonts'; import SharedStyles, { SCREEN_HEIGHT } from '@src/constants/SharedStyles'; import { delay, useKeyboard } from '@src/utils'; import { debounce } from 'lodash'; import React from 'react'; import { ActivityIndicator, Platform, StyleProp, StyleSheet, TextInput, TextInputProps, TextStyle, TouchableOpacity, View, ViewStyle, } from 'react-native'; import { Box } from './Box'; import { Option } from './DropDownButton'; import { ErrorView } from './ErrorView'; import { List } from './FlatList'; import { MyIcon } from './MyIcon'; import { Text } from './Text'; const LIST_ITEM_HEIGHT = 50; type ForwardedProps = { data: Option[]; onOptionSelected?: (option?: Option) => void; selectedOption?: Option; handleSearch?: (text: string) => Promise<Option[]>; // allow user to tap outside to dismiss tapToDismiss?: boolean; optionContainerStyle?: ViewStyle; optionTextStyle?: TextStyle; }; type DropDownListModalProps = { visible?: boolean; onRequestClose: () => void; width: number; height: number; pageX: number; pageY: number; maxHeight?: number; selectedOption?: Option; } & ForwardedProps; function DropDownListView(props: DropDownListModalProps) { const { visible, data, onRequestClose, onOptionSelected, tapToDismiss, height, width, pageX, pageY, selectedOption, maxHeight, } = props; // const [heightState, setHeightState] = React.useState(0); const [keyboardHeight] = useKeyboard(); // const onLayout = React.useCallback((event: LayoutChangeEvent) => setHeightState(event.nativeEvent.layout.height), []); const keyExtractor = React.useCallback((option: Option) => option.value + option.name, []); const listHeight = maxHeight || 200; // calculate position of view let y = pageY; if (SCREEN_HEIGHT - keyboardHeight - (pageY + height) < data.length * LIST_ITEM_HEIGHT) { console.log('go top'); y = -(LIST_ITEM_HEIGHT * data.length < listHeight ? LIST_ITEM_HEIGHT * data.length : listHeight); } else { console.log('go bottom'); y = height; } // if (pageY + height + heightState > screenHeight - PADDING) { // // go top // y = -heightState - OFFSET; // } else { // // go bottom // y = OFFSET; // } // const viewStyle: ViewStyle = { // position: 'absolute', // left: x, // ri // top: 0, // width, // // opacity: heightState ? 1 : 0, // }; const viewStyle: ViewStyle = { position: 'absolute', top: y, }; if (!visible) { return null; } return ( <View style={[optionStyles.list, { maxHeight: listHeight, position: 'absolute' }, viewStyle]}> <List keyboardShouldPersistTaps="handled" extraData={selectedOption} ItemSeparatorComponent={() => <Box style={optionStyles.separator} />} ListFooterComponent={null} keyExtractor={keyExtractor} data={data}> {(option) => { const selected = selectedOption && selectedOption.value === option.value; return ( <TouchableOpacity onPress={() => onOptionSelected && onOptionSelected(option)} style={[optionStyles.container]}> <Text numberOfLines={1} primary={selected}> {option.name} </Text> </TouchableOpacity> ); }} </List> </View> ); } const optionStyles = StyleSheet.create({ list: { ...SharedStyles.shadow1, backgroundColor: Colors.inputBackgroundColor, borderRadius: 4, width: '100%', }, container: { height: 50, justifyContent: 'center', // paddingHorizontal: CONTAINER_PADDING, paddingHorizontal: 10, // backgroundColor: 'blue', }, text: { color: Colors.textColor, }, separator: { // ...sharedStyles.container, height: 0.5, backgroundColor: Colors.borderColor, }, }); type AutocompleteInputProps = { error?: any; label?: string; style?: StyleProp<ViewStyle>; inputProps?: TextInputProps; } & ForwardedProps; type DropDownButtonState = { width: number; height: number; pageX: number; pageY: number; visible: boolean; }; export function AutoCompleteInput({ onOptionSelected, selectedOption, style, error, label, inputProps, handleSearch, ...rest }: AutocompleteInputProps) { const ref = React.useRef<TouchableOpacity>() as React.MutableRefObject<TouchableOpacity>; const inputRef = React.useRef<TextInput>() as React.MutableRefObject<TextInput>; const [isLoading, setIsLoading] = React.useState(false); const [data, setData] = React.useState<Option[]>([]); const [text, setText] = React.useState(''); const [state, setState] = React.useState<DropDownButtonState>({ width: 0, height: 0, pageX: 0, pageY: 0, visible: false, }); const _onOptionSelected = React.useCallback( (option?: Option) => { setState({ ...state, visible: false, }); // console.log(onOptionSelected); onOptionSelected && onOptionSelected(option); }, [state, onOptionSelected], ); const handlerChange = React.useCallback( debounce(async (t: string) => { try { if (handleSearch && t) { setIsLoading(true); const locas = await handleSearch(t); setIsLoading(false); setData(locas); ref.current.measure((_, __, width, height, pageX, pageY) => { console.log({ width, height, pageX, pageY }); setState({ ...state, width, height, pageX, pageY, visible: !!locas.length, }); }); } else { setState({ ...state, visible: false, }); } } catch (e) { setIsLoading(false); console.log(e); } }, 1000), [], ); const onPressClear = React.useCallback(async () => { setText(''); onOptionSelected && onOptionSelected(undefined); await delay(300); inputRef.current?.focus(); }, [inputRef, onOptionSelected]); const { width, height, pageX, pageY, visible } = state; const displayText = (selectedOption && selectedOption.name) || selectedOption || 'Select'; const textStyle = (selectedOption && selectedOption.name) || selectedOption ? undefined : styles.placeholderText; const customStyle = Platform.OS === 'ios' ? { zIndex: 1 } : undefined; return ( <Box style={[styles.container, customStyle]}> <View renderToHardwareTextureAndroid ref={ref}> {label && <Text style={styles.label}>{label}</Text>} <View style={[ styles.inputAutocompleteContainer, { borderColor: error ? Colors.errorColor : Colors.borderColor }, style, ]}> <Box full horizontal> {selectedOption ? ( <Text full style={[textStyle, { alignSelf: 'center' }]}> {displayText} </Text> ) : ( <TextInput value={text} ref={inputRef} style={styles.input} {...inputProps} onChangeText={(t: string) => { setText(t); handlerChange(t); }} /> )} <TouchableOpacity onPress={onPressClear} disabled={!selectedOption} style={styles.iconContainer}> {isLoading ? ( <ActivityIndicator animating /> ) : ( <MyIcon size={20} style={[styles.inputIconColor]} name={selectedOption ? 'times1' : 'chevron-down1'} /> )} </TouchableOpacity> </Box> </View> </View> <ErrorView error={error} /> <DropDownListView {...rest} visible={visible} width={width} onOptionSelected={_onOptionSelected} height={height} pageX={pageX} pageY={pageY} selectedOption={selectedOption} data={data} onRequestClose={() => setState({ ...state, visible: false })} /> </Box> ); } const styles = StyleSheet.create({ container: { marginBottom: 10, // zIndex: 1, // overflow: 'visible', // elevation: 2, }, inputAutocompleteContainer: { paddingLeft: 10, // backgroundColor: Colors.inputBackgroundColor, borderWidth: 1, borderColor: Colors.borderColor, height: 50, justifyContent: 'center', borderRadius: 5, // ...sharedStyles.shadow1, }, iconContainer: { width: 50, alignItems: 'center', justifyContent: 'center', }, inputIconColor: { color: Colors.inputIconColor, fontSize: 16, }, primary: { color: Colors.primary, }, label: { color: Colors.textColor, ...SharedStyles.inputLabelMarginBottom, }, errorContainer: { ...SharedStyles.errorContainer, }, placeholderText: { color: Colors.secondaryTextColor, }, input: { flex: 1, color: Colors.textColor, // marginLeft: 10, fontSize: 16, fontFamily: Fonts.regular, }, button: { justifyContent: 'center', alignItems: 'center', }, });
Editor is loading...