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.
Files changed (62) hide show
  1. package/lib/commonjs/components/ReanimatedTopTab/AnimationHelper.js +2 -2
  2. package/lib/commonjs/components/ReanimatedTopTab/AnimationHelper.js.map +1 -1
  3. package/lib/commonjs/components/ReanimatedTopTab/ReanimatedTabView.js +24 -13
  4. package/lib/commonjs/components/ReanimatedTopTab/ReanimatedTabView.js.map +1 -1
  5. package/lib/commonjs/components/elements/TabBarBaseComponent.js +2 -2
  6. package/lib/commonjs/components/elements/TabBarBaseComponent.js.map +1 -1
  7. package/lib/commonjs/components/wrappers/GestureWrapper.js +1 -1
  8. package/lib/commonjs/components/wrappers/GestureWrapper.js.map +1 -1
  9. package/lib/commonjs/context/Context.hooks.js +30 -1
  10. package/lib/commonjs/context/Context.hooks.js.map +1 -1
  11. package/lib/commonjs/context/Context.js +6 -0
  12. package/lib/commonjs/context/Context.js.map +1 -1
  13. package/lib/commonjs/hooks/useScreenDimensions.js +23 -0
  14. package/lib/commonjs/hooks/useScreenDimensions.js.map +1 -0
  15. package/lib/commonjs/hooks/useScreenScrollable.js +2 -2
  16. package/lib/commonjs/hooks/useScreenScrollable.js.map +1 -1
  17. package/lib/commonjs/navigator/createReanimatedTopTabNavigator.js +5 -20
  18. package/lib/commonjs/navigator/createReanimatedTopTabNavigator.js.map +1 -1
  19. package/lib/module/components/ReanimatedTopTab/AnimationHelper.js +2 -2
  20. package/lib/module/components/ReanimatedTopTab/AnimationHelper.js.map +1 -1
  21. package/lib/module/components/ReanimatedTopTab/ReanimatedTabView.js +25 -14
  22. package/lib/module/components/ReanimatedTopTab/ReanimatedTabView.js.map +1 -1
  23. package/lib/module/components/elements/TabBarBaseComponent.js +2 -2
  24. package/lib/module/components/elements/TabBarBaseComponent.js.map +1 -1
  25. package/lib/module/components/wrappers/GestureWrapper.js +1 -1
  26. package/lib/module/components/wrappers/GestureWrapper.js.map +1 -1
  27. package/lib/module/context/Context.hooks.js +30 -1
  28. package/lib/module/context/Context.hooks.js.map +1 -1
  29. package/lib/module/context/Context.js +6 -0
  30. package/lib/module/context/Context.js.map +1 -1
  31. package/lib/module/hooks/useScreenDimensions.js +18 -0
  32. package/lib/module/hooks/useScreenDimensions.js.map +1 -0
  33. package/lib/module/hooks/useScreenScrollable.js +2 -2
  34. package/lib/module/hooks/useScreenScrollable.js.map +1 -1
  35. package/lib/module/navigator/createReanimatedTopTabNavigator.js +6 -21
  36. package/lib/module/navigator/createReanimatedTopTabNavigator.js.map +1 -1
  37. package/lib/typescript/src/components/ReanimatedTopTab/AnimationHelper.d.ts +7 -3
  38. package/lib/typescript/src/components/ReanimatedTopTab/AnimationHelper.d.ts.map +1 -1
  39. package/lib/typescript/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts +1 -1
  40. package/lib/typescript/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts.map +1 -1
  41. package/lib/typescript/src/components/ReanimatedTopTab/types.d.ts +1 -1
  42. package/lib/typescript/src/components/ReanimatedTopTab/types.d.ts.map +1 -1
  43. package/lib/typescript/src/components/elements/TabBarBaseComponent.d.ts +1 -1
  44. package/lib/typescript/src/components/elements/TabBarBaseComponent.d.ts.map +1 -1
  45. package/lib/typescript/src/context/Context.d.ts.map +1 -1
  46. package/lib/typescript/src/context/Context.hooks.d.ts +1 -0
  47. package/lib/typescript/src/context/Context.hooks.d.ts.map +1 -1
  48. package/lib/typescript/src/hooks/useScreenDimensions.d.ts +2 -0
  49. package/lib/typescript/src/hooks/useScreenDimensions.d.ts.map +1 -0
  50. package/lib/typescript/src/index.d.ts +1 -1
  51. package/lib/typescript/src/navigator/createReanimatedTopTabNavigator.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/components/ReanimatedTopTab/AnimationHelper.ts +7 -6
  54. package/src/components/ReanimatedTopTab/ReanimatedTabView.tsx +28 -16
  55. package/src/components/ReanimatedTopTab/types.ts +1 -1
  56. package/src/components/elements/TabBarBaseComponent.tsx +2 -2
  57. package/src/components/wrappers/GestureWrapper.tsx +1 -1
  58. package/src/context/Context.hooks.ts +34 -0
  59. package/src/context/Context.tsx +7 -0
  60. package/src/hooks/useScreenDimensions.ts +17 -0
  61. package/src/hooks/useScreenScrollable.ts +2 -2
  62. 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
- import type { ReanimatedTabViewTypes } from './types';
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: ReanimatedTabViewTypes.NavigationState
17
+ navigationState: NavigationState
17
18
  ) => {
18
19
  'worklet';
19
20
  if (animationValue > 0) {
20
21
  return 0;
21
22
  }
22
- const routesLength = navigationState.routes.length - 1;
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: ReanimatedTabViewTypes.NavigationState,
31
+ navigationState: NavigationState,
31
32
  type: 'increment' | 'decrement'
32
33
  ) => {
33
34
  'worklet';
34
35
  if (type === 'increment') {
35
- return navigationState.index === navigationState.routes.length - 1
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: ReanimatedTabViewTypes.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
- useEffect(() => {
63
- scrollPosition.value = AnimationHelper.animation(
64
- -navigationState.index * width
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
- navigationState
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
- navigationState
102
+ _navigationState.value
95
103
  );
96
104
  scrollPosition.value = AnimationHelper.animation(state.value);
97
- runOnJS(onIndexChange)(state.index);
105
+ _navigationState.value.index = state.index;
106
+ runOnJS(navigate)(state.index);
98
107
  }),
99
- [minimumValueToChangeView, navigationState, onIndexChange, width]
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, onIndexChange, scrollPosition, width]
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({ navigationState, position, onIndexChange })
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
- onIndexChange: (index: number) => void;
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
- onIndexChange,
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
- onIndexChange(index);
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(gestureEnabled.value);
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
  };
@@ -37,6 +37,13 @@ export const Provider = memo(
37
37
  context,
38
38
  });
39
39
 
40
+ ContextHooks.useApproachingTabChange({
41
+ config,
42
+ gestureEnabled,
43
+ transformationX,
44
+ currentScreenIndex,
45
+ });
46
+
40
47
  return (
41
48
  <Context.Provider
42
49
  value={{
@@ -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 } = useWindowDimensions();
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, { type SharedValue } from 'react-native-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
- //NOTE: all belows reefers to Tabs component directly. Consider to move it somewhere
51
- const onIndexChange =
52
- (
53
- currentScreenIndex: SharedValue<number>,
54
- gestureEnabled: SharedValue<boolean>
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
- onIndexChange={onIndexChange(
128
- currentScreenIndex,
129
- gestureEnabled
130
- )}
108
+ navigate={navigate}
131
109
  renderScene={renderScene}
132
110
  renderTabBar={TabBarComponent}
133
111
  />