import { BottomTabBarProps } from '@react-navigation/bottom-tabs'; import { CommonActions, Route } from '@react-navigation/native'; import React, { useCallback, useContext } from 'react'; import { Animated, Platform, StyleSheet, TouchableOpacity, useWindowDimensions, View, } from 'react-native'; import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context'; import { Colors, Radius, Styles } from '../../Consts'; import { Icon } from '../../Icon'; import { Text } from '../../Text'; import { FloatingButtonProps } from '../types'; import CustomBottomTabItem from './CustomBottomTabItem'; import { ApplicationContext, MiniAppContext } from '../../Context'; type Props = BottomTabBarProps & { activeTintColor?: string; inactiveTintColor?: string; floatingButton?: FloatingButtonProps; activeBackgroundColor?: string; allowFontScaling?: boolean; inactiveBackgroundColor?: string; labelStyle?: any; iconStyle?: any; showLabel?: boolean; style?: any; tabStyle?: any; badgeStyle?: any; }; const getPaddingBottom = (insets: EdgeInsets) => { return Math.max(insets.bottom - Platform.select({ ios: 4, default: 0 }), 0); }; export default function BottomTabBar({ state, navigation, descriptors, activeBackgroundColor, activeTintColor, allowFontScaling, inactiveBackgroundColor, inactiveTintColor, labelStyle, iconStyle, showLabel, style, floatingButton, }: Props) { const { theme } = useContext(ApplicationContext); const { routes } = state; const widthDevice = useWindowDimensions()?.width; const useFloating = floatingButton && state.routes.length % 2 === 0; const itemWidth = widthDevice / (state.routes.length + (useFloating ? 1 : 0)); // const buildLink = useLinkBuilder(); const indicatorAnimated = React.useRef(new Animated.Value(0)).current; const insets = useSafeAreaInsets(); const paddingBottom = getPaddingBottom(insets); const focusedTab = state?.routes[state.index]?.name || routes[0]?.params; const context = useContext(MiniAppContext); const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false; /** * render tab item */ const renderTabItem = useCallback( (data: Route[]) => { return data?.map(route => { const focused = route?.params?.name === focusedTab; const { options } = descriptors[route.key]; const onPress = () => { const event = navigation.emit({ type: 'tabPress', target: route.key, canPreventDefault: true, }); if (!focused && !event.defaultPrevented) { navigation.dispatch({ ...CommonActions.navigate(route.name), target: state.key, }); } }; const onLongPress = () => { navigation.emit({ type: 'tabLongPress', target: route.key, }); }; return ( ); }); }, [ focusedTab, descriptors, activeTintColor, theme.colors.primary, theme.colors.text.default, inactiveTintColor, activeBackgroundColor, inactiveBackgroundColor, showLabel, labelStyle, iconStyle, allowFontScaling, navigation, state.key, ], ); /** * render floating button */ const renderFloatingButton = useCallback(() => { const renderFloatingIcon = () => { if (typeof floatingButton?.icon === 'string') { return ; } return floatingButton?.icon; }; return ( {renderFloatingIcon()} {floatingButton?.label} ); }, [ activeTintColor, floatingButton?.icon, floatingButton?.label, floatingButton?.onPress, showBaseLineDebug, theme.colors.primary, ]); /** * animated indicator */ React.useEffect(() => { let index = state.index; if (useFloating && index > (state.routes.length - 1) / 2) { index += 1; } Animated.timing(indicatorAnimated, { toValue: itemWidth * index, useNativeDriver: true, duration: 200, }).start(); }, [ state.index, itemWidth, indicatorAnimated, useFloating, state.routes.length, ]); /** * render tab bar */ const renderTabBar = () => { if (useFloating) { const middleIndex = routes.length / 2; const leftArr = routes.slice(0, middleIndex); const rightArr = routes.slice(middleIndex); return ( <> {renderTabItem(leftArr)} {renderFloatingButton()} {renderTabItem(rightArr)} ); } return renderTabItem(routes); }; return ( {useFloating && floatingButton?.container} {renderTabBar()} ); } const styles = StyleSheet.create({ content: { flex: 1, flexDirection: 'row', }, tabBar: { left: 0, right: 0, bottom: 0, borderTopWidth: StyleSheet.hairlineWidth, elevation: 8, }, tabBarFloatingButton: { left: 0, right: 0, bottom: 0, zIndex: 1, shadowColor: Colors.black_20, shadowOffset: { width: 0, height: -2 }, shadowOpacity: 0.05, shadowRadius: 3, elevation: 8, }, floatingContainer: { position: 'absolute', alignItems: 'center', left: 0, right: 0, top: -2, bottom: Platform.OS === 'ios' ? -6 : 4 }, floatingContent: { alignItems: 'center', justifyContent: 'center', height: 48, width: 48, borderRadius: 24, top: -10, }, indicator: { height: 2, width: 44, alignSelf: 'center', borderBottomLeftRadius: 1, borderBottomRightRadius: 1, }, debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 }, });