@rn-tools/navigation 2.2.5 → 2.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rn-tools/navigation",
3
- "version": "2.2.5",
3
+ "version": "2.3.0",
4
4
  "main": "./src/index.ts",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -22,7 +22,7 @@
22
22
  "@typescript-eslint/parser": "^6.8.0",
23
23
  "babel-preset-expo": "~11.0.0",
24
24
  "eslint": "^8.56.0",
25
- "expo": "^51.0.8",
25
+ "expo": "~52.0.0",
26
26
  "jest": "^29.7.0",
27
27
  "jest-expo": "^51.0.2",
28
28
  "lint-staged": "^15.2.0",
@@ -33,7 +33,6 @@
33
33
  "peerDependencies": {
34
34
  "react": "*",
35
35
  "react-native": "*",
36
- "react-native-safe-area-context": "*",
37
36
  "react-native-screens": "*"
38
37
  },
39
38
  "dependencies": {
@@ -29,12 +29,6 @@ A set of useful navigation components for React Native. Built with `react-native
29
29
  yarn expo install @rn-tools/navigation react-native-screens
30
30
  ```
31
31
 
32
- **Note:** It's recommended that you install and wrap your app in a `SafeAreaProvider` to ensure components are rendered correctly based on the device's insets:
33
-
34
- ```bash
35
- yarn expo install react-native-safe-area-context
36
- ```
37
-
38
32
  ## Basic Usage
39
33
 
40
34
  For basic usage, the exported `Stack.Navigator` and `Tabs.Navigator` will get you up and running quickly.
@@ -117,14 +111,10 @@ import { Tabs, navigation, Stack } from "@rn-tools/navigation";
117
111
  import * as React from "react";
118
112
  import { View, Text, Button } from "react-native";
119
113
 
120
- // It's recommended to wrap your App in a SafeAreaProvider once
121
- import { SafeAreaProvider } from "react-native-safe-area-context";
122
114
 
123
115
  export function BasicTabs() {
124
116
  return (
125
- <SafeAreaProvider>
126
117
  <Stack.Navigator rootScreen={<MyTabs />} />
127
- </SafeAreaProvider>
128
118
  );
129
119
  }
130
120
 
@@ -301,16 +291,12 @@ function switchMainTabsToTab(tabIndex: number) {
301
291
 
302
292
  ### Rendering a header
303
293
 
304
- Use the `Stack.Header` component to render a native header in a screen.
294
+ Use the `Stack.Header` component to render a native header in a screen by passing it as a prop to `Stack.Screen`.
305
295
 
306
296
  Under the hood this is using `react-native-screens` header - [here is a reference for the available props](https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screenstackheaderconfig)
307
297
 
308
298
  You can provide custom left, center, and right views in the header by using the `Stack.HeaderLeft`, `Stack.HeaderCenter`, and `Stack.HeaderRight` view container components as children of `Stack.Header`.
309
299
 
310
- **Note:** Wrap your App in a `SafeAreaProvider` to ensure your screen components are rendered correctly with the header
311
-
312
- **Note:**: The header component **has to be the first child** of a `Stack.Screen` component.
313
-
314
300
  ```tsx
315
301
  import { navigation, Stack } from "@rn-tools/navigation";
316
302
  import * as React from "react";
@@ -331,20 +317,20 @@ function MyScreenWithHeader() {
331
317
  let [title, setTitle] = React.useState("");
332
318
 
333
319
  return (
334
- <Stack.Screen>
335
- {/* Header must be the first child */}
336
- <Stack.Header
337
- title={title}
338
- // Some potentially useful props - see the reference posted above for all available props
339
- backTitle="Custom back title"
340
- backTitleFontSize={16}
341
- hideBackButton={false}
342
- >
343
- <Stack.HeaderRight>
344
- <Text>Custom right text!</Text>
345
- </Stack.HeaderRight>
346
- </Stack.Header>
347
-
320
+ <Stack.Screen
321
+ header={
322
+ <Stack.Header
323
+ title={title}
324
+ backTitle="Custom back title"
325
+ backTitleFontSize={16}
326
+ hideBackButton={false}
327
+ >
328
+ <Stack.HeaderRight>
329
+ <Text>Custom right text!</Text>
330
+ </Stack.HeaderRight>
331
+ </Stack.Header>
332
+ }
333
+ >
348
334
  <View
349
335
  style={{
350
336
  flex: 1,
@@ -751,17 +737,19 @@ function MyScreen() {
751
737
  preventNativeDismiss={!canGoBack}
752
738
  nativeBackButtonDismissalEnabled={!canGoBack}
753
739
  gestureEnabled={canGoBack}
740
+ header={
741
+ <Stack.Header title="Prevent going back">
742
+ <Stack.HeaderLeft>
743
+ <TouchableOpacity
744
+ onPress={onPressBackButton}
745
+ style={{ opacity: canGoBack ? 1 : 0.4 }}
746
+ >
747
+ <Text>Back</Text>
748
+ </TouchableOpacity>
749
+ </Stack.HeaderLeft>
750
+ </Stack.Header>
751
+ }
754
752
  >
755
- <Stack.Header title="Prevent going back">
756
- <Stack.HeaderLeft>
757
- <TouchableOpacity
758
- onPress={onPressBackButton}
759
- style={{ opacity: canGoBack ? 1 : 0.4 }}
760
- >
761
- <Text>Back</Text>
762
- </TouchableOpacity>
763
- </Stack.HeaderLeft>
764
- </Stack.Header>
765
753
  <View style={{ paddingVertical: 48, paddingHorizontal: 16, gap: 16 }}>
766
754
  <Text style={{ fontSize: 22, fontWeight: "medium" }}>
767
755
  Enter some text and try to go back
package/src/stack.tsx CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  import { DEFAULT_SLOT_NAME } from "./navigation-reducer";
32
32
  import { useNavigationDispatch, useNavigationState } from "./navigation-store";
33
33
  import type { StackItem } from "./types";
34
- import { generateStackId, useSafeAreaInsetsSafe } from "./utils";
34
+ import { generateStackId } from "./utils";
35
35
 
36
36
  let StackIdContext = React.createContext<string>("");
37
37
  let ScreenIdContext = React.createContext<string>("");
@@ -39,7 +39,7 @@ let ScreenIdContext = React.createContext<string>("");
39
39
  // Component returned from `react-native-screens` references `react-navigation` data structures in recent updates
40
40
  // This is a workaround to make it work with our custom navigation
41
41
  let RNScreenStack = React.memo(function RNScreenStack(
42
- props: RNScreenStackProps
42
+ props: RNScreenStackProps,
43
43
  ) {
44
44
  let { children, gestureDetectorBridge, ...rest } = props;
45
45
  let ref = React.useRef(null);
@@ -64,7 +64,7 @@ export type StackRootProps = {
64
64
 
65
65
  let useStackInternal = (stackId = "") => {
66
66
  let stack: StackItem | undefined = useNavigationState(
67
- (state) => state.stacks.lookup[stackId]
67
+ (state) => state.stacks.lookup[stackId],
68
68
  );
69
69
  return stack;
70
70
  };
@@ -132,7 +132,7 @@ export type StackScreensProps = RNScreenStackProps;
132
132
  function StackScreens({ style: styleProp, ...props }: StackScreensProps) {
133
133
  let style = React.useMemo(
134
134
  () => styleProp || StyleSheet.absoluteFill,
135
- [styleProp]
135
+ [styleProp],
136
136
  );
137
137
 
138
138
  return <RNScreenStack {...props} style={style} />;
@@ -143,7 +143,9 @@ let defaultScreenStyle: ViewStyle = {
143
143
  backgroundColor: "white",
144
144
  };
145
145
 
146
- export type StackScreenProps = RNScreenProps;
146
+ export type StackScreenProps = RNScreenProps & {
147
+ header?: React.ReactElement<StackScreenHeaderProps>;
148
+ };
147
149
 
148
150
  let HeaderHeightContext = React.createContext<number>(0);
149
151
 
@@ -153,6 +155,7 @@ let StackScreen = React.memo(function StackScreen({
153
155
  gestureEnabled = true,
154
156
  onDismissed: onDismissedProp,
155
157
  onHeaderHeightChange: onHeaderHeightChangeProp,
158
+ header,
156
159
  ...props
157
160
  }: StackScreenProps) {
158
161
  let stackId = React.useContext(StackIdContext);
@@ -168,7 +171,7 @@ let StackScreen = React.memo(function StackScreen({
168
171
  dispatch({ type: "POP_SCREEN_BY_KEY", key: screenId });
169
172
  onDismissedProp?.(e);
170
173
  },
171
- [onDismissedProp, dispatch, screenId]
174
+ [onDismissedProp, dispatch, screenId],
172
175
  );
173
176
 
174
177
  React.useEffect(() => {
@@ -182,36 +185,41 @@ let StackScreen = React.memo(function StackScreen({
182
185
  }
183
186
 
184
187
  BackHandler.addEventListener("hardwareBackPress", backHandler);
185
-
186
- return () => {
187
- BackHandler.removeEventListener("hardwareBackPress", backHandler);
188
- };
189
188
  }, [gestureEnabled, stack, screenId, isActive, dispatch]);
190
189
 
191
- let style = React.useMemo(() => styleProp || defaultScreenStyle, [styleProp]);
192
-
193
- let [headerHeight, setHeaderHeight] = React.useState(0);
190
+ let parentHeaderHeight = React.useContext(HeaderHeightContext);
191
+ let [headerHeight, setHeaderHeight] = React.useState(parentHeaderHeight);
194
192
 
195
193
  let onHeaderHeightChange: ScreenProps["onHeaderHeightChange"] =
196
194
  React.useCallback(
197
195
  (e) => {
198
- Platform.OS === "ios" && setHeaderHeight(e.nativeEvent.headerHeight);
196
+ Platform.OS === "ios" &&
197
+ e.nativeEvent.headerHeight > 0 &&
198
+ setHeaderHeight(e.nativeEvent.headerHeight);
199
199
  onHeaderHeightChangeProp?.(e);
200
200
  },
201
- [onHeaderHeightChangeProp]
201
+ [onHeaderHeightChangeProp],
202
202
  );
203
203
 
204
+ let style = React.useMemo(
205
+ () => [
206
+ defaultScreenStyle,
207
+ { paddingTop: headerHeight || parentHeaderHeight },
208
+ styleProp,
209
+ ],
210
+ [styleProp, headerHeight, parentHeaderHeight],
211
+ );
212
+
204
213
  return (
205
214
  <HeaderHeightContext.Provider value={headerHeight}>
206
- {/* @ts-expect-error - Ref typings in RNScreens */}
207
215
  <RNScreen
208
216
  {...props}
209
217
  style={style}
210
- activityState={isActive ? 2 : 0}
211
218
  gestureEnabled={gestureEnabled}
212
219
  onDismissed={onDismissed}
213
220
  onHeaderHeightChange={onHeaderHeightChange}
214
221
  >
222
+ {header}
215
223
  {children}
216
224
  </RNScreen>
217
225
  </HeaderHeightContext.Provider>
@@ -255,20 +263,7 @@ export type StackScreenHeaderProps = RNScreenStackHeaderConfigProps;
255
263
  let StackScreenHeader = React.memo(function StackScreenHeader({
256
264
  ...props
257
265
  }: StackScreenHeaderProps) {
258
- let headerHeight = React.useContext(HeaderHeightContext);
259
-
260
- let placeholderStyle = React.useMemo<ViewStyle>(() => {
261
- return {
262
- height: headerHeight,
263
- };
264
- }, [headerHeight]);
265
-
266
- return (
267
- <React.Fragment>
268
- <RNScreenStackHeaderConfig {...props} />
269
- <View style={placeholderStyle} />
270
- </React.Fragment>
271
- );
266
+ return <RNScreenStackHeaderConfig {...props} />;
272
267
  });
273
268
 
274
269
  let StackScreenHeaderLeft = React.memo(function StackScreenHeaderLeft({
@@ -292,7 +287,7 @@ let StackScreenHeaderRight = React.memo(function StackScreenHeaderRight({
292
287
  let ScreenStackHeaderBackButtonImage = React.memo(
293
288
  function ScreenStackHeaderBackButtonImage(props: ImageProps) {
294
289
  return <RNScreenStackHeaderBackButtonImage {...props} />;
295
- }
290
+ },
296
291
  );
297
292
 
298
293
  export type StackNavigatorProps = Omit<StackRootProps, "children"> & {
package/src/tabs.tsx CHANGED
@@ -141,10 +141,6 @@ let TabsScreen = React.memo(function TabsScreen({
141
141
  }
142
142
 
143
143
  BackHandler.addEventListener("hardwareBackPress", backHandler);
144
-
145
- return () => {
146
- BackHandler.removeEventListener("hardwareBackPress", backHandler);
147
- };
148
144
  }, [tabId, dispatch, getNavigationStore]);
149
145
 
150
146
  let style = React.useMemo(
@@ -153,7 +149,6 @@ let TabsScreen = React.memo(function TabsScreen({
153
149
  );
154
150
 
155
151
  return (
156
- // @ts-expect-error - Ref typings in RNScreens
157
152
  <RNScreen
158
153
  active={isActive ? 1 : 0}
159
154
  activityState={isActive ? 2 : 0}
package/src/utils.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Platform } from 'react-native';
2
- import { useSafeAreaInsets, type EdgeInsets } from 'react-native-safe-area-context'
3
2
 
4
3
  export let generateStackId = createIdGenerator("stack");
5
4
  export let generateScreenId = createIdGenerator("screen");
@@ -19,7 +18,7 @@ export let serializeTabIndexKey = (tabId: string, index: number) =>
19
18
 
20
19
 
21
20
 
22
- let baseInsets: EdgeInsets = {
21
+ let baseInsets = {
23
22
  top: Platform.OS === "ios" ? 59 : 49,
24
23
  bottom: Platform.OS === "ios" ? 34 : 0,
25
24
  right: 0,
@@ -32,9 +31,9 @@ export function useSafeAreaInsetsSafe() {
32
31
  try {
33
32
  // Linter thinks this is conditional but it seems fine
34
33
  // eslint-disable-next-line
35
- insets = useSafeAreaInsets();
34
+ insets = baseInsets;
36
35
  } catch (error) {
37
- console.warn("`react-native-safe-area-context` missing - Please install and wrap your app in a SafeAreaProvider" );
36
+ console.warn("`react-native-safe-area-context` missing - Please install and wrap your app in a SafeAreaProvider");
38
37
  }
39
38
 
40
39
  return insets;