| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- import React, { useRef, useState, useCallback } from 'react';
- import {
- View,
- Text,
- TouchableOpacity,
- StyleSheet,
- StyleProp,
- ViewStyle,
- FlatList,
- TextInput,
- Dimensions,
- Platform
- } from 'react-native';
- import ActionSheet, { ActionSheetRef } from 'react-native-actions-sheet';
- import ChevronIcon from 'assets/icons/travels-screens/down-arrow.svg';
- import { Colors } from 'src/theme';
- import CloseSvg from 'assets/icons/close.svg';
- import CheckSvg from 'assets/icons/mark.svg';
- export interface SelectSheetProps<T extends Record<string, any>> {
- data: T[];
- labelField: keyof T;
- valueField: keyof T;
- value?: T[keyof T] | null;
- onChange: (item: T) => void;
- placeholder?: string;
- searchable?: boolean;
- searchPlaceholder?: string;
- disabled?: boolean;
- style?: StyleProp<ViewStyle>;
- renderItem?: (item: T, isSelected: boolean) => React.ReactNode;
- name?: string;
- hideTitle?: boolean;
- initialSnapPoint?: number;
- }
- export const SelectSheet = <T extends Record<string, any>>({
- data,
- labelField,
- valueField,
- value,
- onChange,
- placeholder = 'Select...',
- searchable = false,
- searchPlaceholder = 'Search...',
- disabled = false,
- style,
- renderItem,
- name,
- hideTitle = false,
- initialSnapPoint = 70
- }: SelectSheetProps<T>) => {
- const sheetRef = useRef<ActionSheetRef>(null);
- const [query, setQuery] = useState('');
- const selectedItem = value != null ? data.find((item) => item[valueField] === value) : null;
- const filteredData =
- searchable && query.trim()
- ? data.filter((item) => String(item[labelField]).toLowerCase().includes(query.toLowerCase()))
- : data;
- const handleSelect = useCallback(
- (item: T) => {
- sheetRef.current?.hide();
- onChange(item);
- },
- [onChange]
- );
- const handleOpen = useCallback(() => {
- if (disabled) return;
- setQuery('');
- sheetRef.current?.show();
- }, [disabled]);
- return (
- <>
- <TouchableOpacity
- onPress={handleOpen}
- activeOpacity={disabled ? 1 : 0.7}
- style={[styles.trigger, disabled && styles.triggerDisabled, style]}
- >
- <Text
- style={[styles.triggerText, !selectedItem && styles.placeholderText]}
- numberOfLines={1}
- >
- {selectedItem ? String(selectedItem[labelField]) : placeholder}
- </Text>
- <ChevronIcon width={12} height={12} fill={Colors.TEXT_GRAY} style={styles.chevron} />
- </TouchableOpacity>
- <ActionSheet
- ref={sheetRef}
- gestureEnabled={Platform.OS !== 'android'}
- closeOnTouchBackdrop
- snapPoints={[initialSnapPoint, 100]}
- initialSnapIndex={0}
- defaultOverlayOpacity={0.4}
- containerStyle={styles.sheetContainer}
- onClose={() => setQuery('')}
- >
- <View style={styles.sheetContent}>
- <TouchableOpacity
- onPress={() => sheetRef.current?.hide()}
- style={[styles.closeBtnAlone, Platform.OS === 'ios' ? { paddingTop: 6 } : {}]}
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
- >
- <CloseSvg width={15} height={15} />
- </TouchableOpacity>
- {!hideTitle && (
- <View style={styles.titleRow}>
- <Text style={[styles.sheetTitle, Platform.OS === 'android' ? { paddingTop: 0 } : {}]}>
- {placeholder}
- </Text>
- </View>
- )}
- {searchable && (
- <View style={styles.searchWrapper}>
- <TextInput
- style={styles.searchInput}
- placeholder={searchPlaceholder}
- placeholderTextColor={Colors.LIGHT_GRAY}
- value={query}
- onChangeText={setQuery}
- autoCorrect={false}
- clearButtonMode="while-editing"
- />
- </View>
- )}
- <FlatList
- data={filteredData}
- keyExtractor={(item: T, i: number) => String(item[valueField] ?? i)}
- keyboardShouldPersistTaps="handled"
- style={styles.list}
- contentContainerStyle={styles.listContent}
- initialNumToRender={20}
- renderItem={({ item }: { item: T }) => {
- const isSelected = item[valueField] === value;
- if (renderItem) {
- return (
- <TouchableOpacity onPress={() => handleSelect(item)} activeOpacity={0.7}>
- {renderItem(item, isSelected)}
- </TouchableOpacity>
- );
- }
- return (
- <TouchableOpacity
- style={styles.option}
- onPress={() => handleSelect(item)}
- activeOpacity={0.7}
- >
- <Text
- style={[styles.optionText, isSelected && styles.optionTextSelectedWeight]}
- numberOfLines={1}
- >
- {String(item[labelField])}
- </Text>
- {isSelected && (
- <View style={styles.checkmark}>
- <CheckSvg fill={Colors.DARK_BLUE} height={12} width={15} />
- </View>
- )}
- </TouchableOpacity>
- );
- }}
- />
- </View>
- </ActionSheet>
- </>
- );
- };
- const styles = StyleSheet.create({
- trigger: {
- height: 40,
- backgroundColor: Colors.FILL_LIGHT,
- borderRadius: 8,
- paddingHorizontal: 12,
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between'
- },
- triggerDisabled: {
- opacity: 0.5
- },
- triggerText: {
- flex: 1,
- fontSize: 15,
- color: Colors.DARK_BLUE,
- marginRight: 6
- },
- placeholderText: {
- color: Colors.TEXT_GRAY
- },
- sheetContent: {
- height: Dimensions.get('window').height * 0.9,
- backgroundColor: Colors.WHITE,
- borderTopLeftRadius: 20,
- borderTopRightRadius: 20,
- overflow: 'hidden'
- },
- sheetContainer: {
- height: '90%',
- backgroundColor: Colors.WHITE,
- borderTopLeftRadius: 20,
- borderTopRightRadius: 20
- },
- indicator: {
- width: 44,
- height: 5,
- borderRadius: 3,
- backgroundColor: Colors.LIGHT_GRAY,
- marginTop: 8
- },
- searchWrapper: {
- paddingHorizontal: 16,
- paddingVertical: 12
- },
- searchInput: {
- height: 44,
- backgroundColor: Colors.FILL_LIGHT,
- borderRadius: 6,
- paddingHorizontal: 16,
- fontSize: 16,
- color: Colors.DARK_BLUE
- },
- list: {
- flex: 1
- },
- listContent: {
- paddingBottom: 40,
- paddingTop: 8
- },
- option: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingHorizontal: 24,
- paddingVertical: 16
- },
- optionText: {
- flex: 1,
- fontSize: 16,
- color: Colors.DARK_BLUE
- },
- optionTextSelectedWeight: {
- fontWeight: '700'
- },
- checkmark: {
- marginLeft: 12
- },
- checkmarkText: {
- fontSize: 18,
- color: Colors.DARK_BLUE,
- fontWeight: '700'
- },
- chevron: {
- marginLeft: 2
- },
- titleRow: {
- flexDirection: 'row',
- alignItems: 'center',
- borderBottomWidth: StyleSheet.hairlineWidth,
- borderBottomColor: Colors.DARK_LIGHT
- },
- sheetTitle: {
- flex: 1,
- fontSize: 16,
- fontWeight: '700',
- color: Colors.DARK_BLUE,
- paddingVertical: 12,
- paddingHorizontal: 24,
- textAlign: 'center'
- },
- closeBtn: {
- paddingHorizontal: 16,
- paddingVertical: 12
- },
- closeBtnAlone: {
- alignSelf: 'flex-end',
- paddingHorizontal: 16,
- paddingTop: 16
- }
- });
|