@rn-tools/navigation 2.1.0 → 2.2.1
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 +228 -6
- package/package.json +2 -1
- package/src/deep-links.tsx +41 -0
- package/src/index.ts +2 -1
- package/src/navigation-reducer.ts +33 -19
- package/src/navigation.tsx +2 -2
- package/src/stack.tsx +34 -0
- package/src/tabs.tsx +5 -1
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ A set of useful navigation components for React Native. Built with `react-native
|
|
|
18
18
|
- [Tabs](#tabs)
|
|
19
19
|
- [Guides](#guides)
|
|
20
20
|
- [Authentication](#authentication)
|
|
21
|
+
- [Deep Links](#deep-links)
|
|
22
|
+
- [Preventing going back](#preventing-going-back)
|
|
21
23
|
|
|
22
24
|
## Installation
|
|
23
25
|
|
|
@@ -301,6 +303,8 @@ Use the `Stack.Header` component to render a native header in a screen.
|
|
|
301
303
|
|
|
302
304
|
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)
|
|
303
305
|
|
|
306
|
+
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
|
+
|
|
304
308
|
**Note:** Wrap your App in a `SafeAreaProvider` to ensure your screen components are rendered correctly with the header
|
|
305
309
|
|
|
306
310
|
**Note:**: The header component **has to be the first child** of a `Stack.Screen` component.
|
|
@@ -308,7 +312,7 @@ Under the hood this is using `react-native-screens` header - [here is a referenc
|
|
|
308
312
|
```tsx
|
|
309
313
|
import { navigation, Stack } from "@rn-tools/navigation";
|
|
310
314
|
import * as React from "react";
|
|
311
|
-
import { Button, View, TextInput } from "react-native";
|
|
315
|
+
import { Button, View, TextInput, Text } from "react-native";
|
|
312
316
|
|
|
313
317
|
export function HeaderExample() {
|
|
314
318
|
return (
|
|
@@ -333,7 +337,11 @@ function MyScreenWithHeader() {
|
|
|
333
337
|
backTitle="Custom back title"
|
|
334
338
|
backTitleFontSize={16}
|
|
335
339
|
hideBackButton={false}
|
|
336
|
-
|
|
340
|
+
>
|
|
341
|
+
<Stack.HeaderRight>
|
|
342
|
+
<Text>Custom right text!</Text>
|
|
343
|
+
</Stack.HeaderRight>
|
|
344
|
+
</Stack.Header>
|
|
337
345
|
|
|
338
346
|
<View
|
|
339
347
|
style={{
|
|
@@ -356,7 +364,7 @@ function MyScreenWithHeader() {
|
|
|
356
364
|
|
|
357
365
|
## Components
|
|
358
366
|
|
|
359
|
-
The `Navigator` components in the previous examples are fairly straightforward wrappers around other lower level `Stack` and `Tabs` components.
|
|
367
|
+
The `Navigator` components in the previous examples are fairly straightforward wrappers around other lower level `Stack` and `Tabs` components.
|
|
360
368
|
|
|
361
369
|
If you need to customize behaviour, design a component API you prefer to use, or just enjoy writing your own components, you can use these implementations as a reference to build your own.
|
|
362
370
|
|
|
@@ -462,7 +470,6 @@ let TabNavigator = React.memo(function TabNavigator({
|
|
|
462
470
|
</Tabs.Root>
|
|
463
471
|
);
|
|
464
472
|
});
|
|
465
|
-
|
|
466
473
|
```
|
|
467
474
|
|
|
468
475
|
- `Tabs.Root` - The root component for a tabs navigator.
|
|
@@ -475,8 +482,6 @@ let TabNavigator = React.memo(function TabNavigator({
|
|
|
475
482
|
- `Tabs.Tab` - A tab in a tabs navigator
|
|
476
483
|
- This is a Pressable component that switches the active screen
|
|
477
484
|
|
|
478
|
-
|
|
479
|
-
|
|
480
485
|
## Guides
|
|
481
486
|
|
|
482
487
|
### Authentication
|
|
@@ -522,3 +527,220 @@ let useUser = () => {
|
|
|
522
527
|
return user;
|
|
523
528
|
};
|
|
524
529
|
```
|
|
530
|
+
|
|
531
|
+
### Deep Links
|
|
532
|
+
|
|
533
|
+
This section will cover how to respond to deep links in your app. Deep links usually have some extra setup required - use Expo's [Deep Linking Guide](https://docs.expo.dev/guides/deep-linking/) to get started.
|
|
534
|
+
|
|
535
|
+
Once you are able to receive deep links, use the `DeepLinks` component exported from this library to handle them. In this example we will have a basic 3 tab view. We want to response to the link `home/items/:id` by navigating to the home tab and then pushing a detail screen with the corresponding id.
|
|
536
|
+
|
|
537
|
+
The deep link component takes an array of handlers which are functions that will be invoked when their `path` matches the deep link that was opened.
|
|
538
|
+
- Only the first matching handler will be invoked.
|
|
539
|
+
- The handler function will receive the params from the deep link - these use the same token syntax as libraries like `react-router` and `express` for path params.
|
|
540
|
+
|
|
541
|
+
```tsx
|
|
542
|
+
import { DeepLinks, navigation, Stack, Tabs } from "@rn-tools/navigation";
|
|
543
|
+
import * as React from "react";
|
|
544
|
+
import { View, Text, TouchableOpacity } from "react-native";
|
|
545
|
+
import * as Linking from "expo-linking";
|
|
546
|
+
|
|
547
|
+
export function DeepLinksExample() {
|
|
548
|
+
// You'll likely want to use Expo's Linking API to get the current URL and path
|
|
549
|
+
// let url = Linking.useURL()
|
|
550
|
+
// let { path } = Linking.parse(url)
|
|
551
|
+
|
|
552
|
+
// But it's easier to test hardcoded strings for the sake of this example
|
|
553
|
+
let path = "/testing/home/item/4";
|
|
554
|
+
|
|
555
|
+
return (
|
|
556
|
+
<DeepLinks
|
|
557
|
+
path={path}
|
|
558
|
+
handlers={[
|
|
559
|
+
{
|
|
560
|
+
path: "/testing/home/item/:itemId",
|
|
561
|
+
handler: (params: { itemId: string }) => {
|
|
562
|
+
let itemId = params.itemId;
|
|
563
|
+
|
|
564
|
+
// Go to home tab
|
|
565
|
+
navigation.setTabIndex(0);
|
|
566
|
+
|
|
567
|
+
// Push the screen we want
|
|
568
|
+
navigation.pushScreen(
|
|
569
|
+
<Stack.Screen>
|
|
570
|
+
<MyScreen title={`Item: ${itemId}`} />
|
|
571
|
+
</Stack.Screen>
|
|
572
|
+
);
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
]}
|
|
576
|
+
>
|
|
577
|
+
<MyTabs />
|
|
578
|
+
</DeepLinks>
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function MyTabs() {
|
|
583
|
+
return (
|
|
584
|
+
<Tabs.Navigator
|
|
585
|
+
tabbarPosition="bottom"
|
|
586
|
+
screens={[
|
|
587
|
+
{
|
|
588
|
+
key: "1",
|
|
589
|
+
screen: (
|
|
590
|
+
<Stack.Navigator
|
|
591
|
+
rootScreen={<MyScreen bg="red" title="Home screen" isRoot />}
|
|
592
|
+
/>
|
|
593
|
+
),
|
|
594
|
+
tab: ({ isActive }) => <MyTab text="Home" isActive={isActive} />,
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
key: "2",
|
|
598
|
+
screen: (
|
|
599
|
+
<Stack.Navigator
|
|
600
|
+
rootScreen={<MyScreen bg="blue" title="Search screen" isRoot />}
|
|
601
|
+
/>
|
|
602
|
+
),
|
|
603
|
+
tab: ({ isActive }) => <MyTab text="Search" isActive={isActive} />,
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
key: "3",
|
|
607
|
+
screen: (
|
|
608
|
+
<Stack.Navigator
|
|
609
|
+
rootScreen={
|
|
610
|
+
<MyScreen bg="purple" title="Settings screen" isRoot />
|
|
611
|
+
}
|
|
612
|
+
/>
|
|
613
|
+
),
|
|
614
|
+
tab: ({ isActive }) => <MyTab text="Settings" isActive={isActive} />,
|
|
615
|
+
},
|
|
616
|
+
]}
|
|
617
|
+
/>
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function MyTab({ isActive, text }: { isActive?: boolean; text: string }) {
|
|
622
|
+
return (
|
|
623
|
+
<View
|
|
624
|
+
style={{
|
|
625
|
+
padding: 16,
|
|
626
|
+
justifyContent: "center",
|
|
627
|
+
alignItems: "center",
|
|
628
|
+
}}
|
|
629
|
+
>
|
|
630
|
+
<Text style={{ fontSize: 12, fontWeight: isActive ? "bold" : "normal" }}>
|
|
631
|
+
{text}
|
|
632
|
+
</Text>
|
|
633
|
+
</View>
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function MyScreen({
|
|
638
|
+
bg = "white",
|
|
639
|
+
title = "",
|
|
640
|
+
isRoot = false,
|
|
641
|
+
}: {
|
|
642
|
+
title?: string;
|
|
643
|
+
bg?: string;
|
|
644
|
+
isRoot?: boolean;
|
|
645
|
+
}) {
|
|
646
|
+
return (
|
|
647
|
+
<View style={{ flex: 1, backgroundColor: bg }}>
|
|
648
|
+
<View className="flex-1 items-center justify-center gap-4">
|
|
649
|
+
<Text style={{ fontSize: 26, fontWeight: "semibold" }}>{title}</Text>
|
|
650
|
+
|
|
651
|
+
{!isRoot && (
|
|
652
|
+
<TouchableOpacity
|
|
653
|
+
onPress={() => {
|
|
654
|
+
navigation.popScreen();
|
|
655
|
+
}}
|
|
656
|
+
>
|
|
657
|
+
<Text>Pop</Text>
|
|
658
|
+
</TouchableOpacity>
|
|
659
|
+
)}
|
|
660
|
+
</View>
|
|
661
|
+
</View>
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Preventing going back
|
|
667
|
+
|
|
668
|
+
If you want to prevent users from popping a screen and potentially losing unsaved data, you can stop the screen from being dismissed by a gesture or pressing the back button.
|
|
669
|
+
|
|
670
|
+
**Note:**: The native header component does not provide a reliable way to prevent going back on iOS, so you'll have to provide your own custom back button by using the `Stack.HeaderLeft` component
|
|
671
|
+
|
|
672
|
+
```tsx
|
|
673
|
+
import { navigation, Stack } from "@rn-tools/navigation";
|
|
674
|
+
import * as React from "react";
|
|
675
|
+
import {
|
|
676
|
+
Text,
|
|
677
|
+
TextInput,
|
|
678
|
+
TouchableOpacity,
|
|
679
|
+
Button,
|
|
680
|
+
View,
|
|
681
|
+
Alert,
|
|
682
|
+
} from "react-native";
|
|
683
|
+
|
|
684
|
+
export function PreventGoingBack() {
|
|
685
|
+
return (
|
|
686
|
+
<Button
|
|
687
|
+
title="Push screen"
|
|
688
|
+
onPress={() => navigation.pushScreen(<MyScreen />)}
|
|
689
|
+
/>
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function MyScreen() {
|
|
694
|
+
let [input, setInput] = React.useState("");
|
|
695
|
+
|
|
696
|
+
let canGoBack = input.length === 0;
|
|
697
|
+
|
|
698
|
+
let onPressBackButton = React.useCallback(() => {
|
|
699
|
+
if (canGoBack) {
|
|
700
|
+
navigation.popScreen();
|
|
701
|
+
} else {
|
|
702
|
+
Alert.alert("Are you sure you want to go back?", "", [
|
|
703
|
+
{
|
|
704
|
+
text: "Cancel",
|
|
705
|
+
style: "cancel",
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
text: "Yes",
|
|
709
|
+
onPress: () => navigation.popScreen(),
|
|
710
|
+
},
|
|
711
|
+
]);
|
|
712
|
+
}
|
|
713
|
+
}, [canGoBack]);
|
|
714
|
+
|
|
715
|
+
return (
|
|
716
|
+
<Stack.Screen
|
|
717
|
+
preventNativeDismiss={!canGoBack}
|
|
718
|
+
nativeBackButtonDismissalEnabled={!canGoBack}
|
|
719
|
+
gestureEnabled={canGoBack}
|
|
720
|
+
>
|
|
721
|
+
<Stack.Header title="Prevent going back">
|
|
722
|
+
<Stack.HeaderLeft>
|
|
723
|
+
<TouchableOpacity
|
|
724
|
+
onPress={onPressBackButton}
|
|
725
|
+
style={{ opacity: canGoBack ? 1 : 0.4 }}
|
|
726
|
+
>
|
|
727
|
+
<Text>Back</Text>
|
|
728
|
+
</TouchableOpacity>
|
|
729
|
+
</Stack.HeaderLeft>
|
|
730
|
+
</Stack.Header>
|
|
731
|
+
<View style={{ paddingVertical: 48, paddingHorizontal: 16, gap: 16 }}>
|
|
732
|
+
<Text style={{ fontSize: 22, fontWeight: "medium" }}>
|
|
733
|
+
Enter some text and try to go back
|
|
734
|
+
</Text>
|
|
735
|
+
<TextInput
|
|
736
|
+
value={input}
|
|
737
|
+
onChangeText={setInput}
|
|
738
|
+
placeholder="Enter some text"
|
|
739
|
+
onSubmitEditing={() => setInput("")}
|
|
740
|
+
/>
|
|
741
|
+
<Button title="Submit" onPress={() => setInput("")} />
|
|
742
|
+
</View>
|
|
743
|
+
</Stack.Screen>
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rn-tools/navigation",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"main": "./src/index.ts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"react-native-screens": "*"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"path-to-regexp": "^6.2.2",
|
|
37
38
|
"zustand": "^4.5.2"
|
|
38
39
|
},
|
|
39
40
|
"jest": {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { match } from "path-to-regexp";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
export type DeepLinkHandler<T> = {
|
|
5
|
+
path: string;
|
|
6
|
+
handler: (params: T) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function buildMatchers<T>(handlers: DeepLinkHandler<T>[]) {
|
|
10
|
+
return handlers.map(({ path, handler }) => {
|
|
11
|
+
let fn = match(path, { decode: decodeURIComponent });
|
|
12
|
+
return { fn, handler };
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type DeepLinksProps<T> = {
|
|
17
|
+
path: string;
|
|
18
|
+
handlers: DeepLinkHandler<T>[];
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function DeepLinks<T>({ path, handlers, children }: DeepLinksProps<T>) {
|
|
23
|
+
let matchers = React.useRef(buildMatchers(handlers));
|
|
24
|
+
|
|
25
|
+
React.useLayoutEffect(() => {
|
|
26
|
+
matchers.current = buildMatchers(handlers);
|
|
27
|
+
}, [handlers]);
|
|
28
|
+
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
for (let matcher of matchers.current) {
|
|
31
|
+
let { fn, handler } = matcher;
|
|
32
|
+
let match = fn(path);
|
|
33
|
+
if (match) {
|
|
34
|
+
setImmediate(() => handler(match.params as T));
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, [path]);
|
|
39
|
+
|
|
40
|
+
return <>{children}</>;
|
|
41
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -103,6 +103,12 @@ type SetTabIndexAction = {
|
|
|
103
103
|
tabId: string;
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
+
type PopActiveTabAction = {
|
|
107
|
+
type: "POP_ACTIVE_TAB";
|
|
108
|
+
tabId: string;
|
|
109
|
+
index: number;
|
|
110
|
+
};
|
|
111
|
+
|
|
106
112
|
type RegisterTabAction = {
|
|
107
113
|
type: "REGISTER_TAB";
|
|
108
114
|
depth: number;
|
|
@@ -126,7 +132,8 @@ type TabActions =
|
|
|
126
132
|
| SetTabIndexAction
|
|
127
133
|
| RegisterTabAction
|
|
128
134
|
| UnregisterTabAction
|
|
129
|
-
| TabBackAction
|
|
135
|
+
| TabBackAction
|
|
136
|
+
| PopActiveTabAction;
|
|
130
137
|
|
|
131
138
|
type SetDebugModeAction = {
|
|
132
139
|
type: "SET_DEBUG_MODE";
|
|
@@ -375,28 +382,35 @@ export function reducer(
|
|
|
375
382
|
}
|
|
376
383
|
);
|
|
377
384
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
385
|
+
return nextState;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
case "POP_ACTIVE_TAB": {
|
|
389
|
+
let { tabId, index } = action;
|
|
390
|
+
let { renderCharts } = context;
|
|
391
|
+
|
|
392
|
+
let tabKey = serializeTabIndexKey(tabId, index);
|
|
393
|
+
let stackIds = renderCharts.stacksByTabIndex[tabKey];
|
|
381
394
|
|
|
382
|
-
|
|
383
|
-
stackIds.forEach((stackId) => {
|
|
384
|
-
let stack = nextState.stacks.lookup[stackId];
|
|
385
|
-
let screenIdsToRemove = stack.screens;
|
|
395
|
+
let nextState: NavigationState = Object.assign({}, state);
|
|
386
396
|
|
|
387
|
-
|
|
397
|
+
if (stackIds?.length > 0) {
|
|
398
|
+
stackIds.forEach((stackId) => {
|
|
399
|
+
let stack = nextState.stacks.lookup[stackId];
|
|
400
|
+
let screenIdsToRemove = stack.screens;
|
|
388
401
|
|
|
389
|
-
|
|
390
|
-
delete nextScreensLookup[id];
|
|
391
|
-
});
|
|
402
|
+
let nextScreensLookup = Object.assign({}, nextState.screens.lookup);
|
|
392
403
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
(id) => !screenIdsToRemove.includes(id)
|
|
396
|
-
);
|
|
397
|
-
nextState.screens.lookup = nextScreensLookup;
|
|
404
|
+
screenIdsToRemove.forEach((id) => {
|
|
405
|
+
delete nextScreensLookup[id];
|
|
398
406
|
});
|
|
399
|
-
|
|
407
|
+
|
|
408
|
+
nextState.stacks.lookup[stackId].screens = [];
|
|
409
|
+
nextState.screens.ids = nextState.screens.ids.filter(
|
|
410
|
+
(id) => !screenIdsToRemove.includes(id)
|
|
411
|
+
);
|
|
412
|
+
nextState.screens.lookup = nextScreensLookup;
|
|
413
|
+
});
|
|
400
414
|
}
|
|
401
415
|
|
|
402
416
|
return nextState;
|
|
@@ -442,7 +456,7 @@ export function reducer(
|
|
|
442
456
|
let { tabId } = action;
|
|
443
457
|
let nextState: NavigationState = Object.assign({}, state);
|
|
444
458
|
|
|
445
|
-
let tab
|
|
459
|
+
let tab = nextState.tabs.lookup[tabId];
|
|
446
460
|
|
|
447
461
|
let lastActiveIndex = tab.history[tab.history.length - 1];
|
|
448
462
|
|
package/src/navigation.tsx
CHANGED
|
@@ -12,9 +12,9 @@ import type { PushScreenOptions } from "./types";
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Ideas:
|
|
15
|
-
* - pull in safe area provider and use as default props to tabs
|
|
16
15
|
* - lifecycles / screen tracking
|
|
17
|
-
* - testing
|
|
16
|
+
* - testing guide
|
|
17
|
+
* - routing example -> fragments to ids
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
export function createNavigation() {
|
package/src/stack.tsx
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
StyleSheet,
|
|
7
7
|
useWindowDimensions,
|
|
8
8
|
View,
|
|
9
|
+
type ImageProps,
|
|
9
10
|
type LayoutRectangle,
|
|
11
|
+
type ViewProps,
|
|
10
12
|
type ViewStyle,
|
|
11
13
|
} from "react-native";
|
|
12
14
|
import {
|
|
@@ -14,7 +16,11 @@ import {
|
|
|
14
16
|
Screen as RNScreen,
|
|
15
17
|
ScreenProps as RNScreenProps,
|
|
16
18
|
ScreenStackHeaderConfig as RNScreenStackHeaderConfig,
|
|
19
|
+
ScreenStackHeaderLeftView as RNScreenStackHeaderLeftView,
|
|
20
|
+
ScreenStackHeaderRightView as RNScreenStackHeaderRightView,
|
|
21
|
+
ScreenStackHeaderCenterView as RNScreenStackHeaderCenterView,
|
|
17
22
|
ScreenStackHeaderConfigProps as RNScreenStackHeaderConfigProps,
|
|
23
|
+
ScreenStackHeaderBackButtonImage as RNScreenStackHeaderBackButtonImage,
|
|
18
24
|
} from "react-native-screens";
|
|
19
25
|
import ScreenStackNativeComponent from "react-native-screens/src/fabric/ScreenStackNativeComponent";
|
|
20
26
|
|
|
@@ -249,6 +255,30 @@ let StackScreenHeader = React.memo(function StackScreenHeader({
|
|
|
249
255
|
);
|
|
250
256
|
});
|
|
251
257
|
|
|
258
|
+
let StackScreenHeaderLeft = React.memo(function StackScreenHeaderLeft({
|
|
259
|
+
...props
|
|
260
|
+
}: ViewProps) {
|
|
261
|
+
return <RNScreenStackHeaderLeftView {...props} />;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
let StackScreenHeaderCenter = React.memo(function StackScreenHeaderCenter({
|
|
265
|
+
...props
|
|
266
|
+
}: ViewProps) {
|
|
267
|
+
return <RNScreenStackHeaderCenterView {...props} />;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
let StackScreenHeaderRight = React.memo(function StackScreenHeaderRight({
|
|
271
|
+
...props
|
|
272
|
+
}: ViewProps) {
|
|
273
|
+
return <RNScreenStackHeaderRightView {...props} />;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
let ScreenStackHeaderBackButtonImage = React.memo(
|
|
277
|
+
function ScreenStackHeaderBackButtonImage(props: ImageProps) {
|
|
278
|
+
return <RNScreenStackHeaderBackButtonImage {...props} />;
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
|
|
252
282
|
type StackNavigatorProps = Omit<StackRootProps, "children"> & {
|
|
253
283
|
rootScreen: React.ReactElement<unknown>;
|
|
254
284
|
};
|
|
@@ -272,6 +302,10 @@ export let Stack = {
|
|
|
272
302
|
Screens: StackScreens,
|
|
273
303
|
Screen: StackScreen,
|
|
274
304
|
Header: StackScreenHeader,
|
|
305
|
+
HeaderLeft: StackScreenHeaderLeft,
|
|
306
|
+
HeaderCenter: StackScreenHeaderCenter,
|
|
307
|
+
HeaderRight: StackScreenHeaderRight,
|
|
308
|
+
HeaderBackImage: ScreenStackHeaderBackButtonImage,
|
|
275
309
|
Slot: StackSlot,
|
|
276
310
|
Navigator: StackNavigator,
|
|
277
311
|
};
|
package/src/tabs.tsx
CHANGED
|
@@ -225,7 +225,11 @@ let TabsTab = React.memo(function TabsTab({
|
|
|
225
225
|
|
|
226
226
|
let onPress: () => void = React.useCallback(() => {
|
|
227
227
|
dispatch({ type: "SET_TAB_INDEX", tabId, index });
|
|
228
|
-
|
|
228
|
+
|
|
229
|
+
if (isActive) {
|
|
230
|
+
dispatch({ type: "POP_ACTIVE_TAB", tabId, index });
|
|
231
|
+
}
|
|
232
|
+
}, [tabId, index, dispatch, isActive]);
|
|
229
233
|
|
|
230
234
|
let style = React.useMemo(() => {
|
|
231
235
|
let baseStyle = props.style || defaultTabStyle;
|