@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 +2 -3
- package/{README.md → readme.md} +27 -39
- package/src/stack.tsx +27 -32
- package/src/tabs.tsx +0 -5
- package/src/utils.ts +3 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rn-tools/navigation",
|
|
3
|
-
"version": "2.
|
|
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": "
|
|
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": {
|
package/{README.md → readme.md}
RENAMED
|
@@ -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
|
-
{
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
</Stack.
|
|
346
|
-
|
|
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
|
|
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
|
|
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" &&
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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;
|