UNPKG

react-native-curved-tab-bar

Version:

A customizable curved tab bar component for React Native with smooth animations

240 lines (231 loc) 6.58 kB
"use strict"; /* eslint-disable react-hooks/exhaustive-deps */ import React, { useEffect, useRef } from 'react'; import { Animated, Image, Keyboard, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { getGradientColors, vh, vw } from "../utils.js"; import { FloatingButton } from "./FloatingButton.js"; import { TabBarBackground } from "./TabBarBackground.js"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; export const CurvedTabBar = ({ tabs, activeIndex, onTabPress, gradientColors, heightPercentage = 9, floatingButtonSize = 6, activeIconColor = '#ffffff', inactiveIconColor = '#cccccc', inactiveTextColor = '#cccccc', fontSize = 12, fontFamily, hideOnKeyboard = false, springConfig = { damping: 12, stiffness: 120 }, shadowConfig = { shadowColor: '#000', shadowOffset: { width: 0, height: 3 }, shadowOpacity: 0.2, shadowRadius: 20, elevation: 8 } }) => { const [isKeyboardVisible, setKeyboardVisible] = React.useState(false); // Animation values const curvePosition = useRef(new Animated.Value(0)).current; const floatingAnims = useRef(tabs.map(() => new Animated.Value(0))).current; const processedGradientColors = getGradientColors(gradientColors); // Keyboard visibility handling useEffect(() => { if (!hideOnKeyboard) return; const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => { setKeyboardVisible(true); }); const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { setKeyboardVisible(false); }); return () => { keyboardDidShowListener?.remove(); keyboardDidHideListener?.remove(); }; }, [hideOnKeyboard]); // Calculate tab positions const getTabPosition = index => { const screenWidth = Math.ceil(vw * 100); const tabWidth = screenWidth / tabs.length; const tabCenter = index * tabWidth + tabWidth / 2; const screenCenter = screenWidth / 2; return -screenWidth + (tabCenter - screenCenter); }; // Animate curve and floating icon const animateToTab = targetIndex => { const targetPosition = getTabPosition(targetIndex); // Animate curve position Animated.spring(curvePosition, { toValue: targetPosition, useNativeDriver: true, damping: springConfig.damping, stiffness: springConfig.stiffness }).start(); // Reset all floating animations floatingAnims.forEach((anim, index) => { Animated.spring(anim, { toValue: index === targetIndex ? -vh * 4.2 : 0, useNativeDriver: true, damping: 10, stiffness: 100 }).start(); }); }; // Initialize with focused tab useEffect(() => { const timer = setTimeout(() => { animateToTab(activeIndex); }, 100); return () => clearTimeout(timer); }, []); // Animate when tab changes useEffect(() => { animateToTab(activeIndex); }, [activeIndex]); const styles = createStyles({ heightPercentage, fontSize, fontFamily, inactiveIconColor, inactiveTextColor }); if (hideOnKeyboard && isKeyboardVisible) { return null; } return /*#__PURE__*/_jsxs(View, { style: styles.wrapper, children: [/*#__PURE__*/_jsx(View, { style: styles.tabBackground, children: /*#__PURE__*/_jsx(TabBarBackground, { curvePosition: curvePosition, gradientColors: processedGradientColors, heightPercentage: heightPercentage }) }), tabs.map((tab, index) => { const isFocused = activeIndex === index; const handlePress = () => { onTabPress(index, tab); }; return /*#__PURE__*/_jsx(Animated.View, { style: [styles.tabContainer, { transform: [{ translateY: floatingAnims[index] }] }], children: /*#__PURE__*/_jsx(TouchableOpacity, { onPress: handlePress, style: styles.tabButton, children: isFocused ? /*#__PURE__*/ // Floating button with gradient background _jsx(FloatingButton, { icon: tab.icon, iconTintColor: activeIconColor, gradientColors: processedGradientColors, size: floatingButtonSize, shadowConfig: shadowConfig, badgeCount: tab.badgeCount }) : /*#__PURE__*/ // Regular tab button _jsxs(_Fragment, { children: [/*#__PURE__*/_jsxs(View, { style: styles.iconContainer, children: [/*#__PURE__*/_jsx(Image, { style: [styles.icon, { tintColor: inactiveIconColor }], source: tab.icon }), tab.badgeCount && tab.badgeCount > 0 && /*#__PURE__*/_jsx(View, { style: styles.badge, children: /*#__PURE__*/_jsx(Text, { style: styles.badgeText, children: tab.badgeCount > 99 ? '99+' : tab.badgeCount.toString() }) })] }), /*#__PURE__*/_jsx(Text, { style: [styles.tabText, { color: inactiveTextColor }], children: tab.label })] }) }) }, tab.key); })] }); }; const createStyles = ({ heightPercentage, fontSize, fontFamily }) => StyleSheet.create({ wrapper: { position: 'absolute', bottom: 0, alignSelf: 'center', backgroundColor: 'transparent', justifyContent: 'space-between', height: Math.ceil(vh * heightPercentage), flexDirection: 'row', width: '100%' }, tabBackground: { position: 'absolute', bottom: 0, zIndex: 20, width: '100%' }, tabContainer: { flex: 1, zIndex: 30 }, tabButton: { alignItems: 'center', justifyContent: 'center', flex: 1, paddingBottom: Platform.OS === 'ios' ? vh * 0.98 : 0 }, tabText: { fontSize, fontFamily, textAlign: 'center' }, iconContainer: { position: 'relative' }, icon: { width: 24, height: 24, resizeMode: 'contain', marginBottom: 2 }, badge: { position: 'absolute', top: -5, right: -10, backgroundColor: '#ff4444', borderRadius: 10, minWidth: 20, height: 20, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 4 }, badgeText: { color: 'white', fontSize: 10, fontWeight: 'bold' } }); //# sourceMappingURL=CurvedTabBar.js.map