import React, { useMemo } from 'react'; import { View, Text, ScrollView, StyleSheet, StyleProp, ViewStyle, TouchableOpacity, useWindowDimensions, } from 'react-native'; import Animated, { useAnimatedStyle, withTiming, useDerivedValue, } from 'react-native-reanimated'; import { Colors, Spacing, FontSize, Animation } from '@/constants/theme'; interface Category { id: string; name: string; } interface CategoryTabsProps { categories?: Category[]; activeId?: string; onChange?: (category: Category) => void; style?: StyleProp; } const DEFAULT_CATEGORIES: Category[] = [ { id: 'all', name: '全部' }, { id: 'finance', name: '财经' }, { id: 'entertainment', name: '娱乐' }, { id: 'education', name: '教育' }, { id: 'technology', name: '科技' }, { id: 'sports', name: '体育' }, { id: 'lifestyle', name: '生活' }, { id: 'travel', name: '旅行' }, ]; const CategoryTabs: React.FC = ({ categories = DEFAULT_CATEGORIES, activeId = 'all', onChange, style, }) => { const { width: screenWidth } = useWindowDimensions(); const activeIndex = useMemo(() => { return categories.findIndex(cat => cat.id === activeId); }, [categories, activeId]); const indicatorPosition = useDerivedValue(() => { if (categories.length === 0) return 0; const activeIndexSafe = activeIndex >= 0 ? activeIndex : 0; const padding = 16; const gap = 8; let offset = padding; for (let i = 0; i < activeIndexSafe; i++) { const item = categories[i]; const itemWidth = Math.min( Math.max(44, item.name.length * 16 + 32), screenWidth / 3 ); offset += itemWidth + gap; } return offset; }, [activeIndex, categories, screenWidth]); const activeCategory = categories[activeIndex] || categories[0]; const indicatorStyle = useAnimatedStyle(() => { if (!activeCategory) { return { width: 0, left: 0 }; } const width = Math.min( Math.max(44, activeCategory.name.length * 16 + 32), screenWidth / 3 ); return { width: withTiming(width, { duration: Animation.duration.normal }), left: withTiming(indicatorPosition.value, { duration: Animation.duration.normal }), }; }); const renderCategory = (category: Category, index: number) => { const isActive = category.id === activeId; return ( onChange?.(category)} activeOpacity={0.7} > {category.name} ); }; return ( {categories.map(renderCategory)} ); }; const styles = StyleSheet.create({ container: { backgroundColor: Colors.background.secondary, width: '100%', }, scrollContent: { paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, gap: Spacing.sm, }, tabItem: { minWidth: 44, paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, height: 40, justifyContent: 'center', alignItems: 'center', }, tabText: { fontSize: FontSize.sm, color: Colors.text.secondary, fontWeight: '500', }, activeTabText: { color: Colors.brand.primary, fontWeight: '600', }, indicator: { position: 'absolute', bottom: 0, height: 2, backgroundColor: Colors.brand.primary, borderRadius: 1, }, }); export default React.memo(CategoryTabs);