Files
frontend/src/components/business/VoteEditor.tsx

204 lines
5.3 KiB
TypeScript
Raw Normal View History

/**
* VoteEditor
*
*/
import React, { useCallback } from 'react';
import {
View,
TouchableOpacity,
StyleSheet,
TextInput,
} from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { colors, spacing, fontSizes, borderRadius } from '../../theme';
import Text from '../common/Text';
interface VoteEditorProps {
options: string[];
onAddOption: () => void;
onRemoveOption: (index: number) => void;
onUpdateOption: (index: number, value: string) => void;
maxOptions?: number;
minOptions?: number;
maxLength?: number;
}
const VoteEditor: React.FC<VoteEditorProps> = ({
options,
onAddOption,
onRemoveOption,
onUpdateOption,
maxOptions = 10,
minOptions = 2,
maxLength = 50,
}) => {
const validOptionsCount = options.filter(opt => opt.trim() !== '').length;
const canAddOption = options.length < maxOptions;
const canRemoveOption = options.length > minOptions;
return (
<View style={styles.container}>
{/* 标题栏 */}
<View style={styles.header}>
<View style={styles.headerLeft}>
<MaterialCommunityIcons
name="vote"
size={18}
color={colors.primary.main}
/>
<Text variant="body" style={styles.headerTitle}>
</Text>
</View>
<Text variant="caption" color={colors.text.hint}>
{validOptionsCount}/{maxOptions}
</Text>
</View>
{/* 选项列表 */}
<View style={styles.optionsContainer}>
{options.map((option, index) => (
<View key={index} style={styles.optionRow}>
<View style={styles.optionIndex}>
<Text variant="caption" color={colors.text.hint}>
{index + 1}
</Text>
</View>
<TextInput
style={styles.optionInput}
value={option}
onChangeText={(text) => onUpdateOption(index, text)}
placeholder={`输入选项 ${index + 1}`}
placeholderTextColor={colors.text.hint}
maxLength={maxLength}
returnKeyType="done"
/>
{canRemoveOption && (
<TouchableOpacity
style={styles.removeButton}
onPress={() => onRemoveOption(index)}
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
>
<MaterialCommunityIcons
name="close-circle"
size={20}
color={colors.text.hint}
/>
</TouchableOpacity>
)}
{!canRemoveOption && options.length <= minOptions && (
<View style={styles.removeButtonPlaceholder} />
)}
</View>
))}
{/* 添加选项按钮 */}
{canAddOption && (
<TouchableOpacity
style={styles.addOptionButton}
onPress={onAddOption}
activeOpacity={0.7}
>
<MaterialCommunityIcons
name="plus-circle-outline"
size={20}
color={colors.primary.main}
/>
<Text variant="body" color={colors.primary.main} style={styles.addOptionText}>
</Text>
</TouchableOpacity>
)}
</View>
{/* 提示信息 */}
<View style={styles.hintContainer}>
<Text variant="caption" color={colors.text.hint}>
{minOptions}
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: colors.background.default,
borderRadius: borderRadius.lg,
marginHorizontal: spacing.lg,
marginTop: spacing.md,
marginBottom: spacing.md,
padding: spacing.md,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: spacing.md,
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
},
headerTitle: {
fontWeight: '600',
color: colors.text.primary,
},
optionsContainer: {
gap: spacing.sm,
},
optionRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
},
optionIndex: {
width: 20,
height: 20,
borderRadius: borderRadius.full,
backgroundColor: colors.background.disabled,
justifyContent: 'center',
alignItems: 'center',
},
optionInput: {
flex: 1,
fontSize: fontSizes.md,
color: colors.text.primary,
backgroundColor: colors.background.paper,
borderRadius: borderRadius.md,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderWidth: 1,
borderColor: colors.divider,
height: 44,
},
removeButton: {
padding: spacing.xs,
},
removeButtonPlaceholder: {
width: 28,
},
addOptionButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: spacing.sm,
paddingVertical: spacing.sm,
marginTop: spacing.xs,
},
addOptionText: {
fontWeight: '500',
},
hintContainer: {
marginTop: spacing.md,
paddingTop: spacing.sm,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.divider,
alignItems: 'center',
},
});
export default VoteEditor;