react-navigation-reanimated-top-tabs 0.3.0 → 0.4.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 (196) hide show
  1. package/lib/commonjs/components/ReanimatedTopTab/ReanimatedTabView.js +5 -3
  2. package/lib/commonjs/components/ReanimatedTopTab/ReanimatedTabView.js.map +1 -1
  3. package/lib/commonjs/components/elements/TabBarBaseComponent.js +6 -5
  4. package/lib/commonjs/components/elements/TabBarBaseComponent.js.map +1 -1
  5. package/lib/commonjs/components/elements/TabBarLabelBaseComponent.js +2 -4
  6. package/lib/commonjs/components/elements/TabBarLabelBaseComponent.js.map +1 -1
  7. package/lib/commonjs/components/wrappers/GestureWrapper.js +18 -26
  8. package/lib/commonjs/components/wrappers/GestureWrapper.js.map +1 -1
  9. package/lib/commonjs/components/wrappers/ScreenWrapper.js +4 -4
  10. package/lib/commonjs/components/wrappers/ScreenWrapper.js.map +1 -1
  11. package/lib/commonjs/components/wrappers/ScrollView.js +27 -16
  12. package/lib/commonjs/components/wrappers/ScrollView.js.map +1 -1
  13. package/lib/commonjs/context/Context.helpers.js +19 -0
  14. package/lib/commonjs/context/Context.helpers.js.map +1 -0
  15. package/lib/commonjs/context/Context.hooks.js +216 -0
  16. package/lib/commonjs/context/Context.hooks.js.map +1 -0
  17. package/lib/commonjs/context/Context.js +68 -0
  18. package/lib/commonjs/context/Context.js.map +1 -0
  19. package/lib/commonjs/hooks/useHeader.js +30 -0
  20. package/lib/commonjs/hooks/useHeader.js.map +1 -0
  21. package/lib/commonjs/hooks/useScreenGesture.js +21 -0
  22. package/lib/commonjs/hooks/useScreenGesture.js.map +1 -0
  23. package/lib/commonjs/hooks/useScreenProperties.js +21 -0
  24. package/lib/commonjs/hooks/useScreenProperties.js.map +1 -0
  25. package/lib/commonjs/hooks/{Tab.hooks.js → useScreenScrollable.js} +14 -83
  26. package/lib/commonjs/hooks/useScreenScrollable.js.map +1 -0
  27. package/lib/commonjs/hooks/useTabContext.js +11 -0
  28. package/lib/commonjs/hooks/useTabContext.js.map +1 -0
  29. package/lib/commonjs/hooks/useTabOffset.js +18 -0
  30. package/lib/commonjs/hooks/useTabOffset.js.map +1 -0
  31. package/lib/commonjs/index.js +25 -11
  32. package/lib/commonjs/index.js.map +1 -1
  33. package/lib/commonjs/navigator/createReanimatedTopTabNavigator.js +14 -29
  34. package/lib/commonjs/navigator/createReanimatedTopTabNavigator.js.map +1 -1
  35. package/lib/commonjs/services/Warning.service.js +16 -0
  36. package/lib/commonjs/services/Warning.service.js.map +1 -0
  37. package/lib/module/components/ReanimatedTopTab/ReanimatedTabView.js +8 -6
  38. package/lib/module/components/ReanimatedTopTab/ReanimatedTabView.js.map +1 -1
  39. package/lib/module/components/elements/TabBarBaseComponent.js +5 -4
  40. package/lib/module/components/elements/TabBarBaseComponent.js.map +1 -1
  41. package/lib/module/components/elements/TabBarLabelBaseComponent.js +2 -4
  42. package/lib/module/components/elements/TabBarLabelBaseComponent.js.map +1 -1
  43. package/lib/module/components/wrappers/GestureWrapper.js +18 -27
  44. package/lib/module/components/wrappers/GestureWrapper.js.map +1 -1
  45. package/lib/module/components/wrappers/ScreenWrapper.js +3 -3
  46. package/lib/module/components/wrappers/ScreenWrapper.js.map +1 -1
  47. package/lib/module/components/wrappers/ScrollView.js +23 -12
  48. package/lib/module/components/wrappers/ScrollView.js.map +1 -1
  49. package/lib/module/context/Context.helpers.js +15 -0
  50. package/lib/module/context/Context.helpers.js.map +1 -0
  51. package/lib/module/context/Context.hooks.js +213 -0
  52. package/lib/module/context/Context.hooks.js.map +1 -0
  53. package/lib/module/context/Context.js +64 -0
  54. package/lib/module/context/Context.js.map +1 -0
  55. package/lib/module/hooks/useHeader.js +25 -0
  56. package/lib/module/hooks/useHeader.js.map +1 -0
  57. package/lib/module/hooks/useScreenGesture.js +16 -0
  58. package/lib/module/hooks/useScreenGesture.js.map +1 -0
  59. package/lib/module/hooks/useScreenProperties.js +16 -0
  60. package/lib/module/hooks/useScreenProperties.js.map +1 -0
  61. package/lib/module/hooks/useScreenScrollable.js +57 -0
  62. package/lib/module/hooks/useScreenScrollable.js.map +1 -0
  63. package/lib/module/hooks/useTabContext.js +6 -0
  64. package/lib/module/hooks/useTabContext.js.map +1 -0
  65. package/lib/module/hooks/useTabOffset.js +12 -0
  66. package/lib/module/hooks/useTabOffset.js.map +1 -0
  67. package/lib/module/index.js +21 -7
  68. package/lib/module/index.js.map +1 -1
  69. package/lib/module/navigator/createReanimatedTopTabNavigator.js +13 -27
  70. package/lib/module/navigator/createReanimatedTopTabNavigator.js.map +1 -1
  71. package/lib/module/services/Warning.service.js +12 -0
  72. package/lib/module/services/Warning.service.js.map +1 -0
  73. package/lib/typescript/src/components/ReanimatedTopTab/AnimationHelper.d.ts.map +1 -0
  74. package/lib/typescript/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts.map +1 -0
  75. package/lib/typescript/src/components/ReanimatedTopTab/types.d.ts.map +1 -0
  76. package/lib/typescript/src/components/elements/TabBarBaseComponent.d.ts.map +1 -0
  77. package/lib/typescript/src/components/elements/TabBarLabelBaseComponent.d.ts.map +1 -0
  78. package/lib/typescript/src/components/wrappers/GestureWrapper.d.ts +7 -0
  79. package/lib/typescript/src/components/wrappers/GestureWrapper.d.ts.map +1 -0
  80. package/lib/typescript/src/components/wrappers/ScreenWrapper.d.ts.map +1 -0
  81. package/lib/typescript/src/components/wrappers/ScrollView.d.ts +5 -0
  82. package/lib/typescript/src/components/wrappers/ScrollView.d.ts.map +1 -0
  83. package/lib/typescript/{commonjs/src/context/TabContext.d.ts → src/context/Context.d.ts} +2 -2
  84. package/lib/typescript/src/context/Context.d.ts.map +1 -0
  85. package/lib/typescript/src/context/Context.helpers.d.ts +4 -0
  86. package/lib/typescript/src/context/Context.helpers.d.ts.map +1 -0
  87. package/lib/typescript/src/context/Context.hooks.d.ts +15 -0
  88. package/lib/typescript/src/context/Context.hooks.d.ts.map +1 -0
  89. package/lib/typescript/src/hooks/useHeader.d.ts +10 -0
  90. package/lib/typescript/src/hooks/useHeader.d.ts.map +1 -0
  91. package/lib/typescript/src/hooks/useScreenGesture.d.ts +2 -0
  92. package/lib/typescript/src/hooks/useScreenGesture.d.ts.map +1 -0
  93. package/lib/typescript/src/hooks/useScreenProperties.d.ts +10 -0
  94. package/lib/typescript/src/hooks/useScreenProperties.d.ts.map +1 -0
  95. package/lib/typescript/src/hooks/useScreenScrollable.d.ts +14 -0
  96. package/lib/typescript/src/hooks/useScreenScrollable.d.ts.map +1 -0
  97. package/lib/typescript/src/hooks/useTabContext.d.ts +2 -0
  98. package/lib/typescript/src/hooks/useTabContext.d.ts.map +1 -0
  99. package/lib/typescript/src/hooks/useTabOffset.d.ts +2 -0
  100. package/lib/typescript/src/hooks/useTabOffset.d.ts.map +1 -0
  101. package/lib/typescript/{commonjs/src → src}/index.d.ts +11 -1
  102. package/lib/typescript/src/index.d.ts.map +1 -0
  103. package/lib/typescript/src/navigator/createReanimatedTopTabNavigator.d.ts.map +1 -0
  104. package/lib/typescript/src/services/Warning.service.d.ts +5 -0
  105. package/lib/typescript/src/services/Warning.service.d.ts.map +1 -0
  106. package/lib/typescript/{module/src → src}/types.d.ts +24 -17
  107. package/lib/typescript/src/types.d.ts.map +1 -0
  108. package/package.json +24 -38
  109. package/src/components/ReanimatedTopTab/ReanimatedTabView.tsx +7 -6
  110. package/src/components/elements/TabBarBaseComponent.tsx +6 -2
  111. package/src/components/elements/TabBarLabelBaseComponent.tsx +3 -2
  112. package/src/components/wrappers/GestureWrapper.tsx +27 -29
  113. package/src/components/wrappers/ScreenWrapper.tsx +4 -3
  114. package/src/components/wrappers/ScrollView.tsx +33 -19
  115. package/src/context/Context.helpers.tsx +10 -0
  116. package/src/context/Context.hooks.ts +253 -0
  117. package/src/context/Context.tsx +69 -0
  118. package/src/hooks/useHeader.ts +30 -0
  119. package/src/hooks/useScreenGesture.ts +13 -0
  120. package/src/hooks/useScreenProperties.ts +11 -0
  121. package/src/hooks/useScreenScrollable.ts +69 -0
  122. package/src/hooks/useTabContext.ts +4 -0
  123. package/src/hooks/useTabOffset.ts +8 -0
  124. package/src/index.tsx +18 -8
  125. package/src/navigator/createReanimatedTopTabNavigator.tsx +8 -32
  126. package/src/services/Warning.service.ts +11 -0
  127. package/src/types.ts +23 -19
  128. package/lib/commonjs/context/TabContext.hooks.js +0 -48
  129. package/lib/commonjs/context/TabContext.hooks.js.map +0 -1
  130. package/lib/commonjs/context/TabContext.js +0 -86
  131. package/lib/commonjs/context/TabContext.js.map +0 -1
  132. package/lib/commonjs/hooks/Tab.hooks.js.map +0 -1
  133. package/lib/module/context/TabContext.hooks.js +0 -44
  134. package/lib/module/context/TabContext.hooks.js.map +0 -1
  135. package/lib/module/context/TabContext.js +0 -82
  136. package/lib/module/context/TabContext.js.map +0 -1
  137. package/lib/module/hooks/Tab.hooks.js +0 -121
  138. package/lib/module/hooks/Tab.hooks.js.map +0 -1
  139. package/lib/typescript/commonjs/src/components/ReanimatedTopTab/AnimationHelper.d.ts.map +0 -1
  140. package/lib/typescript/commonjs/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts.map +0 -1
  141. package/lib/typescript/commonjs/src/components/ReanimatedTopTab/types.d.ts.map +0 -1
  142. package/lib/typescript/commonjs/src/components/elements/TabBarBaseComponent.d.ts.map +0 -1
  143. package/lib/typescript/commonjs/src/components/elements/TabBarLabelBaseComponent.d.ts.map +0 -1
  144. package/lib/typescript/commonjs/src/components/wrappers/GestureWrapper.d.ts +0 -2
  145. package/lib/typescript/commonjs/src/components/wrappers/GestureWrapper.d.ts.map +0 -1
  146. package/lib/typescript/commonjs/src/components/wrappers/ScreenWrapper.d.ts.map +0 -1
  147. package/lib/typescript/commonjs/src/components/wrappers/ScrollView.d.ts +0 -6
  148. package/lib/typescript/commonjs/src/components/wrappers/ScrollView.d.ts.map +0 -1
  149. package/lib/typescript/commonjs/src/context/TabContext.d.ts.map +0 -1
  150. package/lib/typescript/commonjs/src/context/TabContext.hooks.d.ts +0 -5
  151. package/lib/typescript/commonjs/src/context/TabContext.hooks.d.ts.map +0 -1
  152. package/lib/typescript/commonjs/src/hooks/Tab.hooks.d.ts +0 -33
  153. package/lib/typescript/commonjs/src/hooks/Tab.hooks.d.ts.map +0 -1
  154. package/lib/typescript/commonjs/src/index.d.ts.map +0 -1
  155. package/lib/typescript/commonjs/src/navigator/createReanimatedTopTabNavigator.d.ts.map +0 -1
  156. package/lib/typescript/commonjs/src/types.d.ts +0 -56
  157. package/lib/typescript/commonjs/src/types.d.ts.map +0 -1
  158. package/lib/typescript/module/src/components/ReanimatedTopTab/AnimationHelper.d.ts +0 -11
  159. package/lib/typescript/module/src/components/ReanimatedTopTab/AnimationHelper.d.ts.map +0 -1
  160. package/lib/typescript/module/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts +0 -14
  161. package/lib/typescript/module/src/components/ReanimatedTopTab/ReanimatedTabView.d.ts.map +0 -1
  162. package/lib/typescript/module/src/components/ReanimatedTopTab/types.d.ts +0 -30
  163. package/lib/typescript/module/src/components/ReanimatedTopTab/types.d.ts.map +0 -1
  164. package/lib/typescript/module/src/components/elements/TabBarBaseComponent.d.ts +0 -23
  165. package/lib/typescript/module/src/components/elements/TabBarBaseComponent.d.ts.map +0 -1
  166. package/lib/typescript/module/src/components/elements/TabBarLabelBaseComponent.d.ts +0 -7
  167. package/lib/typescript/module/src/components/elements/TabBarLabelBaseComponent.d.ts.map +0 -1
  168. package/lib/typescript/module/src/components/wrappers/GestureWrapper.d.ts +0 -2
  169. package/lib/typescript/module/src/components/wrappers/GestureWrapper.d.ts.map +0 -1
  170. package/lib/typescript/module/src/components/wrappers/ScreenWrapper.d.ts +0 -6
  171. package/lib/typescript/module/src/components/wrappers/ScreenWrapper.d.ts.map +0 -1
  172. package/lib/typescript/module/src/components/wrappers/ScrollView.d.ts +0 -6
  173. package/lib/typescript/module/src/components/wrappers/ScrollView.d.ts.map +0 -1
  174. package/lib/typescript/module/src/context/TabContext.d.ts +0 -4
  175. package/lib/typescript/module/src/context/TabContext.d.ts.map +0 -1
  176. package/lib/typescript/module/src/context/TabContext.hooks.d.ts +0 -5
  177. package/lib/typescript/module/src/context/TabContext.hooks.d.ts.map +0 -1
  178. package/lib/typescript/module/src/hooks/Tab.hooks.d.ts +0 -33
  179. package/lib/typescript/module/src/hooks/Tab.hooks.d.ts.map +0 -1
  180. package/lib/typescript/module/src/index.d.ts +0 -35
  181. package/lib/typescript/module/src/index.d.ts.map +0 -1
  182. package/lib/typescript/module/src/navigator/createReanimatedTopTabNavigator.d.ts +0 -4
  183. package/lib/typescript/module/src/navigator/createReanimatedTopTabNavigator.d.ts.map +0 -1
  184. package/lib/typescript/module/src/types.d.ts.map +0 -1
  185. package/src/context/TabContext.hooks.ts +0 -55
  186. package/src/context/TabContext.tsx +0 -115
  187. package/src/hooks/Tab.hooks.ts +0 -127
  188. /package/lib/{typescript/commonjs → commonjs}/package.json +0 -0
  189. /package/lib/{typescript/module → module}/package.json +0 -0
  190. /package/lib/typescript/{commonjs/src → src}/components/ReanimatedTopTab/AnimationHelper.d.ts +0 -0
  191. /package/lib/typescript/{commonjs/src → src}/components/ReanimatedTopTab/ReanimatedTabView.d.ts +0 -0
  192. /package/lib/typescript/{commonjs/src → src}/components/ReanimatedTopTab/types.d.ts +0 -0
  193. /package/lib/typescript/{commonjs/src → src}/components/elements/TabBarBaseComponent.d.ts +0 -0
  194. /package/lib/typescript/{commonjs/src → src}/components/elements/TabBarLabelBaseComponent.d.ts +0 -0
  195. /package/lib/typescript/{commonjs/src → src}/components/wrappers/ScreenWrapper.d.ts +0 -0
  196. /package/lib/typescript/{commonjs/src → src}/navigator/createReanimatedTopTabNavigator.d.ts +0 -0
