bw-expo-app/components/forms/form-fields/select-input.tsx

164 lines
4.1 KiB
TypeScript

import { View, StyleSheet, TouchableOpacity } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { useThemeColor } from '@/hooks/use-theme-color';
import { FormFieldSchema } from '@/lib/types/template-run';
import { useState } from 'react';
interface SelectInputFieldProps {
field: FormFieldSchema;
value: string | number;
onChange: (value: string | number) => void;
error?: string;
}
export function SelectInputField({ field, value, onChange, error }: SelectInputFieldProps) {
const textColor = useThemeColor({}, 'text');
const borderColor = useThemeColor({}, 'border');
const errorColor = useThemeColor({}, 'error');
const backgroundColor = useThemeColor({}, 'background');
const [isOpen, setIsOpen] = useState(false);
const selectedOption = field.options?.find(option => option.value === value);
const handleSelect = (option: any) => {
onChange(option.value);
setIsOpen(false);
};
return (
<View style={styles.container}>
{field.label && (
<ThemedText style={styles.label}>
{field.label}
{field.required && <ThemedText style={[styles.required, { color: errorColor }]}> *</ThemedText>}
</ThemedText>
)}
<TouchableOpacity
style={[
styles.selectBox,
{
borderColor: error ? errorColor : borderColor,
backgroundColor,
}
]}
onPress={() => setIsOpen(!isOpen)}
activeOpacity={0.8}
>
<ThemedText style={[styles.selectedText, { color: selectedOption ? textColor : '#999' }]}>
{selectedOption?.label || field.placeholder || '请选择...'}
</ThemedText>
<ThemedText style={[styles.arrow, { color: textColor }]}>
{isOpen ? '▲' : '▼'}
</ThemedText>
</TouchableOpacity>
{isOpen && field.options && (
<View style={[styles.dropdown, { backgroundColor, borderColor }]}>
{field.options.map((option, index) => (
<TouchableOpacity
key={index}
style={[
styles.option,
{
backgroundColor: option.value === value ? borderColor : 'transparent',
borderBottomColor: borderColor,
}
]}
onPress={() => handleSelect(option)}
activeOpacity={0.7}
>
<ThemedText style={[
styles.optionText,
{
color: option.value === value ? '#fff' : textColor,
fontWeight: option.value === value ? '600' : '400',
}
]}>
{option.label}
</ThemedText>
</TouchableOpacity>
))}
</View>
)}
{field.description && (
<ThemedText style={styles.description}>{field.description}</ThemedText>
)}
{error && (
<ThemedText style={[styles.errorText, { color: errorColor }]}>
{error}
</ThemedText>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
marginBottom: 16,
position: 'relative',
zIndex: 10,
},
label: {
fontSize: 16,
fontWeight: '600',
marginBottom: 8,
},
required: {
fontSize: 16,
fontWeight: '600',
},
selectBox: {
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
minHeight: 44,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
selectedText: {
fontSize: 16,
flex: 1,
},
arrow: {
fontSize: 12,
marginLeft: 8,
},
dropdown: {
borderWidth: 1,
borderRadius: 8,
marginTop: 4,
maxHeight: 200,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
position: 'absolute',
top: '100%',
left: 0,
right: 0,
zIndex: 20,
},
option: {
paddingHorizontal: 12,
paddingVertical: 10,
borderBottomWidth: 1,
},
optionText: {
fontSize: 16,
},
description: {
fontSize: 12,
opacity: 0.7,
marginTop: 4,
},
errorText: {
fontSize: 12,
marginTop: 4,
},
});