autocomplete
unknown
typescript
5 years ago
9.3 kB
12
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...