Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
204 lines
5.3 KiB
TypeScript
204 lines
5.3 KiB
TypeScript
/**
|
|
* 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;
|