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

344 lines
9.4 KiB
TypeScript
Raw Normal View History

/**
* 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';
interface TabBarProps {
tabs: string[];
activeIndex: number;
onTabChange: (index: number) => void;
scrollable?: boolean;
rightContent?: ReactNode;
variant?: TabBarVariant;
icons?: string[];
style?: StyleProp<ViewStyle>;
}
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,
borderRadius: borderRadius['2xl'],
marginHorizontal: spacing.lg,
marginVertical: spacing.sm,
padding: spacing.xs,
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 16,
elevation: 6,
},
modernTab: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: spacing.md,
paddingHorizontal: spacing.sm,
borderRadius: borderRadius.lg,
position: 'relative',
backgroundColor: 'transparent',
},
modernTabActive: {
backgroundColor: colors.primary.main + '12',
shadowColor: colors.primary.main + '20',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 3,
},
modernTabContent: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
modernTabIcon: {
marginRight: spacing.xs,
},
modernTabText: {
fontWeight: '600',
fontSize: fontSizes.md,
},
modernTabTextActive: {
fontWeight: '700',
},
modernTabIndicator: {
position: 'absolute',
bottom: 4,
width: 20,
height: 3,
backgroundColor: colors.primary.main,
borderRadius: borderRadius.full,
},
});
}
const TabBar: React.FC<TabBarProps> = ({
tabs,
activeIndex,
onTabChange,
scrollable = false,
rightContent,
variant = 'default',
icons,
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 (
<TouchableOpacity
key={index}
style={[styles.modernTab, isActive && styles.modernTabActive]}
onPress={() => onTabChange(index)}
activeOpacity={0.7}
>
<View style={styles.modernTabContent}>
{icon && (
<MaterialCommunityIcons
name={icon as any}
size={18}
color={isActive ? colors.primary.main : colors.text.secondary}
style={styles.modernTabIcon}
/>
)}
<Text
variant="body"
color={isActive ? colors.primary.main : colors.text.secondary}
style={isActive ? [styles.modernTabText, styles.modernTabTextActive] : styles.modernTabText}
>
{tab}
</Text>
</View>
{isActive && <View style={styles.modernTabIndicator} />}
</TouchableOpacity>
);
}
if (variant === 'pill') {
return (
<TouchableOpacity
key={index}
style={[styles.pillTab, isActive && styles.pillTabActive]}
onPress={() => onTabChange(index)}
activeOpacity={0.8}
>
<Text
variant="body"
color={isActive ? colors.text.inverse : colors.text.secondary}
style={styles.pillTabText}
>
{tab}
</Text>
</TouchableOpacity>
);
}
if (variant === 'segmented') {
const isFirst = index === 0;
const isLast = index === tabs.length - 1;
return (
<TouchableOpacity
key={index}
style={[
styles.segmentedTab,
isActive && styles.segmentedTabActive,
isFirst && styles.segmentedTabFirst,
isLast && styles.segmentedTabLast,
]}
onPress={() => onTabChange(index)}
activeOpacity={0.8}
>
<View style={styles.segmentedTabContent}>
{icon && (
<MaterialCommunityIcons
name={icon as any}
size={16}
color={isActive ? colors.primary.main : colors.text.secondary}
style={styles.segmentedTabIcon}
/>
)}
<Text variant="body" color={isActive ? colors.primary.main : colors.text.secondary} style={styles.segmentedTabText}>
{tab}
</Text>
</View>
</TouchableOpacity>
);
}
return (
<TouchableOpacity
key={index}
style={[styles.tab, isActive && styles.activeTab]}
onPress={() => onTabChange(index)}
activeOpacity={0.7}
>
<Text
variant="body"
color={isActive ? colors.primary.main : colors.text.secondary}
style={isActive ? [styles.tabText, styles.activeTabText] : styles.tabText}
>
{tab}
</Text>
{isActive && <View style={styles.activeIndicator} />}
</TouchableOpacity>
);
});
};
const getContainerStyle = () => {
switch (variant) {
case 'pill':
return styles.pillContainer;
case 'segmented':
return styles.segmentedContainer;
case 'modern':
return styles.modernContainer;
default:
return styles.container;
}
};
if (scrollable) {
return (
<View style={[getContainerStyle(), style]}>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.scrollableContainer}>
{renderTabs()}
</ScrollView>
{rightContent && <View style={styles.rightContent}>{rightContent}</View>}
</View>
);
}
return (
<View style={[getContainerStyle(), style]}>
{renderTabs()}
{rightContent && <View style={styles.rightContent}>{rightContent}</View>}
</View>
);
};
export default TabBar;