Files
info-tager-mobile/components/ui/Combobox.tsx
Samandar Turgunboyev 124798419b fitst commit
2026-01-28 18:26:50 +05:00

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,
},
});