fitst commit
This commit is contained in:
164
components/ui/Combobox.tsx
Normal file
164
components/ui/Combobox.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user