134 lines
3.8 KiB
TypeScript
134 lines
3.8 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
Modal,
|
|
FlatList,
|
|
TextInput,
|
|
SafeAreaView,
|
|
Platform,
|
|
StatusBar,
|
|
} from 'react-native';
|
|
import Icon from 'react-native-vector-icons/Ionicons';
|
|
import { useTheme } from '@theme';
|
|
import { getStyles } from './formPicker.styles';
|
|
import { FormPickerProps, FormPickerOption } from './formPicker.props';
|
|
|
|
export const FormPicker: React.FC<FormPickerProps> = ({
|
|
label,
|
|
value,
|
|
onValueChange,
|
|
options,
|
|
required = false,
|
|
placeholder = 'Select...',
|
|
searchable = false,
|
|
}) => {
|
|
const { theme: colors } = useTheme();
|
|
const styles = getStyles(colors);
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
const [query, setQuery] = useState('');
|
|
|
|
const selectedOption = options.find(opt => opt.value === value);
|
|
|
|
const filtered = searchable
|
|
? options.filter(opt =>
|
|
opt.label.toLowerCase().includes(query.toLowerCase()),
|
|
)
|
|
: options;
|
|
|
|
const handleSelect = (opt: FormPickerOption) => {
|
|
onValueChange(opt.value);
|
|
setModalVisible(false);
|
|
setQuery('');
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Text style={styles.label}>
|
|
{label}
|
|
{required && <Text style={styles.required}> *</Text>}
|
|
</Text>
|
|
|
|
<TouchableOpacity
|
|
style={styles.pickerContainer}
|
|
activeOpacity={0.7}
|
|
onPress={() => setModalVisible(true)}>
|
|
<Text
|
|
style={[styles.pickerText, !selectedOption && styles.placeholder]}>
|
|
{selectedOption?.label || placeholder}
|
|
</Text>
|
|
<Icon name="chevron-down" size={18} color={colors.textSecondary} />
|
|
</TouchableOpacity>
|
|
|
|
<Modal
|
|
visible={modalVisible}
|
|
animationType="slide"
|
|
transparent
|
|
onRequestClose={() => setModalVisible(false)}>
|
|
<TouchableOpacity
|
|
style={styles.backdrop}
|
|
activeOpacity={1}
|
|
onPress={() => setModalVisible(false)}
|
|
/>
|
|
<SafeAreaView style={styles.sheet}>
|
|
{/* Header */}
|
|
<View style={styles.sheetHeader}>
|
|
<Text style={styles.sheetTitle}>{label}</Text>
|
|
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
|
<Icon name="close" size={22} color={colors.text} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Search */}
|
|
{searchable && (
|
|
<View style={styles.searchWrapper}>
|
|
<Icon
|
|
name="search-outline"
|
|
size={16}
|
|
color={colors.textSecondary}
|
|
style={styles.searchIcon}
|
|
/>
|
|
<TextInput
|
|
style={styles.searchInput}
|
|
placeholder="Search..."
|
|
placeholderTextColor={colors.textMuted}
|
|
value={query}
|
|
onChangeText={setQuery}
|
|
autoFocus
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
{/* Options */}
|
|
<FlatList
|
|
data={filtered}
|
|
keyExtractor={item => item.value}
|
|
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
|
renderItem={({ item }) => {
|
|
const isSelected = item.value === value;
|
|
return (
|
|
<TouchableOpacity
|
|
style={styles.optionRow}
|
|
activeOpacity={0.6}
|
|
onPress={() => handleSelect(item)}>
|
|
<Text
|
|
style={[
|
|
styles.optionText,
|
|
isSelected && styles.optionTextSelected,
|
|
]}>
|
|
{item.label}
|
|
</Text>
|
|
{isSelected && (
|
|
<Icon name="checkmark" size={18} color={colors.icon} />
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
}}
|
|
/>
|
|
</SafeAreaView>
|
|
</Modal>
|
|
</View>
|
|
);
|
|
};
|