@rn-tools/navigation 2.3.0 → 3.0.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 CHANGED
@@ -1,27 +1,6 @@
1
1
  # @rn-tools/navigation
2
2
 
3
- A set of useful navigation components for React Native. Built with `react-native-screens` and designed with flexibility in mind.
4
-
5
- ## Table of Contents
6
-
7
- - [Installation](#installation)
8
- - [Basic Usage](#basic-usage)
9
- - [Stack Navigator](#stack-navigator)
10
- - [Tab Navigator](#tab-navigator)
11
- - [Rendering a stack inside of a tabbed screen](#rendering-a-stack-inside-of-a-tabbed-screen)
12
- - [Targeting a specific stack](#targeting-a-specific-stack)
13
- - [Pushing a screen once](#pushing-a-screen-once)
14
- - [Targeting specific tabs](#targeting-specific-tabs)
15
- - [Rendering a header](#rendering-a-header)
16
- - [Configuring screen props](#configuring-screen-props)
17
- - [Components](#components)
18
- - [Stack](#stack)
19
- - [Tabs](#tabs)
20
- - [Guides](#guides)
21
- - [Authentication](#authentication)
22
- - [Deep Links](#deep-links)
23
- - [Preventing going back](#preventing-going-back)
24
- - [Testing](#testing)
3
+ Navigation primitives for React Native. Built on `react-native-screens`.
25
4
 
26
5
  ## Installation
27
6
 
@@ -29,750 +8,61 @@ A set of useful navigation components for React Native. Built with `react-native
29
8
  yarn expo install @rn-tools/navigation react-native-screens
30
9
  ```
31
10
 
32
- ## Basic Usage
33
-
34
- For basic usage, the exported `Stack.Navigator` and `Tabs.Navigator` will get you up and running quickly.
35
-
36
- The [Guides](#guides) section covers how to use lower-level `Stack` and `Tabs` components in a variety of navigation patterns.
37
-
38
- `Stack` and `Tabs` are composable components that can be safely nested within each other without any additional configuration or setup.
39
-
40
- ### Stack Navigator
41
-
42
- The `Stack.Navigator` component manages screens. Under the hood this is using `react-native-screens` to handle pushing and popping natively.
43
-
44
- Screens are pushed and popped by the exported navigation methods:
45
-
46
- - `navigation.pushScreen(screenElement: React.ReactElement<ScreenProps>, options?: PushScreenOptions) => void`
47
-
48
- - `navigation.popScreen(numberOfScreens: number) => void`
49
-
50
- In the majority of cases, these methods will determine the right stack without you needing to specify. But you can target a specific stacks as well if you need to! This is covered in the [Targeting a specific stack](#targeting-a-specific-stack) section.
51
-
52
- ```tsx
53
- import { Stack, navigation } from "@rn-tools/navigation";
54
- import * as React from "react";
55
- import { View, Text, Button } from "react-native";
56
-
57
- export function BasicStack() {
58
- return <Stack.Navigator rootScreen={<MyScreen title="Root Screen" />} />;
59
- }
60
-
61
- function MyScreen({
62
- title,
63
- children,
64
- }: {
65
- title: string;
66
- children?: React.ReactNode;
67
- }) {
68
- function pushScreen() {
69
- navigation.pushScreen(
70
- <Stack.Screen>
71
- <MyScreen title="Pushed screen">
72
- <Button title="Pop screen" onPress={popScreen} />
73
- </MyScreen>
74
- </Stack.Screen>
75
- );
76
- }
77
-
78
- function popScreen() {
79
- navigation.popScreen();
80
- }
81
-
82
- return (
83
- <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
84
- <Text>{title}</Text>
85
- <Button title="Push screen" onPress={pushScreen} />
86
- {children}
87
- </View>
88
- );
89
- }
90
- ```
91
-
92
- **Note**: The components passed to `navigation.pushScreen` need to be wrapped in a `Stack.Screen`. Create a wrapper to simplify your usage if you'd like:
93
-
94
- ```tsx
95
- function myPushScreen(
96
- element: React.ReactElement<unknown>,
97
- options?: PushScreenOptions
98
- ) {
99
- navigation.pushScreen(<Stack.Screen>{element}</Stack.Screen>, options);
100
- }
101
- ```
102
-
103
- ### Tab Navigator
104
-
105
- The `Tabs.Navigator` component also uses `react-native-screens` to handle switching between tabs natively.
106
-
107
- The active tab can be changed via the `navigation.setTabIndex` method, however the built in tabbar handles switching between screens out of the box.
108
-
109
- ```tsx
110
- import { Tabs, navigation, Stack } from "@rn-tools/navigation";
111
- import * as React from "react";
112
- import { View, Text, Button } from "react-native";
113
-
114
-
115
- export function BasicTabs() {
116
- return (
117
- <Stack.Navigator rootScreen={<MyTabs />} />
118
- );
119
- }
120
-
121
- function MyTabs() {
122
- return (
123
- <Tabs.Navigator
124
- tabbarPosition="bottom"
125
- tabbarStyle={{ backgroundColor: "blue" }}
126
- screens={[
127
- {
128
- key: "1",
129
- screen: <MyScreen title="Screen 1" bg="red" />,
130
- tab: ({ isActive }) => <MyTab isActive={isActive}>1</MyTab>,
131
- },
132
- {
133
- key: "2",
134
- screen: <MyScreen title="Screen 2" bg="blue" />,
135
- tab: ({ isActive }) => <MyTab isActive={isActive}>2</MyTab>,
136
- },
137
- {
138
- key: "3",
139
- screen: <MyScreen title="Screen 3" bg="purple" />,
140
- tab: ({ isActive }) => <MyTab isActive={isActive}>3</MyTab>,
141
- },
142
- ]}
143
- />
144
- );
145
- }
146
-
147
- function MyTab({
148
- children,
149
- isActive,
150
- }: {
151
- children: React.ReactNode;
152
- isActive: boolean;
153
- }) {
154
- return (
155
- <View style={{ padding: 16, alignItems: "center" }}>
156
- <Text
157
- style={isActive ? { fontWeight: "bold" } : { fontWeight: "normal" }}
158
- >
159
- {children}
160
- </Text>
161
- </View>
162
- );
163
- }
164
-
165
- function MyScreen({
166
- title,
167
- children,
168
- bg,
169
- }: {
170
- title: string;
171
- children?: React.ReactNode;
172
- bg?: string;
173
- }) {
174
- function pushScreen() {
175
- navigation.pushScreen(
176
- <Stack.Screen>
177
- <MyScreen title="Pushed screen" bg={bg}>
178
- <Button title="Pop screen" onPress={popScreen} />
179
- </MyScreen>
180
- </Stack.Screen>
181
- );
182
- }
183
-
184
- function popScreen() {
185
- navigation.popScreen();
186
- }
187
-
188
- return (
189
- <View
190
- style={{
191
- flex: 1,
192
- justifyContent: "center",
193
- alignItems: "center",
194
- backgroundColor: bg || "white",
195
- }}
196
- >
197
- <Text>{title}</Text>
198
- <Button title="Push screen" onPress={pushScreen} />
199
- {children}
200
- </View>
201
- );
202
- }
203
- ```
204
-
205
- ### Rendering a stack inside of a tabbed screen
206
-
207
- Each tab can have its own stack by nesting the `Stack.Navigator` component.
208
-
209
- ```tsx
210
- function MyTabs() {
211
- return (
212
- <Tabs.Navigator
213
- screens={[
214
- {
215
- key: "1",
216
- // Wrap the screen in a Stack.Navigator:
217
- screen: (
218
- <Stack.Navigator
219
- rootScreen={<MyScreen title="Screen 1" bg="red" />}
220
- />
221
- ),
222
- tab: ({ isActive }) => <MyTab isActive={isActive}>1</MyTab>,
223
- },
224
- // ...other screens
225
- ]}
226
- />
227
- );
228
- }
229
- ```
230
-
231
- ### Targeting a specific stack
232
-
233
- Provide an `id` prop to a stack and target when pushing the screen.
234
-
235
- ```tsx
236
- let MAIN_STACK_ID = "mainStack";
237
-
238
- function App() {
239
- return (
240
- <Stack.Navigator
241
- id={MAIN_STACK_ID}
242
- rootScreen={<MyScreen title="Root Screen" />}
243
- />
244
- );
245
- }
246
-
247
- function pushToMainStack(
248
- screenElement: React.ReactElement<unknown>,
249
- options: PushScreenOptions
250
- ) {
251
- navigation.pushScreen(<Stack.Screen>{screenElement}</Stack.Screen>, {
252
- ...options,
253
- stackId: MAIN_STACK_ID,
254
- });
255
- }
256
- ```
257
-
258
- ### Pushing a screen once
259
-
260
- Provide a `screenId` option to only push the screen once. Screen ids are unique across all stacks.
261
-
262
- ```tsx
263
- function pushThisScreenOnce() {
264
- navigation.pushScreen(
265
- <Stack.Screen>
266
- <MyScreen title="Pushed screen" />
267
- </Stack.Screen>,
268
- {
269
- // This screen will only be pushed once
270
- screenId: "unique-key",
271
- }
272
- );
273
- }
274
- ```
275
-
276
- ### Targeting specific tabs
277
-
278
- Similar to `Stack.Navigator`, pass an `id` prop to a `Tabs.Navigator` and target a navigator expliclity when setting the active tab.
279
-
280
- ```tsx
281
- let MAIN_TAB_ID = "mainTabs";
282
-
283
- function App() {
284
- return <Tabs.Navigator id={MAIN_TAB_ID} screens={tabs} />;
285
- }
286
-
287
- function switchMainTabsToTab(tabIndex: number) {
288
- navigation.setTabIndex(tabIndex, { tabId: MAIN_TAB_ID });
289
- }
290
- ```
291
-
292
- ### Rendering a header
293
-
294
- Use the `Stack.Header` component to render a native header in a screen by passing it as a prop to `Stack.Screen`.
295
-
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)
297
-
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`.
299
-
300
- ```tsx
301
- import { navigation, Stack } from "@rn-tools/navigation";
302
- import * as React from "react";
303
- import { Button, View, TextInput, Text } from "react-native";
304
-
305
- export function HeaderExample() {
306
- return (
307
- <View>
308
- <Button
309
- title="Push screen with header"
310
- onPress={() => navigation.pushScreen(<MyScreenWithHeader />)}
311
- />
312
- </View>
313
- );
314
- }
315
-
316
- function MyScreenWithHeader() {
317
- let [title, setTitle] = React.useState("");
318
-
319
- return (
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
- >
334
- <View
335
- style={{
336
- flex: 1,
337
- alignItems: "center",
338
- paddingVertical: 48,
339
- }}
340
- >
341
- <TextInput
342
- style={{ fontSize: 26, fontWeight: "semibold" }}
343
- value={title}
344
- onChangeText={setTitle}
345
- placeholder="Enter header text"
346
- />
347
- </View>
348
- </Stack.Screen>
349
- );
350
- }
351
- ```
352
-
353
- ### Configuring screen props
354
-
355
- 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)
356
-
357
- Some notable props are `stackPresentation`, `stackAnimation`, and `gestureEnabled`, however there are many more available.
11
+ ## Quick Start
358
12
 
359
13
  ```tsx
360
- function pushNativeModalScreen() {
361
- navigation.pushScreen(
362
- <Stack.Screen
363
- stackPresentation="modal"
364
- stackAnimation="slide_from_bottom"
365
- gestureEnabled={true}
366
- >
367
- {/* If you want to push more screens inside of the modal, wrap it with a Stack */}
368
- <Stack.Navigator rootScreen={<MyScreen title="Modal screen" />} />
369
- </Stack.Screen>
370
- );
371
- }
372
- ```
373
-
374
- ## Components
375
-
376
- The `Navigator` components in the previous examples are fairly straightforward wrappers around other lower level `Stack` and `Tabs` components.
377
-
378
- 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.
379
-
380
- ### Stack
381
-
382
- This is the implementation of the exported `Stack.Navigator` component:
383
-
384
- ```tsx
385
- type StackNavigatorProps = Omit<StackRootProps, "children"> & {
386
- rootScreen: React.ReactElement<unknown>;
387
- };
388
-
389
- export function StackNavigator({
390
- rootScreen,
391
- ...rootProps
392
- }: Stack.NavigatorProps) {
393
- return (
394
- <Stack.Root {...rootProps}>
395
- <Stack.Screens>
396
- <Stack.Screen>{rootScreen}</Stack.Screen>
397
- <Stack.Slot />
398
- </Stack.Screens>
399
- </Stack.Root>
400
- );
401
- }
402
- ```
403
-
404
- - `Stack.Root` - The root component for a stack navigator.
405
- - `Stack.Screens` - The container for all screens in a stack.
406
- - This is a `react-native-screens` StackScreenContainer component under the hood.
407
- - All UI rendered children should be `Stack.Screen` or `Stack.Slot` components.
408
- - You can still render contexts and other non-UI components directly under `Stack.Screens`. See the Authentication guide for examples of this
409
- - `Stack.Screen` - A screen in a stack.
410
- - This is a `react-native-screens` StackScreen component under the hood.
411
- - Notable props include `gestureEnabled`, `stackPresentation` and `preventNativeDismiss` to control how the screen can be interacted with.
412
- - Reference for props that can be passed: [Screen Props](https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screen)
413
- - `Stack.Slot` - A slot for screens to be pushed into.
414
- - This component is used to render screens that are pushed using `navigation.pushScreen` - don't forget to render this somewhere in `Stack.Screens`!
415
- - `Stack.Header` - A header for a screen.
416
- - **Must be rendered as the first child of a `Stack.Screen` component.**
417
- - This is a `react-native-screens` StackHeader component under the hood.
418
- - Reference for props that can be passed: [Header Props](https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screenstackheaderconfig)
419
-
420
- ## Tabs
421
-
422
- This is the implementation of the exported `Tabs.Navigator` component:
423
-
424
- ```tsx
425
- export type TabNavigatorProps = Omit<TabsRootProps, "children"> & {
426
- screens: TabNavigatorScreenOptions[];
427
- tabbarPosition?: "top" | "bottom";
428
- tabbarStyle?: ViewProps["style"];
429
- };
430
-
431
- export type TabNavigatorScreenOptions = {
432
- key: string;
433
- screen: React.ReactElement<unknown>;
434
- tab: (props: { isActive: boolean; onPress: () => void }) => React.ReactNode;
435
- };
436
-
437
- let TabNavigator = React.memo(function TabNavigator({
438
- screens,
439
- tabbarPosition = "bottom",
440
- tabbarStyle: tabbarStyleProp,
441
- ...rootProps
442
- }: TabNavigatorProps) {
443
- let insets = useSafeAreaInsetsSafe();
444
-
445
- let tabbarStyle = React.useMemo(() => {
446
- return [
447
- defaultTabbarStyle,
448
- {
449
- paddingBottom: tabbarPosition === "bottom" ? insets.bottom : 0,
450
- paddingTop: tabbarPosition === "top" ? insets.top : 0,
451
- },
452
- tabbarStyleProp,
453
- ];
454
- }, [tabbarPosition, tabbarStyleProp, insets]);
455
-
456
- return (
457
- <Tabs.Root {...rootProps}>
458
- {tabbarPosition === "top" && (
459
- <Tabs.Tabbar style={tabbarStyle}>
460
- {screens.map((screen) => {
461
- return <Tabs.Tab key={screen.key}>{screen.tab}</Tabs.Tab>;
462
- })}
463
- </Tabs.Tabbar>
464
- )}
465
-
466
- <Tabs.Screens>
467
- {screens.map((screen) => {
468
- return <Tabs.Screen key={screen.key}>{screen.screen}</Tabs.Screen>;
469
- })}
470
- </Tabs.Screens>
471
-
472
- {tabbarPosition === "bottom" && (
473
- <Tabs.Tabbar style={tabbarStyle}>
474
- {screens.map((screen) => {
475
- return <Tabs.Tab key={screen.key}>{screen.tab}</Tabs.Tab>;
476
- })}
477
- </Tabs.Tabbar>
478
- )}
479
- </Tabs.Root>
480
- );
481
- });
482
- ```
483
-
484
- - `Tabs.Root` - The root component for a tabs navigator.
485
- - `Tabs.Screens` - The container for all screens in a tabs navigator.
486
- - This is a `react-native-screens` ScreenContainer component under the hood.
487
- - All UI rendered children should be `Tabs.Screen` components.
488
- - `Tabs.Screen` - A screen in a tabs navigator.
489
- - `Tabs.Tabbar` - The tab bar for a tabs navigator.
490
- - Each child Tab of the tab bar will target the screen that corresponds to its index
491
- - `Tabs.Tab` - A tab in a tabs navigator
492
- - This is a Pressable component that switches the active screen
493
-
494
- ## Guides
495
-
496
- ### Authentication
497
-
498
- For this example, we want to show our main app when the user is logged in, otherwise show the login screen. You can use the `Stack` component to conditionally render screens based on the user's state.
499
-
500
- ```tsx
501
- import * as React from "react";
502
- import { Stack } from "@rn-tools/navigation";
503
-
504
- function App() {
505
- let [user, setUser] = React.useState(null);
506
-
507
- return (
508
- <Stack.Root>
509
- <Stack.Screens>
510
- <Stack.Screen>
511
- <MyLoginScreen onLoginSuccess={(user) => setUser(user)} />
512
- </Stack.Screen>
513
-
514
- {user != null && (
515
- <UserContext.Provider value={user}>
516
- <Stack.Screen gestureEnabled={false}>
517
- <MyAuthenticatedApp />
518
- </Stack.Screen>
519
- <Stack.Slot />
520
- </UserContext.Provider>
521
- )}
522
- </Stack.Screens>
523
- </Stack.Root>
524
- );
525
- }
526
-
527
- let UserContext = React.createContext<User | null>(null);
528
-
529
- let useUser = () => {
530
- let user = React.useContext(UserContext);
531
-
532
- if (user == null) {
533
- throw new Error("User not found");
534
- }
535
-
536
- return user;
537
- };
538
- ```
539
-
540
- ### Deep Links
541
-
542
- 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. Once you are able to receive deep links, use the `DeepLinks` component exported from this library to handle them.
543
-
544
- In this example we will have a basic 3 tab view. We want to repond to the link `home/items/:id` by navigating to the home tab and then pushing a detail screen with the corresponding item id. 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.
545
-
546
- - Only the first matching handler will be invoked.
547
- - 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.
548
- - Make sure that the `DeepLinks` component is inside of a `Stack` component
549
-
550
- ```tsx
551
- import { DeepLinks, navigation, Stack, Tabs } from "@rn-tools/navigation";
552
- import * as Linking from "expo-linking";
553
- import * as React from "react";
554
- import { View, Text, TouchableOpacity } from "react-native";
555
-
556
- export default function DeepLinksExample() {
557
- // You'll likely want to use Expo's Linking API to get the current URL and path
558
- // let url = Linking.useURL()
559
- // let { path } = Linking.parse(url)
560
-
561
- // But it's easier to test hardcoded strings for the sake of this example
562
- let path = "/home/item/2";
563
-
564
- return (
565
- <Stack.Navigator
566
- rootScreen={
567
- <DeepLinks
568
- path={path}
569
- handlers={[
570
- {
571
- path: "/home/item/:itemId",
572
- handler: (params: { itemId: string }) => {
573
- let itemId = params.itemId;
574
-
575
- // Go to home tab
576
- navigation.setTabIndex(0);
577
-
578
- // Push the screen we want
579
- navigation.pushScreen(
580
- <Stack.Screen>
581
- <MyScreen title={`Item: ${itemId}`} />
582
- </Stack.Screen>
583
- );
584
- },
585
- },
586
- ]}
587
- >
588
- <MyTabs />
589
- </DeepLinks>
590
- }
591
- />
592
- );
593
- }
594
-
595
- function MyTabs() {
596
- return (
597
- <Tabs.Navigator
598
- tabbarPosition="bottom"
599
- screens={[
600
- {
601
- key: "1",
602
- screen: (
603
- <Stack.Navigator
604
- rootScreen={<MyScreen bg="red" title="Home screen" isRoot />}
605
- />
606
- ),
607
- tab: ({ isActive }) => <MyTab text="Home" isActive={isActive} />,
608
- },
609
- {
610
- key: "2",
611
- screen: (
612
- <Stack.Navigator
613
- rootScreen={<MyScreen bg="blue" title="Search screen" isRoot />}
614
- />
615
- ),
616
- tab: ({ isActive }) => <MyTab text="Search" isActive={isActive} />,
617
- },
618
- {
619
- key: "3",
620
- screen: (
621
- <Stack.Navigator
622
- rootScreen={
623
- <MyScreen bg="purple" title="Settings screen" isRoot />
624
- }
625
- />
626
- ),
627
- tab: ({ isActive }) => <MyTab text="Settings" isActive={isActive} />,
628
- },
629
- ]}
630
- />
631
- );
632
- }
633
-
634
- function MyTab({ isActive, text }: { isActive?: boolean; text: string }) {
635
- return (
636
- <View
637
- style={{
638
- padding: 16,
639
- justifyContent: "center",
640
- alignItems: "center",
641
- }}
642
- >
643
- <Text style={{ fontSize: 12, fontWeight: isActive ? "bold" : "normal" }}>
644
- {text}
645
- </Text>
646
- </View>
647
- );
648
- }
649
-
650
- function MyScreen({
651
- bg = "white",
652
- title = "",
653
- isRoot = false,
654
- }: {
655
- title?: string;
656
- bg?: string;
657
- isRoot?: boolean;
658
- }) {
659
- return (
660
- <View style={{ flex: 1, backgroundColor: bg }}>
661
- <View
662
- style={{
663
- flex: 1,
664
- justifyContent: "center",
665
- alignItems: "center",
666
- gap: 4,
667
- }}
668
- >
669
- <Text style={{ fontSize: 26, fontWeight: "semibold" }}>{title}</Text>
670
-
671
- {!isRoot && (
672
- <TouchableOpacity
673
- onPress={() => {
674
- navigation.popScreen();
675
- }}
676
- >
677
- <Text>Pop</Text>
678
- </TouchableOpacity>
679
- )}
680
- </View>
681
- </View>
14
+ import {
15
+ createNavigation,
16
+ Navigation,
17
+ Stack,
18
+ Tabs,
19
+ type TabScreenOptions,
20
+ } from "@rn-tools/navigation";
21
+
22
+ const navigation = createNavigation();
23
+
24
+ const tabScreens: TabScreenOptions[] = [
25
+ {
26
+ id: "home",
27
+ screen: <Stack id="home" rootScreen={<HomeScreen />} />,
28
+ tab: ({ isActive, onPress }) => (
29
+ <Pressable onPress={onPress}>
30
+ <Text style={{ fontWeight: isActive ? "bold" : "normal" }}>Home</Text>
31
+ </Pressable>
32
+ ),
33
+ },
34
+ {
35
+ id: "settings",
36
+ screen: <SettingsScreen />,
37
+ tab: ({ isActive, onPress }) => (
38
+ <Pressable onPress={onPress}>
39
+ <Text style={{ fontWeight: isActive ? "bold" : "normal" }}>Settings</Text>
40
+ </Pressable>
41
+ ),
42
+ },
43
+ ];
44
+
45
+ export default function App() {
46
+ return (
47
+ <Navigation navigation={navigation}>
48
+ <Tabs id="main-tabs" screens={tabScreens} />
49
+ </Navigation>
682
50
  );
683
51
  }
684
52
  ```
685
53
 
686
- ### Preventing going back
687
-
688
- 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.
689
-
690
- **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
54
+ Push and pop screens imperatively:
691
55
 
692
56
  ```tsx
693
- import { navigation, Stack } from "@rn-tools/navigation";
694
- import * as React from "react";
695
- import {
696
- Text,
697
- TextInput,
698
- TouchableOpacity,
699
- Button,
700
- View,
701
- Alert,
702
- } from "react-native";
703
-
704
- export function PreventGoingBack() {
705
- return (
706
- <Button
707
- title="Push screen"
708
- onPress={() => navigation.pushScreen(<MyScreen />)}
709
- />
710
- );
711
- }
712
-
713
- function MyScreen() {
714
- let [input, setInput] = React.useState("");
715
-
716
- let canGoBack = input.length === 0;
717
-
718
- let onPressBackButton = React.useCallback(() => {
719
- if (canGoBack) {
720
- navigation.popScreen();
721
- } else {
722
- Alert.alert("Are you sure you want to go back?", "", [
723
- {
724
- text: "Cancel",
725
- style: "cancel",
726
- },
727
- {
728
- text: "Yes",
729
- onPress: () => navigation.popScreen(),
730
- },
731
- ]);
732
- }
733
- }, [canGoBack]);
734
-
735
- return (
736
- <Stack.Screen
737
- preventNativeDismiss={!canGoBack}
738
- nativeBackButtonDismissalEnabled={!canGoBack}
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
- }
752
- >
753
- <View style={{ paddingVertical: 48, paddingHorizontal: 16, gap: 16 }}>
754
- <Text style={{ fontSize: 22, fontWeight: "medium" }}>
755
- Enter some text and try to go back
756
- </Text>
757
- <TextInput
758
- value={input}
759
- onChangeText={setInput}
760
- placeholder="Enter some text"
761
- onSubmitEditing={() => setInput("")}
762
- />
763
- <Button title="Submit" onPress={() => setInput("")} />
764
- </View>
765
- </Stack.Screen>
766
- );
767
- }
57
+ navigation.pushScreen(<DetailScreen />, { id: "detail" });
58
+ navigation.popScreen();
59
+ navigation.setActiveTab(1);
768
60
  ```
769
61
 
770
- ### Testing
771
-
772
- Recommended:
773
-
774
- - Check out the getting started portion of [React Native Testing Library](https://callstack.github.io/react-native-testing-library/docs/start/quick-start)
62
+ When no explicit target is provided, these methods automatically resolve the deepest active stack or tabs instance.
775
63
 
776
- - Set up [Jest Expo](https://docs.expo.dev/develop/unit-testing/)
64
+ ## Docs
777
65
 
778
- - Reset the navigation state between each test by calling `navigation.reset()`
66
+ - [Navigation](docs/navigation.md) setup, `createNavigation`, `NavigationClient` API, hooks
67
+ - [Stack](docs/stack.md) — stack navigation, pushing/popping, refs, preloading, nesting
68
+ - [Tabs](docs/tabs.md) — tab navigation, tab bar, refs, preloading, nesting with stacks