You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

75 lines
3.8 KiB

import React from 'react';
import { Animated, Easing, PanResponder, View, StyleSheet, } from 'react-native';
export const TabViewBase = ({ value = 0, children, onChange = () => { }, onSwipeStart = () => { }, animationType = 'spring', animationConfig = {}, containerStyle, tabItemContainerStyle, disableSwipe = false, disableTransition = false, minSwipeRatio = 0.4, minSwipeSpeed = 1, }) => {
const translateX = React.useRef(new Animated.Value(0));
const currentIndex = React.useRef(0);
const [containerWidth, setContainerWidth] = React.useState(1);
const childCount = React.useMemo(() => React.Children.toArray(children).length, [children]);
const animate = React.useCallback((toValue) => {
Animated[animationType](translateX.current, Object.assign({ toValue, useNativeDriver: true, easing: Easing.ease }, animationConfig)).start();
}, [animationConfig, animationType]);
const releaseResponder = React.useCallback((_, { dx, vx }) => {
const position = dx / -containerWidth;
const shouldSwipe = Math.abs(position) > minSwipeRatio || Math.abs(vx) > minSwipeSpeed;
currentIndex.current += shouldSwipe ? Math.sign(position) : 0;
animate(currentIndex.current);
onChange(currentIndex.current);
}, [animate, containerWidth, minSwipeRatio, minSwipeSpeed, onChange]);
const panResponder = React.useMemo(() => PanResponder.create({
onPanResponderGrant: (_, { vx }) => {
onSwipeStart(vx > 0 ? 'left' : 'right');
},
onMoveShouldSetPanResponder: (_, { dx, dy, vx, vy }) => {
const panXInt = Math.floor(currentIndex.current);
return (!((dx > 0 && panXInt <= 0) ||
(dx < 0 && panXInt >= childCount - 1)) &&
Math.abs(dx) > Math.abs(dy) * 2 &&
Math.abs(vx) > Math.abs(vy) * 2.5);
},
onPanResponderMove: (_, { dx }) => {
const position = dx / -containerWidth;
translateX.current.setValue(Math.floor(currentIndex.current) + position);
},
onPanResponderRelease: releaseResponder,
onPanResponderTerminate: releaseResponder,
}), [childCount, containerWidth, onSwipeStart, releaseResponder]);
React.useEffect(() => {
if (Number.isInteger(value) && value !== currentIndex.current) {
animate(value);
currentIndex.current = value;
}
}, [animate, value]);
return (React.createElement(View, { style: [styles.container, containerStyle], onLayout: ({ nativeEvent: { layout } }) => {
setContainerWidth(layout.width);
} },
React.createElement(Animated.View, Object.assign({ testID: "RNE__TabView", style: StyleSheet.flatten([
StyleSheet.absoluteFillObject,
styles.container,
{
width: containerWidth * childCount,
transform: [
{
translateX: disableTransition
? -value * containerWidth
: translateX.current.interpolate({
inputRange: [0, 1],
outputRange: [0, -containerWidth],
}),
},
],
},
]) }, (!disableSwipe && panResponder.panHandlers)), React.Children.toArray(children).map((child, index) => (React.createElement(View, { key: index, style: StyleSheet.flatten([
styles.container,
tabItemContainerStyle,
{ width: containerWidth },
]) }, child))))));
};
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'stretch',
},
});
TabViewBase.displayName = 'TabView';