@@ -5,46 +5,60 @@ import {
5
5
  } from 'react-native';
6
6
  import { GestureDetector } from 'react-native-gesture-handler';
7
7
  import Reanimated, { type AnimatedProps } from 'react-native-reanimated';
8
- import {
9
- useScreenGesture,
10
- useScreenProperties,
11
- useScreenScrollable,
12
- useTabContext,
13
- } from '../../hooks/Tab.hooks';
14
8
  import { useRef } from 'react';
15
9
  import { useRoute } from '@react-navigation/native';
10
+ import { useTabContext } from '../../hooks/useTabContext';
11
+ import { useScreenGesture } from '../../hooks/useScreenGesture';
12
+ import { useScreenScrollable } from '../../hooks/useScreenScrollable';
13
+ import { useScreenProperties } from '../../hooks/useScreenProperties';
16
14
 
17
- export interface ScreenWrapperProps
18
- extends AnimatedProps<Omit<ScrollViewProps, 'onScroll'>> {}
15
+ export type ScreenWrapperProps = AnimatedProps<
16
+ Omit<ScrollViewProps, 'onScroll' | 'onLayout' | 'bounces'>
17
+ > &
18
+ Pick<ScrollViewProps, 'onLayout'> & {};
19
19
 
20
20
  export const ScrollView = ({
21
21
  children,
22
22
  style,
23
+ contentContainerStyle,
24
+ onLayout,
23
25
  ...props
24
26
  }: ScreenWrapperProps) => {
25
27
  const gesture = useScreenGesture();
26
- const { style: animatedStyle, ...scrollProps } = useScreenScrollable();
28
+ const {
29
+ style: _style,
30
+ contentContainerStyle: _contentContainerStyle,
31
+ onScroll,
32
+ bounces,
33
+ animatedProps,
34
+ } = useScreenScrollable();
27
35
  const { innerLayout } = useScreenProperties();
28
- const { screenRefs } = useTabContext();
36
+ const { context } = useTabContext();
29
37
 
30
38
  const { key } = useRoute();
31
-
32
39
  const _ref = useRef<Reanimated.ScrollView>(null);
33
40
 
34
- const onLayout = (event: LayoutChangeEvent) => {
41
+ const _onLayout = (event: LayoutChangeEvent) => {
42
+ onLayout?.(event);
35
43
  innerLayout.value = event.nativeEvent.layout;
36
- screenRefs.setRef(key, _ref);
44
+ context.screen.setRef(key, _ref.current);
37
45
  };
38
46
 
39
47
  return (
40
48
  <GestureDetector gesture={gesture}>
41
49
  <Reanimated.ScrollView
42
- {...scrollProps}
43
- {...props}
44
50
  ref={_ref}
45
- contentContainerStyle={_style.contentContainerStyle}
46
- onLayout={onLayout}
47
- style={[style, animatedStyle]}
51
+ contentContainerStyle={[
52
+ _contentContainerStyle,
53
+ styleSheet.contentContainerStyle,
54
+ contentContainerStyle,
55
+ ]}
56
+ onLayout={_onLayout}
57
+ style={[_style, style]}
58
+ onScroll={onScroll}
59
+ bounces={bounces}
60
+ animatedProps={animatedProps}
61
+ {...props}
48
62
  >
49
63
  {children}
50
64
  </Reanimated.ScrollView>
@@ -52,7 +66,7 @@ export const ScrollView = ({
52
66
  );
53
67
  };
54
68
 
55
- const _style = StyleSheet.create({
69
+ const styleSheet = StyleSheet.create({
56
70
  contentContainerStyle: {
57
71
  flexGrow: 1,
58
72
  },
@@ -0,0 +1,10 @@
1
+ const getTargetIndex = (currentValue: number, previousValue: number | null) => {
2
+ 'worklet';
3
+ if (currentValue > (previousValue ?? 0)) {
4
+ return Math.ceil(currentValue);
5
+ } else {
6
+ return Math.floor(currentValue);
7
+ }
8
+ };
9
+
10
+ export const ContextHelpers = { getTargetIndex };
@@ -0,0 +1,253 @@
1
+ import Reanimated, {
2
+ interpolate,
3
+ makeMutable,
4
+ runOnJS,
5
+ useAnimatedReaction,
6
+ useDerivedValue,
7
+ useSharedValue,
8
+ } from 'react-native-reanimated';
9
+ import { type ReanimatedTopTabNavigation } from '../types';
10
+ import { useRef } from 'react';
11
+ import type {
12
+ ParamListBase,
13
+ TabNavigationState,
14
+ } from '@react-navigation/native';
15
+ import { Gesture } from 'react-native-gesture-handler';
16
+ import { assign } from 'lodash';
17
+ import { ContextHelpers } from './Context.helpers';
18
+
19
+ /**
20
+ * Hook to reset the scroll offset for the approaching screen in a tab navigation context.
21
+ *
22
+ * @param {Object} params - The parameters for configuring the scroll offset reset.
23
+ * @param {ReanimatedTopTabNavigation.ContextType["context"]} params.context - The context containing navigation and screen information.
24
+ * @param {ReanimatedTopTabNavigation.ContextType["transformationX"]} params.transformationX - The horizontal transformation value used in animations.
25
+ * @param {ReanimatedTopTabNavigation.ContextType["currentYPosition"]} params.currentYPosition - The current vertical scroll position.
26
+ *
27
+ * The hook is responsible for resetting the vertical scroll offset of the "approaching" screen within a tabbed navigation interface,
28
+ * ensuring a smooth user experience by resetting the scroll position to the top under certain conditions.
29
+ * When the user swipes across tabs, it checks if the approaching screen has a non-zero scroll offset and resets it
30
+ * if the current screen is at the top position.
31
+ */
32
+ const useResetApproachingScreenScrollOffset = ({
33
+ context,
34
+ transformationX,
35
+ currentYPosition,
36
+ }: Pick<
37
+ ReanimatedTopTabNavigation.ContextType,
38
+ 'context' | 'currentYPosition' | 'transformationX'
39
+ >) => {
40
+ const screenOffsets = useDerivedValue(() =>
41
+ Object.values(context.screen.properties).map(({ scrollY }) => scrollY.value)
42
+ );
43
+
44
+ const resetApproachingScreenOffset = (
45
+ index: number,
46
+ shouldReset: boolean
47
+ ) => {
48
+ if (shouldReset) {
49
+ const { scrollTo } = context.screen.getRef(
50
+ context.route.getKeyForIndex(index)
51
+ ).current ?? { scrollTo: () => {} };
52
+ //NOTE: use reanimated scrollTo does not work
53
+ scrollTo({ y: 0, animated: false });
54
+ }
55
+ };
56
+
57
+ useAnimatedReaction(
58
+ () => transformationX.value,
59
+ (current, previous) => {
60
+ const approachingIndex = ContextHelpers.getTargetIndex(current, previous);
61
+
62
+ const approachingScreenOffset =
63
+ screenOffsets.value[approachingIndex] ?? -1;
64
+
65
+ runOnJS(resetApproachingScreenOffset)(
66
+ approachingIndex,
67
+ approachingScreenOffset > 0 && currentYPosition.value === 0
68
+ );
69
+ }
70
+ );
71
+ };
72
+
73
+ /**
74
+ * Custom hook to prepare a key map for Tab Navigation.
75
+ *
76
+ * @param {Object} params - The parameters for the hook.
77
+ * @param {TabNavigationState<any>} params.state - The navigation state object containing route information.
78
+ * @returns {ReanimatedTopTabNavigation.TopTabContextProps['context']['route']} An object containing:
79
+ * - map: A mapping of tab indices to route keys.
80
+ * - getKeyForIndex: A function that takes an index and returns the corresponding route key.
81
+ * @throws Will throw an error if the key for the provided index does not exist.
82
+ */
83
+ const usePrepareKeyMap = ({
84
+ state,
85
+ }: {
86
+ state: TabNavigationState<any>;
87
+ }): ReanimatedTopTabNavigation.TopTabContextProps['context']['route'] => {
88
+ const indexToKeyMap = useRef(
89
+ state.routes.reduce(
90
+ (previousValue, currentValue, currentIndex) => ({
91
+ ...previousValue,
92
+ [currentIndex]: currentValue.key,
93
+ }),
94
+ {} as Record<number, string>
95
+ )
96
+ );
97
+
98
+ const getKeyForIndex = (index: number) => {
99
+ const key = indexToKeyMap.current[index];
100
+ if (!key) throw '[getKeyForIndex]: Key does not exist';
101
+ return key;
102
+ };
103
+
104
+ return { map: indexToKeyMap.current, getKeyForIndex };
105
+ };
106
+
107
+ /**
108
+ * Prepares screen properties and manages scroll references for each route in the navigation state.
109
+ *
110
+ * @param {Object} params - The parameters for preparing the screen.
111
+ * @param {TabNavigationState<any>} params.state - The navigation state containing route information.
112
+ * @returns {Object} An object containing screen properties, and methods to set and get scroll references.
113
+ * @property {Object} properties - The current screen properties.
114
+ * @property {Function} setRef - Function to set the scroll reference for a specific route.
115
+ * @property {Function} getRef - Function to get the scroll reference of a specific route.
116
+ */
117
+ const usePrepareScreen = ({
118
+ state,
119
+ }: {
120
+ state: TabNavigationState<ParamListBase>;
121
+ }) => {
122
+ const screenProperties = useRef(
123
+ state.routes.reduce(
124
+ (prev, route) => ({
125
+ ...prev,
126
+ [route.key]: {
127
+ innerLayout: makeMutable({
128
+ height: 0,
129
+ width: 0,
130
+ x: 0,
131
+ y: 0,
132
+ }),
133
+ nativeGesture: Gesture.Native(),
134
+ outerLayout: makeMutable({
135
+ height: 0,
136
+ width: 0,
137
+ x: 0,
138
+ y: 0,
139
+ }),
140
+ scrollY: makeMutable(0),
141
+ scrollRef: { current: null },
142
+ },
143
+ }),
144
+ {} as ReanimatedTopTabNavigation.ContextType['context']['screen']['properties']
145
+ )
146
+ );
147
+
148
+ const setRef = (key: string, ref: Reanimated.ScrollView) => {
149
+ const newProperties = assign({}, screenProperties.current[key], {
150
+ scrollRef: { current: ref },
151
+ });
152
+
153
+ screenProperties.current = assign({}, screenProperties.current, {
154
+ [key]: newProperties,
155
+ });
156
+ };
157
+
158
+ const getRef = (key: string) => {
159
+ const _ref = screenProperties.current[key]?.scrollRef;
160
+ if (!_ref) throw '[getRef]: Ref does not exist';
161
+ return _ref;
162
+ };
163
+
164
+ return { properties: screenProperties.current, setRef, getRef };
165
+ };
166
+
167
+ /**
168
+ * Prepares the context required for top tab navigation.
169
+ *
170
+ * This function takes the current state of the navigation
171
+ * and prepares the necessary context properties such as `screen`
172
+ * and `route`, which are then used for navigation purposes.
173
+ *
174
+ * @param {Object} params - The parameters for preparing the context.
175
+ * @param {TabNavigationState<any>} params.state - The current state of the navigation.
176
+ *
177
+ * @returns {ReanimatedTopTabNavigation.TopTabContextProps['context']} The prepared context containing screen and route properties.
178
+ */
179
+ const usePrepareContext = ({
180
+ state,
181
+ }: {
182
+ state: TabNavigationState<ParamListBase>;
183
+ }): ReanimatedTopTabNavigation.TopTabContextProps['context'] => {
184
+ const screen = usePrepareScreen({ state });
185
+ const route = usePrepareKeyMap({ state });
186
+
187
+ return { screen, route };
188
+ };
189
+
190
+ /**
191
+ * Custom hook that handles the transformation of a header component based on tab changes, particularly
192
+ * for animations and configurations involving header behavior. Utilizes shared and derived values to
193
+ * manage the translation of the header on the Y-axis and maintain its position based on the specified
194
+ * configurations.
195
+ *
196
+ * @param {Object} params - The parameters object.
197
+ * @param {Array} params.config - An array of configuration settings to determine the header's transformation.
198
+ * @returns {Object} An object containing shared values for header transformations and positioning:
199
+ * - transformationY: The derived Y-axis transformation value for the header.
200
+ * - transformationX: The shared X-axis transformation value to be observed.
201
+ * - currentYPosition: The shared Y-axis position of the header.
202
+ * - headerHeight: The shared height value of the header.
203
+ */
204
+ const useTransformHeaderOnTabChange = ({
205
+ config,
206
+ }: Pick<ReanimatedTopTabNavigation.ContextType, 'config'>) => {
207
+ const headerHeight = useSharedValue(0);
208
+ const transformationY = useSharedValue(0);
209
+ const transformationX = useSharedValue(0);
210
+ const currentYPosition = useSharedValue(0);
211
+
212
+ //Hide header base on config
213
+ const inputRange = useDerivedValue(
214
+ () => config.map((_, idx) => idx),
215
+ [config]
216
+ );
217
+
218
+ const outputRange = useDerivedValue(
219
+ () =>
220
+ config.map((type) => {
221
+ switch (type) {
222
+ case 'normal':
223
+ return 0;
224
+ case 'minimalized':
225
+ return -headerHeight.value;
226
+ default:
227
+ return 0;
228
+ }
229
+ }),
230
+ [config]
231
+ );
232
+
233
+ useAnimatedReaction(
234
+ () => transformationX.value,
235
+ (value) => {
236
+ if (currentYPosition.value === -headerHeight.value) return;
237
+ transformationY.value = interpolate(
238
+ value,
239
+ inputRange.value,
240
+ outputRange.value,
241
+ 'clamp'
242
+ );
243
+ }
244
+ );
245
+
246
+ return { transformationY, transformationX, currentYPosition, headerHeight };
247
+ };
248
+
249
+ export const ContextHooks = {
250
+ useResetApproachingScreenScrollOffset,
251
+ usePrepareContext,
252
+ useTransformHeaderOnTabChange,
253
+ };
@@ -0,0 +1,69 @@
1
+ import { type ReanimatedTopTabNavigation } from '../types';
2
+ import { createContext, memo, useRef } from 'react';
3
+ import { Gesture } from 'react-native-gesture-handler';
4
+ import { useSharedValue } from 'react-native-reanimated';
5
+ import { ContextHooks } from './Context.hooks';
6
+
7
+ export const Context = createContext<ReanimatedTopTabNavigation.ContextType>(
8
+ {} as ReanimatedTopTabNavigation.ContextType
9
+ );
10
+
11
+ export const Provider = memo(
12
+ ({
13
+ children,
14
+ config,
15
+ context,
16
+ }: ReanimatedTopTabNavigation.TopTabContextProps) => {
17
+ const gestureEnabled = useSharedValue(true);
18
+ const currentScreenIndex = useSharedValue(0);
19
+
20
+ const navHeight = useSharedValue(0);
21
+ const closeOffset = useSharedValue(0);
22
+ const topTabHeight = useSharedValue(0);
23
+
24
+ const topTabNativeGesture = useRef(Gesture.Native()).current;
25
+
26
+ const { transformationX, transformationY, currentYPosition, headerHeight } =
27
+ ContextHooks.useTransformHeaderOnTabChange({ config });
28
+
29
+ ContextHooks.useResetApproachingScreenScrollOffset({
30
+ currentYPosition,
31
+ transformationX,
32
+ context,
33
+ });
34
+
35
+ return (
36
+ <Context.Provider
37
+ value={{
38
+ closeOffset,
39
+ config,
40
+ currentScreenIndex,
41
+ currentYPosition,
42
+ gestureEnabled,
43
+ headerHeight,
44
+ navHeight,
45
+ topTabHeight,
46
+ topTabNativeGesture,
47
+ transformationX,
48
+ transformationY,
49
+ context,
50
+ }}
51
+ >
52
+ {children({
53
+ closeOffset,
54
+ config,
55
+ currentScreenIndex,
56
+ currentYPosition,
57
+ gestureEnabled,
58
+ headerHeight,
59
+ navHeight,
60
+ topTabHeight,
61
+ topTabNativeGesture,
62
+ transformationX,
63
+ transformationY,
64
+ context,
65
+ })}
66
+ </Context.Provider>
67
+ );
68
+ }
69
+ );
@@ -0,0 +1,30 @@
1
+ import { useTabContext } from './useTabContext';
2
+ import {
3
+ interpolate,
4
+ useAnimatedStyle,
5
+ withTiming,
6
+ } from 'react-native-reanimated';
7
+
8
+ export const useHeader = () => {
9
+ const { headerHeight, transformationX, transformationY } = useTabContext();
10
+
11
+ const hideHeader = () => {
12
+ transformationX.value = withTiming(-headerHeight.value);
13
+ };
14
+
15
+ const defaultStyle = useAnimatedStyle(() => ({
16
+ transform: [
17
+ {
18
+ translateY: interpolate(
19
+ transformationY.value,
20
+ [0, -headerHeight.value],
21
+ [0, -headerHeight.value],
22
+ 'clamp'
23
+ ),
24
+ },
25
+ ],
26
+ zIndex: -1,
27
+ }));
28
+
29
+ return { hideHeader, defaultStyle };
30
+ };
@@ -0,0 +1,13 @@
1
+ import { useRoute } from '@react-navigation/native';
2
+ import { useTabContext } from './useTabContext';
3
+
4
+ export const useScreenGesture = () => {
5
+ const { key } = useRoute();
6
+ const { context } = useTabContext();
7
+
8
+ const nativeRef = context.screen.properties[key]?.nativeGesture;
9
+
10
+ if (!nativeRef) throw 'GestureRef does not exist for this route';
11
+
12
+ return nativeRef;
13
+ };
@@ -0,0 +1,11 @@
1
+ import { useRoute } from '@react-navigation/native';
2
+ import { useTabContext } from './useTabContext';
3
+
4
+ export const useScreenProperties = () => {
5
+ const { key } = useRoute();
6
+ const { context } = useTabContext();
7
+ const screen = context.screen.properties[key];
8
+ if (!screen) throw 'No screen properties registered for this route';
9
+
10
+ return screen;
11
+ };
@@ -0,0 +1,69 @@
1
+ import { useRoute } from '@react-navigation/native';
2
+ import { useWindowDimensions } from 'react-native';
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
+ import { useTabContext } from './useTabContext';
5
+ import * as React from 'react';
6
+ import { HeaderHeightContext } from '@react-navigation/elements';
7
+ import {
8
+ useAnimatedProps,
9
+ useAnimatedScrollHandler,
10
+ useAnimatedStyle,
11
+ useDerivedValue,
12
+ } from 'react-native-reanimated';
13
+
14
+ import { useScreenProperties } from './useScreenProperties';
15
+
16
+ export const useScreenScrollable = () => {
17
+ const { key } = useRoute();
18
+ const { height } = useWindowDimensions();
19
+ const { bottom, top } = useSafeAreaInsets();
20
+ const {
21
+ currentYPosition,
22
+ gestureEnabled,
23
+ headerHeight,
24
+ context,
25
+ topTabHeight,
26
+ transformationY,
27
+ } = useTabContext();
28
+
29
+ const screen = useScreenProperties();
30
+
31
+ const navigationHeader = React.useContext(HeaderHeightContext) ?? top;
32
+
33
+ const animatedProps = useAnimatedProps(() => ({
34
+ scrollEnabled:
35
+ !gestureEnabled.value || currentYPosition.value === -headerHeight.value,
36
+ }));
37
+
38
+ const onScroll = useAnimatedScrollHandler(({ contentOffset }) => {
39
+ const scrollPosition = context.screen.properties[key]?.scrollY;
40
+ if (!scrollPosition)
41
+ throw 'ScrollPosition property does not exist for this route';
42
+ scrollPosition.value = contentOffset.y;
43
+ });
44
+
45
+ const oppositeHeaderState = useDerivedValue(() =>
46
+ transformationY.value >= 0 ? headerHeight.value : 0
47
+ );
48
+
49
+ const difference = useDerivedValue(() =>
50
+ Math.abs(screen.outerLayout.value.height - screen.innerLayout.value.height)
51
+ );
52
+
53
+ const style = useAnimatedStyle(() => ({
54
+ minHeight:
55
+ height -
56
+ topTabHeight.value -
57
+ oppositeHeaderState.value -
58
+ navigationHeader -
59
+ difference.value,
60
+ }));
61
+
62
+ return {
63
+ animatedProps,
64
+ bounces: false,
65
+ contentContainerStyle: { paddingBottom: bottom },
66
+ onScroll,
67
+ style,
68
+ };
69
+ };
@@ -0,0 +1,4 @@
1
+ import { useContext } from 'react';
2
+ import { Context } from '../context/Context';
3
+
4
+ export const useTabContext = () => useContext(Context);
@@ -0,0 +1,8 @@
1
+ // returns read-only property
2
+ import { useTabContext } from './useTabContext';
3
+ import { useDerivedValue } from 'react-native-reanimated';
4
+
5
+ export const useTabOffset = () => {
6
+ const { transformationX } = useTabContext();
7
+ return useDerivedValue(() => transformationX.value);
8
+ };
package/src/index.tsx CHANGED
@@ -2,23 +2,33 @@ import { ScrollView } from './components/wrappers/ScrollView';
2
2
  import { ScreenWrapper } from './components/wrappers/ScreenWrapper';
3
3
  import { TabBarBaseComponent } from './components/elements/TabBarBaseComponent';
4
4
  import { TabBarLabelBaseComponent } from './components/elements/TabBarLabelBaseComponent';
5
- import {
6
- useHeader,
7
- useScreenGesture,
8
- useScreenProperties,
9
- useScreenScrollable,
10
- useTabContext,
11
- } from './hooks/Tab.hooks';
5
+ import { useEffect } from 'react';
6
+ import { WarningService } from './services/Warning.service';
7
+ import { useTabContext } from './hooks/useTabContext';
8
+ import { useTabOffset } from './hooks/useTabOffset';
9
+ import { useScreenGesture } from './hooks/useScreenGesture';
10
+ import { useScreenScrollable } from './hooks/useScreenScrollable';
11
+ import { useScreenProperties } from './hooks/useScreenProperties';
12
+ import { useHeader } from './hooks/useHeader';
12
13
 
13
14
  export { type ReanimatedTopTabNavigation } from './types';
14
15
  export { createReanimatedTopTabNavigator } from './navigator/createReanimatedTopTabNavigator';
15
16
 
17
+ const _useTabContext = () => {
18
+ useEffect(() => {
19
+ WarningService.warn();
20
+ }, []);
21
+
22
+ return useTabContext();
23
+ };
24
+
16
25
  export const TabHooks = {
17
26
  useHeader,
18
27
  useScreenProperties,
19
28
  useScreenScrollable,
20
29
  useScreenGesture,
21
- useTabContext,
30
+ useTabContext: _useTabContext,
31
+ useTabOffset,
22
32
  };
23
33
 
24
34
  export const Tab = {
@@ -12,17 +12,14 @@ import {
12
12
  type RenderTabsParams,
13
13
  TabBarBaseComponent,
14
14
  } from '../components/elements/TabBarBaseComponent';
15
- import { Provider } from '../context/TabContext';
15
+ import { Provider } from '../context/Context';
16
16
  import { type ReanimatedTopTabNavigation } from '../types';
17
17
  import { omit } from 'lodash';
18
- import { useCallback, useMemo, useRef } from 'react';
19
- import { Gesture } from 'react-native-gesture-handler';
20
- import Reanimated, {
21
- makeMutable,
22
- type SharedValue,
23
- } from 'react-native-reanimated';
18
+ import { useCallback, useMemo } from 'react';
19
+ import Reanimated, { type SharedValue } from 'react-native-reanimated';
24
20
  import type { ReanimatedTabViewTypes } from '../components/ReanimatedTopTab/types';
25
21
  import { ReanimatedTabView } from '../components/ReanimatedTopTab/ReanimatedTabView';
22
+ import { ContextHooks } from '../context/Context.hooks';
26
23
 
27
24
  const TabViewNavigator = ({
28
25
  HeaderComponent,
@@ -51,31 +48,10 @@ const TabViewNavigator = ({
51
48
  if (!config) {
52
49
  _config = state.routes.map(() => 'normal');
53
50
  }
54
- const screenProperties = useRef(
55
- state.routes.reduce(
56
- (prev, route) => ({
57
- ...prev,
58
- [route.key]: {
59
- innerLayout: makeMutable({
60
- height: 0,
61
- width: 0,
62
- x: 0,
63
- y: 0,
64
- }),
65
- nativeGesture: Gesture.Native(),
66
- outerLayout: makeMutable({
67
- height: 0,
68
- width: 0,
69
- x: 0,
70
- y: 0,
71
- }),
72
- scrollY: makeMutable(0),
73
- },
74
- }),
75
- {} as ReanimatedTopTabNavigation.ContextType['screenProperties']
76
- )
77
- ).current;
78
51
 
52
+ const context = ContextHooks.usePrepareContext({ state });
53
+
54
+ //NOTE: all belows reefers to Tabs component directly. Consider to move it somewhere
79
55
  const onIndexChange =
80
56
  (
81
57
  currentScreenIndex: SharedValue<number>,
@@ -142,7 +118,7 @@ const TabViewNavigator = ({
142
118
  descriptors[route.key]?.render();
143
119
 
144
120
  return (
145
- <Provider config={_config} screenProperties={screenProperties}>
121
+ <Provider config={_config} context={context}>
146
122
  {({
147
123
  currentScreenIndex,
148
124
  gestureEnabled,
@@ -0,0 +1,11 @@
1
+ export const WarningService = {
2
+ hasWarned: false,
3
+ warn() {
4
+ if (__DEV__ && !this.hasWarned) {
5
+ console.warn(
6
+ '[react-navigation-reanimated-top-tabs]: You are directly using the tab context, which may lead to unexpected behavior. Please use the provided hooks or API for better stability.'
7
+ );
8
+ this.hasWarned = true;
9
+ }
10
+ },
11
+ };