144 lines
5.6 KiB
TypeScript
144 lines
5.6 KiB
TypeScript
import React from 'react';
|
|
import { View, StyleSheet, TouchableOpacity, Platform, Dimensions } from 'react-native';
|
|
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { theme } from '../styles/theme';
|
|
import { Animated } from 'react-native';
|
|
|
|
const { width } = Dimensions.get('window');
|
|
|
|
export function CustomTabBar({ state, descriptors, navigation }: BottomTabBarProps) {
|
|
return (
|
|
<View style={styles.container}>
|
|
<LinearGradient
|
|
colors={['rgba(255, 255, 255, 0.9)', 'rgba(255, 255, 255, 0.7)']}
|
|
style={[styles.tabBar, theme.shadows.medium]}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 0, y: 1 }}
|
|
>
|
|
{state.routes.map((route, index) => {
|
|
const { options } = descriptors[route.key];
|
|
const isFocused = state.index === index;
|
|
|
|
const onPress = () => {
|
|
const event = navigation.emit({
|
|
type: 'tabPress',
|
|
target: route.key,
|
|
canPreventDefault: true,
|
|
});
|
|
|
|
if (!isFocused && !event.defaultPrevented) {
|
|
navigation.navigate(route.name);
|
|
}
|
|
};
|
|
|
|
const getIconName = (routeName: string, focused: boolean): keyof typeof Ionicons.glyphMap => {
|
|
switch (routeName) {
|
|
case 'index':
|
|
return focused ? 'home' : 'home-outline';
|
|
case 'goals':
|
|
return focused ? 'trophy' : 'trophy-outline';
|
|
case 'attendance':
|
|
return focused ? 'calendar' : 'calendar-outline';
|
|
case 'recommendations':
|
|
return focused ? 'sparkles' : 'sparkles-outline';
|
|
case 'profile':
|
|
return focused ? 'person' : 'person-outline';
|
|
default:
|
|
return 'ellipse-outline';
|
|
}
|
|
};
|
|
|
|
// Animation for scale
|
|
const scaleValue = React.useRef(new Animated.Value(1)).current;
|
|
|
|
React.useEffect(() => {
|
|
Animated.spring(scaleValue, {
|
|
toValue: isFocused ? 1.2 : 1,
|
|
useNativeDriver: true,
|
|
friction: 8,
|
|
}).start();
|
|
}, [isFocused]);
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
key={index}
|
|
accessibilityRole="button"
|
|
accessibilityState={isFocused ? { selected: true } : {}}
|
|
accessibilityLabel={options.tabBarAccessibilityLabel}
|
|
testID={(options as any).tabBarTestID}
|
|
onPress={onPress}
|
|
style={styles.tabItem}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Animated.View style={{ transform: [{ scale: scaleValue }] }}>
|
|
{isFocused ? (
|
|
<LinearGradient
|
|
colors={theme.gradients.primary}
|
|
style={styles.iconContainer}
|
|
>
|
|
<Ionicons
|
|
name={getIconName(route.name, true)}
|
|
size={20}
|
|
color="#fff"
|
|
/>
|
|
</LinearGradient>
|
|
) : (
|
|
<Ionicons
|
|
name={getIconName(route.name, false)}
|
|
size={24}
|
|
color={theme.colors.gray500}
|
|
/>
|
|
)}
|
|
</Animated.View>
|
|
</TouchableOpacity>
|
|
);
|
|
})}
|
|
</LinearGradient>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
alignItems: 'center',
|
|
paddingBottom: Platform.OS === 'ios' ? 30 : 20,
|
|
pointerEvents: 'box-none',
|
|
},
|
|
tabBar: {
|
|
flexDirection: 'row',
|
|
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
|
borderRadius: 35,
|
|
height: 70,
|
|
width: width - 40,
|
|
justifyContent: 'space-around',
|
|
alignItems: 'center',
|
|
borderWidth: 1,
|
|
borderColor: 'rgba(255, 255, 255, 0.5)',
|
|
paddingHorizontal: 10,
|
|
},
|
|
tabItem: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
height: '100%',
|
|
},
|
|
iconContainer: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
shadowColor: theme.colors.primary,
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 8,
|
|
elevation: 4,
|
|
},
|
|
});
|