feat: add expo mobile application source code
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import { useAppTheme } from '../context/ThemeContext';
|
||||
import { Feather } from '@expo/vector-icons';
|
||||
import { Popup } from './Popup';
|
||||
|
||||
interface DropdownProps {
|
||||
label?: string;
|
||||
value: string;
|
||||
options: string[];
|
||||
onSelect: (val: string) => void;
|
||||
required?: boolean;
|
||||
infoText?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const LIME = '#C6F135';
|
||||
|
||||
export const Dropdown: React.FC<DropdownProps> = ({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
onSelect,
|
||||
required = false,
|
||||
infoText,
|
||||
placeholder = 'Choose an option...'
|
||||
}) => {
|
||||
const { colors, isDark } = useAppTheme();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const bg = isDark ? '#1A1A1A' : '#F5F5F5';
|
||||
const border = isDark ? '#2A2A2A' : '#EEEEEE';
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{label && (
|
||||
<View style={styles.labelRow}>
|
||||
<Text style={[styles.label, { color: colors.textSecondary }]}>
|
||||
{label}
|
||||
{required ? <Text style={{ color: '#EF4444' }}> *</Text> : <Text style={{ color: isDark ? '#666' : '#CCC', fontSize: 10 }}> (Optional)</Text>}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.inputBox, { backgroundColor: bg, borderColor: border }]}
|
||||
onPress={() => setIsOpen(true)}
|
||||
>
|
||||
<Text style={[styles.inputText, { color: value ? colors.text : (isDark ? '#444' : '#BBB') }]}>
|
||||
{value || placeholder}
|
||||
</Text>
|
||||
<Feather name="chevron-down" size={18} color={isDark ? '#555' : '#AAA'} />
|
||||
</TouchableOpacity>
|
||||
|
||||
{infoText && <Text style={[styles.infoText, { color: isDark ? '#6B6B6B' : '#9B9B9B' }]}>{infoText}</Text>}
|
||||
|
||||
<Popup visible={isOpen} onClose={() => setIsOpen(false)} title={`${label || 'Option'}`}>
|
||||
<View style={styles.listContainer}>
|
||||
{options.map((opt, idx) => (
|
||||
<TouchableOpacity
|
||||
key={idx}
|
||||
style={[styles.optionItem, { borderBottomColor: border, borderBottomWidth: idx < options.length - 1 ? 1 : 0 }]}
|
||||
onPress={() => {
|
||||
onSelect(opt);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.optionText, { color: colors.text, fontFamily: value === opt ? 'Outfit_700Bold' : 'Outfit_400Regular' }]}>
|
||||
{opt}
|
||||
</Text>
|
||||
{value === opt && <Feather name="check-circle" size={18} color={LIME} />}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</Popup>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: 18,
|
||||
width: '100%',
|
||||
},
|
||||
labelRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-end',
|
||||
marginBottom: 8,
|
||||
},
|
||||
label: {
|
||||
fontSize: 11,
|
||||
fontFamily: 'Outfit_700Bold',
|
||||
letterSpacing: 1,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
inputBox: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 16,
|
||||
borderWidth: 1.5,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
inputText: {
|
||||
fontSize: 15,
|
||||
fontFamily: 'Outfit_500Medium',
|
||||
flex: 1,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 12,
|
||||
marginTop: 6,
|
||||
marginLeft: 4,
|
||||
fontFamily: 'Outfit_400Regular',
|
||||
},
|
||||
listContainer: {
|
||||
paddingTop: 8,
|
||||
},
|
||||
optionItem: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 18,
|
||||
},
|
||||
optionText: {
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user