@mindinventory/react-native-tab-bar-interaction
Version:
A tabbar component for React Native
209 lines (207 loc) • 7.41 kB
JavaScript
"use strict";
import { useEffect, useMemo, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { curveBasis, line } from 'd3-shape';
import { parse, interpolatePath } from 'react-native-redash';
import Animated, { runOnJS, useAnimatedProps, useSharedValue, withTiming, Easing } from 'react-native-reanimated';
import Svg, { Path } from 'react-native-svg';
import { TabItem } from "./TabItem.js";
import AnimatedCircle from "./AnimatedCircle.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const AnimatedPath = Animated.createAnimatedComponent(Path);
const TRANSITION_SPEED = 300;
const FIX_WIDTH = 380;
const TAB_BAR_HEIGHT = 64;
const generateTabShapePath = (position, adjustedHeight, tabContainerWidth, numOfTabs) => {
const adjustedWidth = tabContainerWidth / numOfTabs;
const tabX = adjustedWidth * position;
const scaleGen = tabContainerWidth / FIX_WIDTH * 1;
const SCALE = Number(scaleGen.toFixed(2));
const radius = adjustedHeight / 1.6 * SCALE; // Increase radius slightly
const lineGenerator = line().curve(curveBasis);
const halfCircle = lineGenerator([[tabX - radius, 0],
// Start of left side of half-circle
[tabX - radius, radius / 1.2],
// Control point for smoother, deeper transition
[tabX, radius * 1.4],
// Higher point at the top for more depth
[tabX + radius, radius / 1.2],
// Control point for smoother, deeper transition
[tabX + radius, 0] // End of right side of half-circle
]);
return `${halfCircle}`;
};
export const getPathXCenter = currentPath => {
const curves = parse(currentPath).curves;
const startPoint = curves[0].to;
const endPoint = curves[curves.length - 1].to;
const centerX = (startPoint.x + endPoint.x) / 2;
return centerX;
};
export const getPathXCenterByIndex = (tabPaths, index) => {
const curves = tabPaths[index].curves;
const startPoint = curves[0].to;
const endPoint = curves[curves.length - 1].to;
const centerX = (startPoint.x + endPoint.x) / 2;
return centerX;
};
export const TabBar = props => {
const {
tabs,
onTabChange,
containerWidth,
tabBarContainerBackground,
circleFillColor,
containerBottomSpace,
containerTopRightRadius = 10,
containerTopLeftRadius = 10,
containerBottomLeftRadius = 25,
containerBottomRightRadius = 25,
defaultActiveTabIndex,
transitionSpeed
} = props;
const [currentIndex, setCurrentIndex] = useState(0);
const numOfTabs = useMemo(() => tabs.length > 2 ? tabs.length : 3, [tabs.length]);
const containerPath = useMemo(() => {
// return `M0,0L${containerWidth},0L${containerWidth},0L${containerWidth},${TAB_BAR_HEIGHT}L0,${TAB_BAR_HEIGHT}L0,0`;
return `M${containerTopLeftRadius},0
H${containerWidth - containerTopRightRadius}
A${containerTopRightRadius},${containerTopRightRadius} 0 0 1 ${containerWidth},${containerTopRightRadius}
V${TAB_BAR_HEIGHT - containerBottomRightRadius}
A${containerBottomRightRadius},${containerBottomRightRadius} 0 0 1 ${containerWidth - containerBottomRightRadius},${TAB_BAR_HEIGHT}
H${containerBottomLeftRadius}
A${containerBottomLeftRadius},${containerBottomLeftRadius} 0 0 1 0,${TAB_BAR_HEIGHT - containerBottomLeftRadius}
V${containerTopLeftRadius}
A${containerTopLeftRadius},${containerTopLeftRadius} 0 0 1 ${containerTopLeftRadius},0
Z`;
}, [containerBottomLeftRadius, containerBottomRightRadius, containerTopLeftRadius, containerTopRightRadius, containerWidth]);
const curvedPaths = useMemo(() => {
return Array.from({
length: numOfTabs
}, (_, index) => {
const tabShapePath = generateTabShapePath(index + 0.5, TAB_BAR_HEIGHT, containerWidth, numOfTabs);
return parse(`${tabShapePath}`);
});
}, [containerWidth, numOfTabs]);
const circleXCoordinate = useSharedValue(0);
const progress = useSharedValue(1);
const handleMoveCircle = currentPath => {
circleXCoordinate.value = getPathXCenter(currentPath);
};
const animatedProps = useAnimatedProps(() => {
const currentPath = interpolatePath(progress.value, Array.from({
length: curvedPaths.length
}, (_, index) => index + 1), curvedPaths);
runOnJS(handleMoveCircle)(currentPath);
return {
d: `${containerPath} ${currentPath}`
};
});
const handleTabPress = index => {
progress.value = withTiming(index + 1, {
duration: transitionSpeed ? transitionSpeed : TRANSITION_SPEED,
easing: Easing.linear
});
setCurrentIndex(index);
};
useEffect(() => {
if (defaultActiveTabIndex !== undefined) {
progress.value = withTiming(defaultActiveTabIndex + 1, {
duration: transitionSpeed ? transitionSpeed : TRANSITION_SPEED
});
setCurrentIndex(defaultActiveTabIndex);
}
}, [defaultActiveTabIndex, progress, transitionSpeed]);
const tabBarContainerStyle = useMemo(() => {
return {
bottom: containerBottomSpace ? containerBottomSpace : 0,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10
};
}, [containerBottomSpace]);
if (tabs.length > 5) {
return /*#__PURE__*/_jsx(View, {
style: styles.emptyContainer,
children: /*#__PURE__*/_jsx(Text, {
children: "You can add maximum five tabs"
})
});
} else if (tabs.length < 2) {
return /*#__PURE__*/_jsx(View, {
style: styles.emptyContainer,
children: /*#__PURE__*/_jsx(Text, {
children: "Please add tab data"
})
});
}
return /*#__PURE__*/_jsxs(View, {
style: [styles.tabBarContainer, tabBarContainerStyle],
children: [/*#__PURE__*/_jsx(Svg, {
width: containerWidth,
height: TAB_BAR_HEIGHT,
style: styles.shadowMd,
children: /*#__PURE__*/_jsx(AnimatedPath, {
fill: tabBarContainerBackground ? tabBarContainerBackground : '#fff',
animatedProps: animatedProps
})
}), /*#__PURE__*/_jsx(AnimatedCircle, {
circleX: circleXCoordinate,
circleFillColor: circleFillColor
}), /*#__PURE__*/_jsx(View, {
style: [styles.tabItemsContainer, {
height: TAB_BAR_HEIGHT
}],
children: tabs.map((val, index) => {
return /*#__PURE__*/_jsx(TabItem, {
label: val.name,
activeIcon: val.activeIcon,
inactiveIcon: val.inactiveIcon,
activeIndex: defaultActiveTabIndex ? defaultActiveTabIndex : 1,
index: index,
onTabPress: () => {
handleTabPress(index);
if (val !== undefined) {
onTabChange(val, index);
}
},
containerWidth: containerWidth,
curvedPaths: curvedPaths,
currentIndex: currentIndex,
transitionSpeed: transitionSpeed
}, index.toString());
})
})]
});
};
const styles = StyleSheet.create({
tabBarContainer: {
position: 'absolute',
zIndex: 2,
marginHorizontal: 'auto',
alignSelf: 'center',
bottom: 0,
borderBottomLeftRadius: 30
},
tabItemsContainer: {
position: 'absolute',
flexDirection: 'row',
width: '100%',
overflow: 'hidden'
},
shadowMd: {
elevation: 3,
shadowColor: '#000',
shadowOpacity: 0.2,
shadowRadius: 3,
shadowOffset: {
width: 0,
height: 3
}
},
emptyContainer: {
justifyContent: 'center',
alignItems: 'center',
height: TAB_BAR_HEIGHT
}
});
//# sourceMappingURL=TabBar.js.map