@rn-tools/navigation 2.2.4 → 2.2.6
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/README.md +62 -48
- package/package.json +4 -1
- package/src/__tests__/navigation-reducer.test.tsx +0 -2
- package/src/__tests__/stack.test.tsx +48 -0
- package/src/navigation.tsx +9 -2
- package/src/stack.tsx +53 -80
- package/src/tabs.tsx +9 -13
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ A set of useful navigation components for React Native. Built with `react-native
|
|
|
13
13
|
- [Pushing a screen once](#pushing-a-screen-once)
|
|
14
14
|
- [Targeting specific tabs](#targeting-specific-tabs)
|
|
15
15
|
- [Rendering a header](#rendering-a-header)
|
|
16
|
+
- [Configuring screen props](#configuring-screen-props)
|
|
16
17
|
- [Components](#components)
|
|
17
18
|
- [Stack](#stack)
|
|
18
19
|
- [Tabs](#tabs)
|
|
@@ -20,6 +21,7 @@ A set of useful navigation components for React Native. Built with `react-native
|
|
|
20
21
|
- [Authentication](#authentication)
|
|
21
22
|
- [Deep Links](#deep-links)
|
|
22
23
|
- [Preventing going back](#preventing-going-back)
|
|
24
|
+
- [Testing](#testing)
|
|
23
25
|
|
|
24
26
|
## Installation
|
|
25
27
|
|
|
@@ -299,16 +301,12 @@ function switchMainTabsToTab(tabIndex: number) {
|
|
|
299
301
|
|
|
300
302
|
### Rendering a header
|
|
301
303
|
|
|
302
|
-
Use the `Stack.Header` component to render a native header in a screen.
|
|
304
|
+
Use the `Stack.Header` component to render a native header in a screen by passing it as a prop to `Stack.Screen`.
|
|
303
305
|
|
|
304
306
|
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)
|
|
305
307
|
|
|
306
308
|
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`.
|
|
307
309
|
|
|
308
|
-
**Note:** Wrap your App in a `SafeAreaProvider` to ensure your screen components are rendered correctly with the header
|
|
309
|
-
|
|
310
|
-
**Note:**: The header component **has to be the first child** of a `Stack.Screen` component.
|
|
311
|
-
|
|
312
310
|
```tsx
|
|
313
311
|
import { navigation, Stack } from "@rn-tools/navigation";
|
|
314
312
|
import * as React from "react";
|
|
@@ -329,20 +327,20 @@ function MyScreenWithHeader() {
|
|
|
329
327
|
let [title, setTitle] = React.useState("");
|
|
330
328
|
|
|
331
329
|
return (
|
|
332
|
-
<Stack.Screen
|
|
333
|
-
{
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
</Stack.
|
|
344
|
-
|
|
345
|
-
|
|
330
|
+
<Stack.Screen
|
|
331
|
+
header={
|
|
332
|
+
<Stack.Header
|
|
333
|
+
title={title}
|
|
334
|
+
backTitle="Custom back title"
|
|
335
|
+
backTitleFontSize={16}
|
|
336
|
+
hideBackButton={false}
|
|
337
|
+
>
|
|
338
|
+
<Stack.HeaderRight>
|
|
339
|
+
<Text>Custom right text!</Text>
|
|
340
|
+
</Stack.HeaderRight>
|
|
341
|
+
</Stack.Header>
|
|
342
|
+
}
|
|
343
|
+
>
|
|
346
344
|
<View
|
|
347
345
|
style={{
|
|
348
346
|
flex: 1,
|
|
@@ -362,6 +360,27 @@ function MyScreenWithHeader() {
|
|
|
362
360
|
}
|
|
363
361
|
```
|
|
364
362
|
|
|
363
|
+
### Configuring screen props
|
|
364
|
+
|
|
365
|
+
The `Stack.Screen` component is a wrapper around the `Screen` component from `react-native-screens`. [Screen props reference](https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx#L101)
|
|
366
|
+
|
|
367
|
+
Some notable props are `stackPresentation`, `stackAnimation`, and `gestureEnabled`, however there are many more available.
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
function pushNativeModalScreen() {
|
|
371
|
+
navigation.pushScreen(
|
|
372
|
+
<Stack.Screen
|
|
373
|
+
stackPresentation="modal"
|
|
374
|
+
stackAnimation="slide_from_bottom"
|
|
375
|
+
gestureEnabled={true}
|
|
376
|
+
>
|
|
377
|
+
{/* If you want to push more screens inside of the modal, wrap it with a Stack */}
|
|
378
|
+
<Stack.Navigator rootScreen={<MyScreen title="Modal screen" />} />
|
|
379
|
+
</Stack.Screen>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
365
384
|
## Components
|
|
366
385
|
|
|
367
386
|
The `Navigator` components in the previous examples are fairly straightforward wrappers around other lower level `Stack` and `Tabs` components.
|
|
@@ -550,7 +569,7 @@ export default function DeepLinksExample() {
|
|
|
550
569
|
// let { path } = Linking.parse(url)
|
|
551
570
|
|
|
552
571
|
// But it's easier to test hardcoded strings for the sake of this example
|
|
553
|
-
let path = "/
|
|
572
|
+
let path = "/home/item/2";
|
|
554
573
|
|
|
555
574
|
return (
|
|
556
575
|
<Stack.Navigator
|
|
@@ -559,23 +578,7 @@ export default function DeepLinksExample() {
|
|
|
559
578
|
path={path}
|
|
560
579
|
handlers={[
|
|
561
580
|
{
|
|
562
|
-
path: "/
|
|
563
|
-
handler: (params: { itemId: string }) => {
|
|
564
|
-
let itemId = params.itemId;
|
|
565
|
-
|
|
566
|
-
// Go to home tab
|
|
567
|
-
navigation.setTabIndex(0);
|
|
568
|
-
|
|
569
|
-
// Push the screen we want
|
|
570
|
-
navigation.pushScreen(
|
|
571
|
-
<Stack.Screen>
|
|
572
|
-
<MyScreen title={`Item: ${itemId}`} />
|
|
573
|
-
</Stack.Screen>
|
|
574
|
-
);
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
path: "/testing/home/item/:itemId",
|
|
581
|
+
path: "/home/item/:itemId",
|
|
579
582
|
handler: (params: { itemId: string }) => {
|
|
580
583
|
let itemId = params.itemId;
|
|
581
584
|
|
|
@@ -688,7 +691,6 @@ function MyScreen({
|
|
|
688
691
|
</View>
|
|
689
692
|
);
|
|
690
693
|
}
|
|
691
|
-
|
|
692
694
|
```
|
|
693
695
|
|
|
694
696
|
### Preventing going back
|
|
@@ -745,17 +747,19 @@ function MyScreen() {
|
|
|
745
747
|
preventNativeDismiss={!canGoBack}
|
|
746
748
|
nativeBackButtonDismissalEnabled={!canGoBack}
|
|
747
749
|
gestureEnabled={canGoBack}
|
|
750
|
+
header={
|
|
751
|
+
<Stack.Header title="Prevent going back">
|
|
752
|
+
<Stack.HeaderLeft>
|
|
753
|
+
<TouchableOpacity
|
|
754
|
+
onPress={onPressBackButton}
|
|
755
|
+
style={{ opacity: canGoBack ? 1 : 0.4 }}
|
|
756
|
+
>
|
|
757
|
+
<Text>Back</Text>
|
|
758
|
+
</TouchableOpacity>
|
|
759
|
+
</Stack.HeaderLeft>
|
|
760
|
+
</Stack.Header>
|
|
761
|
+
}
|
|
748
762
|
>
|
|
749
|
-
<Stack.Header title="Prevent going back">
|
|
750
|
-
<Stack.HeaderLeft>
|
|
751
|
-
<TouchableOpacity
|
|
752
|
-
onPress={onPressBackButton}
|
|
753
|
-
style={{ opacity: canGoBack ? 1 : 0.4 }}
|
|
754
|
-
>
|
|
755
|
-
<Text>Back</Text>
|
|
756
|
-
</TouchableOpacity>
|
|
757
|
-
</Stack.HeaderLeft>
|
|
758
|
-
</Stack.Header>
|
|
759
763
|
<View style={{ paddingVertical: 48, paddingHorizontal: 16, gap: 16 }}>
|
|
760
764
|
<Text style={{ fontSize: 22, fontWeight: "medium" }}>
|
|
761
765
|
Enter some text and try to go back
|
|
@@ -772,3 +776,13 @@ function MyScreen() {
|
|
|
772
776
|
);
|
|
773
777
|
}
|
|
774
778
|
```
|
|
779
|
+
|
|
780
|
+
### Testing
|
|
781
|
+
|
|
782
|
+
Recommended:
|
|
783
|
+
|
|
784
|
+
- Check out the getting started portion of [React Native Testing Library](https://callstack.github.io/react-native-testing-library/docs/start/quick-start)
|
|
785
|
+
|
|
786
|
+
- Set up [Jest Expo](https://docs.expo.dev/develop/unit-testing/)
|
|
787
|
+
|
|
788
|
+
- Reset the navigation state between each test by calling `navigation.reset()`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rn-tools/navigation",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
4
4
|
"main": "./src/index.ts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@rn-tools/eslint-config": "*",
|
|
16
|
+
"@testing-library/react-native": "^12.5.1",
|
|
16
17
|
"@types/jest": "^27.0.3",
|
|
17
18
|
"@types/react": "~18.2.79",
|
|
18
19
|
"@types/react-native": "^0.72.8",
|
|
20
|
+
"@types/react-test-renderer": "^18",
|
|
19
21
|
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
|
20
22
|
"@typescript-eslint/parser": "^6.8.0",
|
|
21
23
|
"babel-preset-expo": "~11.0.0",
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
"jest-expo": "^51.0.2",
|
|
26
28
|
"lint-staged": "^15.2.0",
|
|
27
29
|
"prettier": "^3.2.1",
|
|
30
|
+
"react-test-renderer": "18.2.0",
|
|
28
31
|
"typescript": "~5.3.3"
|
|
29
32
|
},
|
|
30
33
|
"peerDependencies": {
|
|
@@ -218,8 +218,6 @@ describe("reducer", () => {
|
|
|
218
218
|
};
|
|
219
219
|
|
|
220
220
|
let newState = reducer(initialStateWithStack, action, context);
|
|
221
|
-
console.log(newState);
|
|
222
|
-
|
|
223
221
|
expect(newState.screens.ids).not.toContain("screen1");
|
|
224
222
|
expect(newState.stacks.lookup.stack1.screens).not.toContain("screen1");
|
|
225
223
|
expect(newState.screens.ids).toContain("screen2");
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { fireEvent, render } from "@testing-library/react-native";
|
|
2
|
+
import { Button, Text } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { navigation } from "../navigation";
|
|
5
|
+
import { Stack } from "../stack";
|
|
6
|
+
|
|
7
|
+
describe("<Stack />", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
navigation.reset();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("render", () => {
|
|
13
|
+
let { getByText } = render(
|
|
14
|
+
<Stack.Navigator
|
|
15
|
+
rootScreen={
|
|
16
|
+
<>
|
|
17
|
+
<Text>Stack works!</Text>
|
|
18
|
+
</>
|
|
19
|
+
}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
let element = getByText("Stack works!");
|
|
23
|
+
expect(element).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("pushing a screen works", () => {
|
|
27
|
+
let { getByText, queryByText } = render(
|
|
28
|
+
<Stack.Navigator
|
|
29
|
+
rootScreen={
|
|
30
|
+
<Button
|
|
31
|
+
title="Push"
|
|
32
|
+
onPress={() =>
|
|
33
|
+
navigation.pushScreen(
|
|
34
|
+
<Stack.Screen>
|
|
35
|
+
<Text>Pushed screen!</Text>
|
|
36
|
+
</Stack.Screen>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
/>
|
|
40
|
+
}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(queryByText("Pushed screen")).toBe(null);
|
|
45
|
+
fireEvent.press(getByText("Push"));
|
|
46
|
+
getByText("Pushed screen!");
|
|
47
|
+
});
|
|
48
|
+
});
|
package/src/navigation.tsx
CHANGED
|
@@ -13,8 +13,6 @@ import type { PushScreenOptions } from "./types";
|
|
|
13
13
|
/**
|
|
14
14
|
* Ideas:
|
|
15
15
|
* - lifecycles / screen tracking
|
|
16
|
-
* - testing guide
|
|
17
|
-
* - routing example -> fragments to ids
|
|
18
16
|
*/
|
|
19
17
|
|
|
20
18
|
export function createNavigation() {
|
|
@@ -66,6 +64,15 @@ function getNavigationFns({ store, dispatch, renderCharts }: NavigationStore) {
|
|
|
66
64
|
0
|
|
67
65
|
);
|
|
68
66
|
let tabIds = renderCharts.tabsByDepth[maxDepth];
|
|
67
|
+
|
|
68
|
+
if (!tabIds || tabIds?.length === 0) {
|
|
69
|
+
if (store.getState().debugModeEnabled) {
|
|
70
|
+
console.warn("No focused tabs found");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
69
76
|
let topTabId = tabIds[tabIds.length - 1];
|
|
70
77
|
return topTabId;
|
|
71
78
|
}
|
package/src/stack.tsx
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import {
|
|
3
3
|
BackHandler,
|
|
4
|
-
PixelRatio,
|
|
5
4
|
Platform,
|
|
6
5
|
StyleSheet,
|
|
7
|
-
useWindowDimensions,
|
|
8
6
|
View,
|
|
9
7
|
type ImageProps,
|
|
10
|
-
type LayoutRectangle,
|
|
11
8
|
type ViewProps,
|
|
12
9
|
type ViewStyle,
|
|
13
10
|
} from "react-native";
|
|
@@ -21,6 +18,7 @@ import {
|
|
|
21
18
|
ScreenStackHeaderCenterView as RNScreenStackHeaderCenterView,
|
|
22
19
|
ScreenStackHeaderConfigProps as RNScreenStackHeaderConfigProps,
|
|
23
20
|
ScreenStackHeaderBackButtonImage as RNScreenStackHeaderBackButtonImage,
|
|
21
|
+
type ScreenProps,
|
|
24
22
|
} from "react-native-screens";
|
|
25
23
|
import ScreenStackNativeComponent from "react-native-screens/src/fabric/ScreenStackNativeComponent";
|
|
26
24
|
|
|
@@ -59,7 +57,7 @@ let RNScreenStack = React.memo(function RNScreenStack(
|
|
|
59
57
|
);
|
|
60
58
|
});
|
|
61
59
|
|
|
62
|
-
type StackRootProps = {
|
|
60
|
+
export type StackRootProps = {
|
|
63
61
|
children: React.ReactNode;
|
|
64
62
|
id?: string;
|
|
65
63
|
};
|
|
@@ -129,7 +127,9 @@ function StackRoot({ children, id }: StackRootProps) {
|
|
|
129
127
|
);
|
|
130
128
|
}
|
|
131
129
|
|
|
132
|
-
|
|
130
|
+
export type StackScreensProps = RNScreenStackProps;
|
|
131
|
+
|
|
132
|
+
function StackScreens({ style: styleProp, ...props }: StackScreensProps) {
|
|
133
133
|
let style = React.useMemo(
|
|
134
134
|
() => styleProp || StyleSheet.absoluteFill,
|
|
135
135
|
[styleProp]
|
|
@@ -143,13 +143,19 @@ 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
|
+
}
|
|
149
|
+
|
|
150
|
+
let HeaderHeightContext = React.createContext<number>(0);
|
|
147
151
|
|
|
148
152
|
let StackScreen = React.memo(function StackScreen({
|
|
149
153
|
children,
|
|
150
154
|
style: styleProp,
|
|
151
155
|
gestureEnabled = true,
|
|
152
156
|
onDismissed: onDismissedProp,
|
|
157
|
+
onHeaderHeightChange: onHeaderHeightChangeProp,
|
|
158
|
+
header,
|
|
153
159
|
...props
|
|
154
160
|
}: StackScreenProps) {
|
|
155
161
|
let stackId = React.useContext(StackIdContext);
|
|
@@ -185,19 +191,44 @@ let StackScreen = React.memo(function StackScreen({
|
|
|
185
191
|
};
|
|
186
192
|
}, [gestureEnabled, stack, screenId, isActive, dispatch]);
|
|
187
193
|
|
|
188
|
-
let
|
|
194
|
+
let parentHeaderHeight = React.useContext(HeaderHeightContext);
|
|
195
|
+
let [headerHeight, setHeaderHeight] = React.useState(parentHeaderHeight);
|
|
196
|
+
|
|
197
|
+
let onHeaderHeightChange: ScreenProps["onHeaderHeightChange"] =
|
|
198
|
+
React.useCallback(
|
|
199
|
+
(e) => {
|
|
200
|
+
Platform.OS === "ios" &&
|
|
201
|
+
e.nativeEvent.headerHeight > 0 &&
|
|
202
|
+
setHeaderHeight(e.nativeEvent.headerHeight);
|
|
203
|
+
onHeaderHeightChangeProp?.(e);
|
|
204
|
+
},
|
|
205
|
+
[onHeaderHeightChangeProp]
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
let style = React.useMemo(
|
|
209
|
+
() => [
|
|
210
|
+
defaultScreenStyle,
|
|
211
|
+
{ paddingTop: headerHeight || parentHeaderHeight },
|
|
212
|
+
styleProp,
|
|
213
|
+
],
|
|
214
|
+
[styleProp, headerHeight, parentHeaderHeight]
|
|
215
|
+
);
|
|
189
216
|
|
|
190
217
|
return (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
218
|
+
<HeaderHeightContext.Provider value={headerHeight}>
|
|
219
|
+
{/* @ts-expect-error - Ref typings in RNScreens */}
|
|
220
|
+
<RNScreen
|
|
221
|
+
{...props}
|
|
222
|
+
style={style}
|
|
223
|
+
activityState={isActive ? 2 : 0}
|
|
224
|
+
gestureEnabled={gestureEnabled}
|
|
225
|
+
onDismissed={onDismissed}
|
|
226
|
+
onHeaderHeightChange={onHeaderHeightChange}
|
|
227
|
+
>
|
|
228
|
+
{header}
|
|
229
|
+
{children}
|
|
230
|
+
</RNScreen>
|
|
231
|
+
</HeaderHeightContext.Provider>
|
|
201
232
|
);
|
|
202
233
|
});
|
|
203
234
|
|
|
@@ -233,26 +264,12 @@ let StackSlot = React.memo(function StackSlot({
|
|
|
233
264
|
);
|
|
234
265
|
});
|
|
235
266
|
|
|
267
|
+
export type StackScreenHeaderProps = RNScreenStackHeaderConfigProps;
|
|
268
|
+
|
|
236
269
|
let StackScreenHeader = React.memo(function StackScreenHeader({
|
|
237
270
|
...props
|
|
238
|
-
}:
|
|
239
|
-
|
|
240
|
-
let insets = useSafeAreaInsetsSafe();
|
|
241
|
-
|
|
242
|
-
let headerHeight = React.useMemo(() => {
|
|
243
|
-
if (Platform.OS === "android") {
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return getDefaultHeaderHeight(layout, false, insets.top);
|
|
248
|
-
}, [layout, insets]);
|
|
249
|
-
|
|
250
|
-
return (
|
|
251
|
-
<React.Fragment>
|
|
252
|
-
<RNScreenStackHeaderConfig {...props} />
|
|
253
|
-
<View style={{ height: headerHeight }} />
|
|
254
|
-
</React.Fragment>
|
|
255
|
-
);
|
|
271
|
+
}: StackScreenHeaderProps) {
|
|
272
|
+
return <RNScreenStackHeaderConfig {...props} />;
|
|
256
273
|
});
|
|
257
274
|
|
|
258
275
|
let StackScreenHeaderLeft = React.memo(function StackScreenHeaderLeft({
|
|
@@ -279,7 +296,7 @@ let ScreenStackHeaderBackButtonImage = React.memo(
|
|
|
279
296
|
}
|
|
280
297
|
);
|
|
281
298
|
|
|
282
|
-
type StackNavigatorProps = Omit<StackRootProps, "children"> & {
|
|
299
|
+
export type StackNavigatorProps = Omit<StackRootProps, "children"> & {
|
|
283
300
|
rootScreen: React.ReactElement<unknown>;
|
|
284
301
|
};
|
|
285
302
|
|
|
@@ -309,47 +326,3 @@ export let Stack = {
|
|
|
309
326
|
Slot: StackSlot,
|
|
310
327
|
Navigator: StackNavigator,
|
|
311
328
|
};
|
|
312
|
-
|
|
313
|
-
// `onLayout` event does not return a value for the native header component
|
|
314
|
-
// This function is copied from react-navigation to get the default header heights
|
|
315
|
-
// Ref: https://github.com/react-navigation/react-navigation/blob/main/packages/elements/src/Header/getDefaultHeaderHeight.tsx#L5
|
|
316
|
-
function getDefaultHeaderHeight(
|
|
317
|
-
layout: Pick<LayoutRectangle, "width" | "height">,
|
|
318
|
-
// TODO - handle modal headers and substacks
|
|
319
|
-
modalPresentation: boolean,
|
|
320
|
-
topInset: number
|
|
321
|
-
): number {
|
|
322
|
-
let headerHeight;
|
|
323
|
-
|
|
324
|
-
// On models with Dynamic Island the status bar height is smaller than the safe area top inset.
|
|
325
|
-
let hasDynamicIsland = Platform.OS === "ios" && topInset > 50;
|
|
326
|
-
let statusBarHeight = hasDynamicIsland
|
|
327
|
-
? topInset - (5 + 1 / PixelRatio.get())
|
|
328
|
-
: topInset;
|
|
329
|
-
|
|
330
|
-
let isLandscape = layout.width > layout.height;
|
|
331
|
-
|
|
332
|
-
if (Platform.OS === "ios") {
|
|
333
|
-
if (Platform.isPad || Platform.isTV) {
|
|
334
|
-
if (modalPresentation) {
|
|
335
|
-
headerHeight = 56;
|
|
336
|
-
} else {
|
|
337
|
-
headerHeight = 50;
|
|
338
|
-
}
|
|
339
|
-
} else {
|
|
340
|
-
if (isLandscape) {
|
|
341
|
-
headerHeight = 32;
|
|
342
|
-
} else {
|
|
343
|
-
if (modalPresentation) {
|
|
344
|
-
headerHeight = 56;
|
|
345
|
-
} else {
|
|
346
|
-
headerHeight = 44;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
351
|
-
headerHeight = 64;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return headerHeight + statusBarHeight;
|
|
355
|
-
}
|
package/src/tabs.tsx
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from "./navigation-store";
|
|
29
29
|
import { generateTabId, useSafeAreaInsetsSafe } from "./utils";
|
|
30
30
|
|
|
31
|
-
type TabsRootProps = {
|
|
31
|
+
export type TabsRootProps = {
|
|
32
32
|
children: React.ReactNode;
|
|
33
33
|
id?: string;
|
|
34
34
|
};
|
|
@@ -44,13 +44,7 @@ let useTabsInternal = (tabId = "") =>
|
|
|
44
44
|
return tab;
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
let TabsRoot = React.memo(function TabsRoot({
|
|
48
|
-
children,
|
|
49
|
-
id,
|
|
50
|
-
}: {
|
|
51
|
-
children: React.ReactNode;
|
|
52
|
-
id?: string;
|
|
53
|
-
}) {
|
|
47
|
+
let TabsRoot = React.memo(function TabsRoot({ children, id }: TabsRootProps) {
|
|
54
48
|
let tabIdRef = React.useRef(id || generateTabId());
|
|
55
49
|
let tabId = tabIdRef.current;
|
|
56
50
|
let tabs = useTabsInternal(tabId);
|
|
@@ -99,10 +93,9 @@ let defaultScreenContainerStyle = {
|
|
|
99
93
|
flex: 1,
|
|
100
94
|
};
|
|
101
95
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}: { children: React.ReactNode } & RNScreenContainerProps) {
|
|
96
|
+
export type TabsScreensProps = RNScreenContainerProps;
|
|
97
|
+
|
|
98
|
+
function TabsScreens({ children, ...props }: TabsScreensProps) {
|
|
106
99
|
return (
|
|
107
100
|
<RNScreenContainer style={defaultScreenContainerStyle} {...props}>
|
|
108
101
|
{React.Children.map(children, (child, index) => {
|
|
@@ -116,11 +109,13 @@ function TabsScreens({
|
|
|
116
109
|
);
|
|
117
110
|
}
|
|
118
111
|
|
|
112
|
+
export type TabsScreenProps = RNScreenProps;
|
|
113
|
+
|
|
119
114
|
let TabsScreen = React.memo(function TabsScreen({
|
|
120
115
|
children,
|
|
121
116
|
style: styleProp,
|
|
122
117
|
...props
|
|
123
|
-
}:
|
|
118
|
+
}: TabsScreenProps) {
|
|
124
119
|
let dispatch = useNavigationDispatch();
|
|
125
120
|
|
|
126
121
|
let tabId = React.useContext(TabIdContext);
|
|
@@ -177,6 +172,7 @@ export let defaultTabbarStyle: ViewStyle = {
|
|
|
177
172
|
backgroundColor: "white",
|
|
178
173
|
};
|
|
179
174
|
|
|
175
|
+
|
|
180
176
|
let TabsTabbar = React.memo(function TabsTabbar({
|
|
181
177
|
children,
|
|
182
178
|
style: styleProp,
|