react-navigation-reanimated-top-tabs 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/ReanimatedTopTab/AnimationHelper.js +2 -2
- package/lib/commonjs/components/ReanimatedTopTab/AnimationHelper.js.map +1 -1
- package/lib/commonjs/components/ReanimatedTopTab/ReanimatedTabView.js +24 -13
- package/lib/commonjs/components/ReanimatedTopTab/ReanimatedTabView.js.map +1 -1
- package/lib/commonjs/components/elements/TabBarBaseComponent.js +2 -2
- package/lib/commonjs/components/elements/TabBarBaseComponent.js.map +1 -1
- package/lib/commonjs/components/wrappers/GestureWrapper.js +1 -1
- package/lib/commonjs/components/wrappers/GestureWrapper.js.map +1 -1
- package/lib/commonjs/context/Context.hooks.js +30 -1
- package/lib/commonjs/context/Context.hooks.js.map +1 -1
- package/lib/commonjs/context/Context.js +6 -0
- package/lib/commonjs/context/Context.js.map +1 -1
- package/lib/commonjs/hooks/useScreenDimensions.js +23 -0
- package/lib/commonjs/hooks/useScreenDimensions.js.map +1 -0
- package/lib/commonjs/hooks/useScreenScrollable.js +2 -2
- package/lib/commonjs/hooks/useScreenScrollable.js.map +1 -1
- package/lib/commonjs/navigator/createReanimatedTopTabNavigator.js +5 -20
- package/lib/commonjs/navigator/createReanimatedTopTabNavigator.js.map +1 -1
- package/lib/module/components/ReanimatedTopTab/AnimationHelper.js +2 -2
- package/lib/module/components/ReanimatedTopTab/AnimationHelper.js.map +1 -1
- package/lib/module/components/ReanimatedTopTab/ReanimatedTabView.js +25 -14
- package/lib/module/components/ReanimatedTopTab/ReanimatedTabView.js.map +1 -1
- package/lib/module/components/elements/TabBarBaseComponent.js +2 -2
- package/lib/module/components/elements/TabBarBaseComponent.js.map +1 -1
- package/lib/module/components/wrappers/GestureWrapper.js +1 -1
- package/lib/module/components/wrappers/GestureWrapper.js.map +1 -1
- package/lib/module/context/Context.hooks.js +30 -1
- package/lib/module/context/Context.hooks.js.map +1 -1
- package/lib/module/context/Context.js +6 -0
- package/lib/module/context/Context.js.map +1 -1
- package/lib/module/hooks/useScreenDimensions.js +18 -0
- package/lib/module/hooks/useScreenDimensions.js.map +1 -0
- package/lib/module/hooks/useScreenScrollable.js +2 -2
- package/lib/module/hooks/useScreenScrollable.js.map +1 -1
- package/lib/module/navigator/createReanimatedTopTabNavigator.js +6 -21
- package/lib/module/navigator/createReanimatedTopTabNavigator.js.map +1 -1
- package/lib/typescript/src/components/ReanimatedTopTab/AnimationHelper.d.ts +7 -3
- package/lib/typescript/src/components/ReanimatedTopTab/AnimationHelper.d.ts.map +1 -1
- package/lib/typescript/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts +1 -1
- package/lib/typescript/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts.map +1 -1
- package/lib/typescript/src/components/ReanimatedTopTab/types.d.ts +1 -1
- package/lib/typescript/src/components/ReanimatedTopTab/types.d.ts.map +1 -1
- package/lib/typescript/src/components/elements/TabBarBaseComponent.d.ts +1 -1
- package/lib/typescript/src/components/elements/TabBarBaseComponent.d.ts.map +1 -1
- package/lib/typescript/src/context/Context.d.ts.map +1 -1
- package/lib/typescript/src/context/Context.hooks.d.ts +1 -0
- package/lib/typescript/src/context/Context.hooks.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useScreenDimensions.d.ts +2 -0
- package/lib/typescript/src/hooks/useScreenDimensions.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/navigator/createReanimatedTopTabNavigator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ReanimatedTopTab/AnimationHelper.ts +7 -6
- package/src/components/ReanimatedTopTab/ReanimatedTabView.tsx +28 -16
- package/src/components/ReanimatedTopTab/types.ts +1 -1
- package/src/components/elements/TabBarBaseComponent.tsx +2 -2
- package/src/components/wrappers/GestureWrapper.tsx +1 -1
- package/src/context/Context.hooks.ts +34 -0
- package/src/context/Context.tsx +7 -0
- package/src/hooks/useScreenDimensions.ts +17 -0
- package/src/hooks/useScreenScrollable.ts +2 -2
- package/src/navigator/createReanimatedTopTabNavigator.tsx +9 -31
|
@@ -5,7 +5,8 @@ import type {
|
|
|
5
5
|
PanGestureHandlerEventPayload,
|
|
6
6
|
} from 'react-native-gesture-handler';
|
|
7
7
|
import { withTiming as reanimatedWithTiming } from 'react-native-reanimated';
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
type NavigationState = { size: number; index: number };
|
|
9
10
|
|
|
10
11
|
const onChange = (
|
|
11
12
|
event: GestureUpdateEvent<
|
|
@@ -13,13 +14,13 @@ const onChange = (
|
|
|
13
14
|
>,
|
|
14
15
|
animationValue: number,
|
|
15
16
|
width: number,
|
|
16
|
-
navigationState:
|
|
17
|
+
navigationState: NavigationState
|
|
17
18
|
) => {
|
|
18
19
|
'worklet';
|
|
19
20
|
if (animationValue > 0) {
|
|
20
21
|
return 0;
|
|
21
22
|
}
|
|
22
|
-
const routesLength = navigationState.
|
|
23
|
+
const routesLength = navigationState.size - 1;
|
|
23
24
|
if (animationValue < -width * routesLength) {
|
|
24
25
|
return -width * routesLength;
|
|
25
26
|
}
|
|
@@ -27,12 +28,12 @@ const onChange = (
|
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
const getIndex = (
|
|
30
|
-
navigationState:
|
|
31
|
+
navigationState: NavigationState,
|
|
31
32
|
type: 'increment' | 'decrement'
|
|
32
33
|
) => {
|
|
33
34
|
'worklet';
|
|
34
35
|
if (type === 'increment') {
|
|
35
|
-
return navigationState.index === navigationState.
|
|
36
|
+
return navigationState.index === navigationState.size - 1
|
|
36
37
|
? navigationState.index
|
|
37
38
|
: navigationState.index + 1;
|
|
38
39
|
}
|
|
@@ -45,7 +46,7 @@ const onEnd = (
|
|
|
45
46
|
event: GestureStateChangeEvent<PanGestureHandlerEventPayload>,
|
|
46
47
|
minimumValueToChangeView: number,
|
|
47
48
|
width: number,
|
|
48
|
-
navigationState:
|
|
49
|
+
navigationState: NavigationState
|
|
49
50
|
) => {
|
|
50
51
|
'worklet';
|
|
51
52
|
if (event.velocityX < -200) {
|
|
@@ -10,6 +10,7 @@ import Reanimated, {
|
|
|
10
10
|
interpolate,
|
|
11
11
|
LinearTransition,
|
|
12
12
|
runOnJS,
|
|
13
|
+
runOnUI,
|
|
13
14
|
useAnimatedStyle,
|
|
14
15
|
useDerivedValue,
|
|
15
16
|
useSharedValue,
|
|
@@ -21,13 +22,13 @@ export interface ReanimatedTabViewProps {
|
|
|
21
22
|
LazyPlaceholder?: () => React.ReactNode;
|
|
22
23
|
lazy?: boolean;
|
|
23
24
|
navigationState: ReanimatedTabViewTypes.NavigationState;
|
|
24
|
-
onIndexChange: (index: number) => void;
|
|
25
25
|
percentageTrigger?: number;
|
|
26
26
|
positionInterpolation?: ReanimatedTabViewTypes.PositionInterpolation;
|
|
27
27
|
renderScene: (params: ReanimatedTabViewTypes.SceneProps) => React.ReactNode;
|
|
28
28
|
renderTabBar?: (
|
|
29
29
|
params: ReanimatedTabViewTypes.RenderTabsParams
|
|
30
30
|
) => React.ReactNode;
|
|
31
|
+
navigate(index: number): void;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export const ReanimatedTabView = React.memo<ReanimatedTabViewProps>(
|
|
@@ -35,11 +36,11 @@ export const ReanimatedTabView = React.memo<ReanimatedTabViewProps>(
|
|
|
35
36
|
LazyPlaceholder = () => null,
|
|
36
37
|
lazy = false,
|
|
37
38
|
navigationState,
|
|
38
|
-
onIndexChange,
|
|
39
39
|
percentageTrigger = 0.4,
|
|
40
40
|
positionInterpolation,
|
|
41
41
|
renderScene,
|
|
42
42
|
renderTabBar,
|
|
43
|
+
navigate,
|
|
43
44
|
}) => {
|
|
44
45
|
const { width } = useWindowDimensions();
|
|
45
46
|
const loadedScreens = useRef([
|
|
@@ -59,22 +60,29 @@ export const ReanimatedTabView = React.memo<ReanimatedTabViewProps>(
|
|
|
59
60
|
);
|
|
60
61
|
}, [positionInterpolation]);
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
scrollPosition.value = AnimationHelper.animation(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}, [navigationState.index, scrollPosition, width]);
|
|
63
|
+
const _navigate = (index: number) => {
|
|
64
|
+
scrollPosition.value = AnimationHelper.animation(-index * width);
|
|
65
|
+
navigate(index);
|
|
66
|
+
};
|
|
67
67
|
|
|
68
68
|
const minimumValueToChangeView = useMemo(
|
|
69
69
|
() => width * percentageTrigger,
|
|
70
70
|
[percentageTrigger, width]
|
|
71
71
|
);
|
|
72
72
|
|
|
73
|
+
const _navigationState = useSharedValue({ size: 0, index: 0 });
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
runOnUI(() => {
|
|
77
|
+
_navigationState.value.size = navigationState.routes.length;
|
|
78
|
+
_navigationState.value.index = navigationState.index;
|
|
79
|
+
})();
|
|
80
|
+
}, [navigationState]);
|
|
81
|
+
|
|
73
82
|
const panGesture = React.useMemo(
|
|
74
83
|
() =>
|
|
75
84
|
Gesture.Pan()
|
|
76
|
-
.failOffsetY(-10)
|
|
77
|
-
.failOffsetY(10)
|
|
85
|
+
.failOffsetY([-10, 10])
|
|
78
86
|
.activeOffsetX([-20, 20])
|
|
79
87
|
.onChange((event) => {
|
|
80
88
|
'worklet';
|
|
@@ -82,7 +90,7 @@ export const ReanimatedTabView = React.memo<ReanimatedTabViewProps>(
|
|
|
82
90
|
event,
|
|
83
91
|
scrollPosition.value,
|
|
84
92
|
width,
|
|
85
|
-
|
|
93
|
+
_navigationState.value
|
|
86
94
|
);
|
|
87
95
|
})
|
|
88
96
|
.onEnd((event) => {
|
|
@@ -91,12 +99,13 @@ export const ReanimatedTabView = React.memo<ReanimatedTabViewProps>(
|
|
|
91
99
|
event,
|
|
92
100
|
minimumValueToChangeView,
|
|
93
101
|
width,
|
|
94
|
-
|
|
102
|
+
_navigationState.value
|
|
95
103
|
);
|
|
96
104
|
scrollPosition.value = AnimationHelper.animation(state.value);
|
|
97
|
-
|
|
105
|
+
_navigationState.value.index = state.index;
|
|
106
|
+
runOnJS(navigate)(state.index);
|
|
98
107
|
}),
|
|
99
|
-
[minimumValueToChangeView,
|
|
108
|
+
[minimumValueToChangeView, width, _navigationState]
|
|
100
109
|
);
|
|
101
110
|
|
|
102
111
|
const scrollPositionStyle = useAnimatedStyle(() => ({
|
|
@@ -121,9 +130,8 @@ export const ReanimatedTabView = React.memo<ReanimatedTabViewProps>(
|
|
|
121
130
|
return;
|
|
122
131
|
}
|
|
123
132
|
scrollPosition.value = AnimationHelper.animation(-newIndex * width);
|
|
124
|
-
onIndexChange(newIndex);
|
|
125
133
|
},
|
|
126
|
-
[navigationState.routes,
|
|
134
|
+
[navigationState.routes, scrollPosition, width]
|
|
127
135
|
);
|
|
128
136
|
|
|
129
137
|
const chooseRender = useCallback(
|
|
@@ -166,7 +174,11 @@ export const ReanimatedTabView = React.memo<ReanimatedTabViewProps>(
|
|
|
166
174
|
<GestureHandlerRootView style={defaultStyles.flex}>
|
|
167
175
|
<Reanimated.View layout={LinearTransition} style={defaultStyles.flex}>
|
|
168
176
|
{renderTabBar
|
|
169
|
-
? renderTabBar({
|
|
177
|
+
? renderTabBar({
|
|
178
|
+
navigationState,
|
|
179
|
+
position,
|
|
180
|
+
navigate: _navigate,
|
|
181
|
+
})
|
|
170
182
|
: null}
|
|
171
183
|
<GestureDetector gesture={panGesture}>
|
|
172
184
|
<Reanimated.View
|
|
@@ -14,7 +14,7 @@ export namespace ReanimatedTabViewTypes {
|
|
|
14
14
|
export interface RenderTabsParams {
|
|
15
15
|
navigationState: NavigationState;
|
|
16
16
|
position: SharedValue<number>;
|
|
17
|
-
|
|
17
|
+
navigate(index: number): void;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface SceneProps {
|
|
@@ -19,7 +19,7 @@ export const TabBarBaseComponent = ({
|
|
|
19
19
|
navigationState,
|
|
20
20
|
position,
|
|
21
21
|
style,
|
|
22
|
-
|
|
22
|
+
navigate,
|
|
23
23
|
}: TabBarProps) => {
|
|
24
24
|
const layout = useWindowDimensions();
|
|
25
25
|
|
|
@@ -52,7 +52,7 @@ export const TabBarBaseComponent = ({
|
|
|
52
52
|
style={styles.labelWrapper}
|
|
53
53
|
key={route.key}
|
|
54
54
|
onPress={() => {
|
|
55
|
-
|
|
55
|
+
navigate(index);
|
|
56
56
|
}}
|
|
57
57
|
>
|
|
58
58
|
{route.tabBarLabel ? (
|
|
@@ -43,7 +43,7 @@ export const GestureWrapper = ({ children }: GestureWrapperProps) => {
|
|
|
43
43
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
44
44
|
} as const;
|
|
45
45
|
|
|
46
|
-
const [_gestureEnabled, setGestureEnabled] = useState(
|
|
46
|
+
const [_gestureEnabled, setGestureEnabled] = useState(true);
|
|
47
47
|
|
|
48
48
|
useAnimatedReaction(
|
|
49
49
|
() => gestureEnabled.value,
|
|
@@ -276,8 +276,42 @@ const useTransformHeaderOnTabChange = ({
|
|
|
276
276
|
};
|
|
277
277
|
};
|
|
278
278
|
|
|
279
|
+
const useApproachingTabChange = ({
|
|
280
|
+
config,
|
|
281
|
+
transformationX,
|
|
282
|
+
gestureEnabled,
|
|
283
|
+
currentScreenIndex,
|
|
284
|
+
}: Pick<
|
|
285
|
+
ReanimatedTopTabNavigation.ContextType,
|
|
286
|
+
'config' | 'transformationX' | 'currentScreenIndex' | 'gestureEnabled'
|
|
287
|
+
>) => {
|
|
288
|
+
const onIndexApproach = (index: number) => {
|
|
289
|
+
'worklet';
|
|
290
|
+
currentScreenIndex.value = index;
|
|
291
|
+
switch (config[index]) {
|
|
292
|
+
case 'normal': {
|
|
293
|
+
gestureEnabled.value = true;
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case 'minimalized': {
|
|
297
|
+
gestureEnabled.value = false;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
useAnimatedReaction(
|
|
304
|
+
() => transformationX.value,
|
|
305
|
+
(current, previous) => {
|
|
306
|
+
const approachingIndex = ContextHelpers.getTargetIndex(current, previous);
|
|
307
|
+
onIndexApproach(approachingIndex);
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
|
|
279
312
|
export const ContextHooks = {
|
|
280
313
|
useResetApproachingScreenScrollOffset,
|
|
281
314
|
usePrepareContext,
|
|
282
315
|
useTransformHeaderOnTabChange,
|
|
316
|
+
useApproachingTabChange,
|
|
283
317
|
};
|
package/src/context/Context.tsx
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Dimensions } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const screenDimensions = Dimensions.get('screen');
|
|
5
|
+
|
|
6
|
+
export const useScreenDimensions = () => {
|
|
7
|
+
const [dimensions, setDimensions] = useState(screenDimensions);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const subscription = Dimensions.addEventListener('change', ({ screen }) => {
|
|
11
|
+
setDimensions(screen);
|
|
12
|
+
});
|
|
13
|
+
return () => subscription?.remove();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return dimensions;
|
|
17
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useRoute } from '@react-navigation/native';
|
|
2
|
-
import { useWindowDimensions } from 'react-native';
|
|
3
2
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
3
|
import { useTabContext } from './useTabContext';
|
|
5
4
|
import * as React from 'react';
|
|
@@ -13,10 +12,11 @@ import {
|
|
|
13
12
|
} from 'react-native-reanimated';
|
|
14
13
|
|
|
15
14
|
import { useScreenProperties } from './useScreenProperties';
|
|
15
|
+
import { useScreenDimensions } from './useScreenDimensions';
|
|
16
16
|
|
|
17
17
|
export const useScreenScrollable = () => {
|
|
18
18
|
const { key } = useRoute();
|
|
19
|
-
const { height } =
|
|
19
|
+
const { height } = useScreenDimensions();
|
|
20
20
|
const { bottom, top } = useSafeAreaInsets();
|
|
21
21
|
const {
|
|
22
22
|
currentYPosition,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createNavigatorFactory,
|
|
3
3
|
type ParamListBase,
|
|
4
|
+
TabActions,
|
|
4
5
|
type TabNavigationState,
|
|
5
6
|
TabRouter,
|
|
6
7
|
useNavigationBuilder,
|
|
@@ -12,7 +13,7 @@ import { Provider } from '../context/Context';
|
|
|
12
13
|
import { type ReanimatedTopTabNavigation } from '../types';
|
|
13
14
|
import { omit } from 'lodash';
|
|
14
15
|
import { useMemo } from 'react';
|
|
15
|
-
import Reanimated
|
|
16
|
+
import Reanimated from 'react-native-reanimated';
|
|
16
17
|
import type { ReanimatedTabViewTypes } from '../components/ReanimatedTopTab/types';
|
|
17
18
|
import { ReanimatedTabView } from '../components/ReanimatedTopTab/ReanimatedTabView';
|
|
18
19
|
import { ContextHooks } from '../context/Context.hooks';
|
|
@@ -47,26 +48,11 @@ const TabViewNavigator = ({
|
|
|
47
48
|
|
|
48
49
|
const context = ContextHooks.usePrepareContext({ state });
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
) =>
|
|
56
|
-
(index: number) => {
|
|
57
|
-
currentScreenIndex.value = index;
|
|
58
|
-
switch (_config[index]) {
|
|
59
|
-
case 'normal': {
|
|
60
|
-
gestureEnabled.value = true;
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
case 'minimalized': {
|
|
64
|
-
gestureEnabled.value = false;
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
navigation.navigate(state.routes[index]?.name ?? '');
|
|
69
|
-
};
|
|
51
|
+
const navigate = (index: number) => {
|
|
52
|
+
const route = state.routes[index];
|
|
53
|
+
if (!route) return;
|
|
54
|
+
navigation.dispatch(TabActions.jumpTo(route.name));
|
|
55
|
+
};
|
|
70
56
|
|
|
71
57
|
const navigationState = useMemo(
|
|
72
58
|
() => ({
|
|
@@ -101,12 +87,7 @@ const TabViewNavigator = ({
|
|
|
101
87
|
|
|
102
88
|
return (
|
|
103
89
|
<Provider config={_config} context={context}>
|
|
104
|
-
{({
|
|
105
|
-
currentScreenIndex,
|
|
106
|
-
gestureEnabled,
|
|
107
|
-
headerHeight,
|
|
108
|
-
transformationY,
|
|
109
|
-
}) => (
|
|
90
|
+
{({ headerHeight, transformationY }) => (
|
|
110
91
|
<>
|
|
111
92
|
<Reanimated.View
|
|
112
93
|
onLayout={({ nativeEvent }) => {
|
|
@@ -124,10 +105,7 @@ const TabViewNavigator = ({
|
|
|
124
105
|
<GestureWrapper>
|
|
125
106
|
<ReanimatedTabView
|
|
126
107
|
navigationState={navigationState}
|
|
127
|
-
|
|
128
|
-
currentScreenIndex,
|
|
129
|
-
gestureEnabled
|
|
130
|
-
)}
|
|
108
|
+
navigate={navigate}
|
|
131
109
|
renderScene={renderScene}
|
|
132
110
|
renderTabBar={TabBarComponent}
|
|
133
111
|
/>
|