165 lines
4.3 KiB
TypeScript
165 lines
4.3 KiB
TypeScript
import { Check, ChevronDown } from 'lucide-react-native';
|
|
import React, { useState } from 'react';
|
|
import { FlatList, Modal, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
|
|
|
interface ComboboxProps {
|
|
value: string;
|
|
onChange: (value: string, label: string) => void;
|
|
items: { label: string; value: string }[];
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export default function Combobox({ value, onChange, items, placeholder, disabled }: ComboboxProps) {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
|
|
const selectedItem = items.find((item) => item.value === value);
|
|
const displayText = selectedItem ? selectedItem.label : placeholder || 'Select...';
|
|
|
|
const filteredItems = items.filter((item) =>
|
|
item.label.toLowerCase().includes(searchQuery.toLowerCase())
|
|
);
|
|
|
|
const handleSelect = (item: { label: string; value: string }) => {
|
|
onChange(item.value, item.label);
|
|
setIsOpen(false);
|
|
setSearchQuery('');
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<TouchableOpacity
|
|
style={[styles.trigger, disabled && styles.triggerDisabled]}
|
|
onPress={() => !disabled && setIsOpen(true)}
|
|
disabled={disabled}
|
|
>
|
|
<Text style={[styles.triggerText, disabled && styles.triggerTextDisabled]}>
|
|
{displayText}
|
|
</Text>
|
|
<ChevronDown size={16} color={disabled ? '#9ca3af' : '#6b7280'} />
|
|
</TouchableOpacity>
|
|
|
|
<Modal
|
|
visible={isOpen}
|
|
transparent
|
|
animationType="slide"
|
|
onRequestClose={() => setIsOpen(false)}
|
|
>
|
|
<View style={styles.modalOverlay}>
|
|
<View style={styles.modalContent}>
|
|
<View style={styles.modalHeader}>
|
|
<Text style={styles.modalTitle}>Select Option</Text>
|
|
<TouchableOpacity onPress={() => setIsOpen(false)}>
|
|
<Text style={styles.closeButton}>Done</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<TextInput
|
|
style={styles.searchInput}
|
|
placeholder="Search..."
|
|
value={searchQuery}
|
|
onChangeText={setSearchQuery}
|
|
autoCapitalize="none"
|
|
/>
|
|
|
|
<FlatList
|
|
data={filteredItems}
|
|
keyExtractor={(item) => item.value}
|
|
renderItem={({ item }) => (
|
|
<TouchableOpacity style={styles.option} onPress={() => handleSelect(item)}>
|
|
<Text style={styles.optionText}>{item.label}</Text>
|
|
{value === item.value && <Check size={18} color="#2563eb" />}
|
|
</TouchableOpacity>
|
|
)}
|
|
style={styles.optionsList}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
</>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
trigger: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
backgroundColor: '#fff',
|
|
borderWidth: 1,
|
|
borderColor: '#d1d5db',
|
|
borderRadius: 8,
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 12,
|
|
},
|
|
triggerDisabled: {
|
|
backgroundColor: '#f9fafb',
|
|
borderColor: '#e5e7eb',
|
|
},
|
|
triggerText: {
|
|
fontSize: 14,
|
|
color: '#374151',
|
|
flex: 1,
|
|
},
|
|
triggerTextDisabled: {
|
|
color: '#9ca3af',
|
|
},
|
|
modalOverlay: {
|
|
flex: 1,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
justifyContent: 'flex-end',
|
|
},
|
|
modalContent: {
|
|
backgroundColor: '#fff',
|
|
borderTopLeftRadius: 20,
|
|
borderTopRightRadius: 20,
|
|
maxHeight: '80%',
|
|
paddingBottom: 20,
|
|
},
|
|
modalHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: 16,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#e5e7eb',
|
|
},
|
|
modalTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '600',
|
|
color: '#111827',
|
|
},
|
|
closeButton: {
|
|
fontSize: 16,
|
|
color: '#2563eb',
|
|
fontWeight: '600',
|
|
},
|
|
searchInput: {
|
|
margin: 16,
|
|
padding: 12,
|
|
backgroundColor: '#f9fafb',
|
|
borderRadius: 8,
|
|
fontSize: 14,
|
|
borderWidth: 1,
|
|
borderColor: '#e5e7eb',
|
|
},
|
|
optionsList: {
|
|
flex: 1,
|
|
},
|
|
option: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 14,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#f3f4f6',
|
|
},
|
|
optionText: {
|
|
fontSize: 15,
|
|
color: '#374151',
|
|
flex: 1,
|
|
},
|
|
});
|