Initial frontend repository commit.
Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
This commit is contained in:
203
src/components/business/VoteEditor.tsx
Normal file
203
src/components/business/VoteEditor.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user