/** * TabBar 标签栏组件 - 美化版 * 用于切换不同标签页,支持多种样式变体 * 新增胶囊式、分段式等现代设计风格 */ import React, { ReactNode, useMemo } from 'react'; import { View, TouchableOpacity, StyleSheet, ScrollView, ViewStyle, StyleProp } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme'; import Text from '../common/Text'; type TabBarVariant = 'default' | 'pill' | 'segmented' | 'modern' | 'home'; interface TabBarProps { tabs: string[]; activeIndex: number; onTabChange: (index: number) => void; scrollable?: boolean; rightContent?: ReactNode; variant?: TabBarVariant; icons?: string[]; /** home 变体的字号,默认 18(HomeScreen 可传 20) */ fontSize?: number; style?: StyleProp; } function createTabBarStyles(colors: AppColors) { return StyleSheet.create({ container: { flexDirection: 'row', backgroundColor: colors.background.paper, borderBottomWidth: 1, borderBottomColor: colors.divider, alignItems: 'center', paddingRight: spacing.xs, }, scrollableContainer: { flexDirection: 'row', paddingHorizontal: spacing.md, backgroundColor: colors.background.paper, borderBottomWidth: 1, borderBottomColor: colors.divider, flex: 1, }, tab: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: spacing.md, position: 'relative', }, activeTab: {}, tabText: { fontWeight: '500', }, activeTabText: { fontWeight: '600', }, activeIndicator: { position: 'absolute', bottom: 0, left: '25%', right: '25%', height: 3, backgroundColor: colors.primary.main, borderTopLeftRadius: borderRadius.sm, borderTopRightRadius: borderRadius.sm, }, rightContent: { paddingLeft: spacing.sm, }, pillContainer: { flexDirection: 'row', backgroundColor: colors.background.default, padding: spacing.sm, gap: spacing.sm, }, pillTab: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: spacing.sm, paddingHorizontal: spacing.md, borderRadius: borderRadius.lg, backgroundColor: colors.background.paper, }, pillTabActive: { backgroundColor: colors.primary.main, }, pillTabText: { fontWeight: '600', }, segmentedContainer: { flexDirection: 'row', backgroundColor: colors.background.default, padding: spacing.xs, marginHorizontal: spacing.md, marginVertical: spacing.sm, borderRadius: borderRadius.lg, }, segmentedTab: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: spacing.sm, backgroundColor: 'transparent', }, segmentedTabActive: { backgroundColor: colors.background.paper, borderRadius: borderRadius.md, shadowColor: colors.chat.shadow, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 2, }, segmentedTabFirst: { borderTopLeftRadius: borderRadius.md, borderBottomLeftRadius: borderRadius.md, }, segmentedTabLast: { borderTopRightRadius: borderRadius.md, borderBottomRightRadius: borderRadius.md, }, segmentedTabText: { fontWeight: '600', }, segmentedTabContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, segmentedTabIcon: { marginRight: spacing.xs, }, modernContainer: { flexDirection: 'row', backgroundColor: colors.background.paper, borderBottomWidth: 1, borderBottomColor: colors.divider, alignItems: 'center', paddingRight: spacing.xs, }, modernTab: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: spacing.md, position: 'relative', backgroundColor: 'transparent', }, modernTabActive: { backgroundColor: 'transparent', }, modernTabContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, modernTabIcon: { marginRight: spacing.xs, }, modernTabText: { fontWeight: '500', fontSize: fontSizes.md, }, modernTabTextActive: { fontWeight: '600', }, modernTabIndicator: { position: 'absolute', bottom: 0, left: '25%', right: '25%', height: 3, backgroundColor: colors.primary.main, borderTopLeftRadius: borderRadius.sm, borderTopRightRadius: borderRadius.sm, }, // home 变体:与 HomeScreen/SearchScreen 一致的文字下划线风格 homeContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'transparent', }, homeTab: { alignItems: 'center', justifyContent: 'center', paddingVertical: spacing.sm, marginRight: 20, borderBottomWidth: 2, borderBottomColor: 'transparent', }, homeTabActive: { borderBottomColor: colors.text.primary, }, homeTabText: { fontSize: fontSizes.xl, fontWeight: '400', color: colors.text.hint, }, homeTabTextActive: { fontSize: fontSizes.xl, fontWeight: '700', color: colors.text.primary, }, }); } const TabBar: React.FC = ({ tabs, activeIndex, onTabChange, scrollable = false, rightContent, variant = 'default', icons, fontSize, style, }) => { const colors = useAppColors(); const styles = useMemo(() => createTabBarStyles(colors), [colors]); const renderTabs = () => { return tabs.map((tab, index) => { const isActive = index === activeIndex; const icon = icons?.[index]; if (variant === 'modern') { return ( onTabChange(index)} activeOpacity={0.7} > {icon && ( )} {tab} {isActive && } ); } if (variant === 'pill') { return ( onTabChange(index)} activeOpacity={0.8} > {tab} ); } if (variant === 'segmented') { const isFirst = index === 0; const isLast = index === tabs.length - 1; return ( onTabChange(index)} activeOpacity={0.8} > {icon && ( )} {tab} ); } if (variant === 'home') { const homeTextStyle = [ isActive ? styles.homeTabTextActive : styles.homeTabText, fontSize != null ? { fontSize } : undefined, ]; return ( onTabChange(index)} activeOpacity={0.7} > {tab} ); } return ( onTabChange(index)} activeOpacity={0.7} > {tab} {isActive && } ); }); }; const getContainerStyle = () => { switch (variant) { case 'pill': return styles.pillContainer; case 'segmented': return styles.segmentedContainer; case 'modern': return styles.modernContainer; case 'home': return styles.homeContainer; default: return styles.container; } }; if (scrollable) { return ( {renderTabs()} {rightContent && {rightContent}} ); } return ( {renderTabs()} {rightContent && {rightContent}} ); }; export default React.memo(TabBar);