@rn-tools/navigation 2.0.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/README.md +422 -0
- package/package.json +44 -0
- package/src/__tests__/navigation-reducer.test.tsx +348 -0
- package/src/contexts.tsx +8 -0
- package/src/index.ts +3 -0
- package/src/navigation-reducer.ts +467 -0
- package/src/navigation-store.ts +55 -0
- package/src/navigation.tsx +141 -0
- package/src/stack.tsx +255 -0
- package/src/tabs.tsx +297 -0
- package/src/types.ts +48 -0
- package/src/utils.ts +14 -0
package/src/stack.tsx
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { BackHandler, StyleSheet, type ViewStyle } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
ScreenStackProps as RNScreenStackProps,
|
|
5
|
+
Screen as RNScreen,
|
|
6
|
+
ScreenProps as RNScreenProps,
|
|
7
|
+
ScreenStackHeaderConfig as RNScreenStackHeaderConfig,
|
|
8
|
+
ScreenStackHeaderConfigProps as RNScreenStackHeaderConfigProps,
|
|
9
|
+
} from "react-native-screens";
|
|
10
|
+
import ScreenStackNativeComponent from "react-native-screens/src/fabric/ScreenStackNativeComponent";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
ActiveContext,
|
|
14
|
+
DepthContext,
|
|
15
|
+
TabIdContext,
|
|
16
|
+
TabScreenIndexContext,
|
|
17
|
+
} from "./contexts";
|
|
18
|
+
import { DEFAULT_SLOT_NAME } from "./navigation-reducer";
|
|
19
|
+
import { useNavigationDispatch, useNavigationState } from "./navigation-store";
|
|
20
|
+
import type { StackItem } from "./types";
|
|
21
|
+
import { generateStackId } from "./utils";
|
|
22
|
+
|
|
23
|
+
let StackIdContext = React.createContext<string>("");
|
|
24
|
+
let ScreenIdContext = React.createContext<string>("");
|
|
25
|
+
|
|
26
|
+
const RNScreenStack = React.memo(function RNScreenStack(
|
|
27
|
+
props: RNScreenStackProps
|
|
28
|
+
) {
|
|
29
|
+
const { children, gestureDetectorBridge, ...rest } = props;
|
|
30
|
+
const ref = React.useRef(null);
|
|
31
|
+
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
if (gestureDetectorBridge) {
|
|
34
|
+
gestureDetectorBridge.current.stackUseEffectCallback(ref);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<ScreenStackNativeComponent {...rest} ref={ref}>
|
|
40
|
+
{children}
|
|
41
|
+
</ScreenStackNativeComponent>
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
type StackRootProps = {
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
id?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let useStackInternal = (stackId = "") => {
|
|
51
|
+
let stack: StackItem | undefined = useNavigationState(
|
|
52
|
+
(state) => state.stacks.lookup[stackId]
|
|
53
|
+
);
|
|
54
|
+
return stack;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function StackRoot({ children, id }: StackRootProps) {
|
|
58
|
+
let idRef = React.useRef(id || generateStackId());
|
|
59
|
+
let stack = useStackInternal(idRef.current);
|
|
60
|
+
|
|
61
|
+
let isActive = React.useContext(ActiveContext);
|
|
62
|
+
let parentDepth = React.useContext(DepthContext);
|
|
63
|
+
let parentStackId = React.useContext(StackIdContext);
|
|
64
|
+
|
|
65
|
+
let depth = parentDepth + 1;
|
|
66
|
+
let stackId = idRef.current;
|
|
67
|
+
let parentTabId = React.useContext(TabIdContext);
|
|
68
|
+
let tabIndex = React.useContext(TabScreenIndexContext);
|
|
69
|
+
|
|
70
|
+
let dispatch = useNavigationDispatch();
|
|
71
|
+
|
|
72
|
+
React.useLayoutEffect(() => {
|
|
73
|
+
if (!stack) {
|
|
74
|
+
dispatch({ type: "CREATE_STACK_INSTANCE", stackId: idRef.current });
|
|
75
|
+
}
|
|
76
|
+
}, [stack, dispatch]);
|
|
77
|
+
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
if (stack != null) {
|
|
80
|
+
dispatch({
|
|
81
|
+
type: "REGISTER_STACK",
|
|
82
|
+
depth,
|
|
83
|
+
isActive,
|
|
84
|
+
stackId: stack.id,
|
|
85
|
+
parentStackId,
|
|
86
|
+
parentTabId,
|
|
87
|
+
tabIndex,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}, [stack, depth, isActive, parentStackId, parentTabId, tabIndex, dispatch]);
|
|
91
|
+
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
return () => {
|
|
94
|
+
if (stackId != null) {
|
|
95
|
+
dispatch({ type: "UNREGISTER_STACK", stackId });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}, [stackId, dispatch]);
|
|
99
|
+
|
|
100
|
+
if (!stack) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<StackIdContext.Provider value={stack.id}>
|
|
106
|
+
<DepthContext.Provider value={depth}>
|
|
107
|
+
<ActiveContext.Provider value={isActive}>
|
|
108
|
+
{children}
|
|
109
|
+
</ActiveContext.Provider>
|
|
110
|
+
</DepthContext.Provider>
|
|
111
|
+
</StackIdContext.Provider>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function StackScreens({
|
|
116
|
+
style: styleProp,
|
|
117
|
+
...props
|
|
118
|
+
}: RNScreenStackProps) {
|
|
119
|
+
let style = React.useMemo(
|
|
120
|
+
() => styleProp || StyleSheet.absoluteFill,
|
|
121
|
+
[styleProp]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<RNScreenStack {...props} style={style} />
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let defaultScreenStyle: ViewStyle = {
|
|
130
|
+
...StyleSheet.absoluteFillObject,
|
|
131
|
+
backgroundColor: "white",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type StackScreenProps = RNScreenProps;
|
|
135
|
+
|
|
136
|
+
let StackScreen = React.memo(function StackScreen({
|
|
137
|
+
children,
|
|
138
|
+
style: styleProp,
|
|
139
|
+
gestureEnabled = true,
|
|
140
|
+
onDismissed: onDismissedProp,
|
|
141
|
+
...props
|
|
142
|
+
}: StackScreenProps) {
|
|
143
|
+
let stackId = React.useContext(StackIdContext);
|
|
144
|
+
let screenId = React.useContext(ScreenIdContext);
|
|
145
|
+
let stack = useStackInternal(stackId);
|
|
146
|
+
|
|
147
|
+
let dispatch = useNavigationDispatch();
|
|
148
|
+
|
|
149
|
+
let isActive = React.useContext(ActiveContext);
|
|
150
|
+
|
|
151
|
+
let onDismissed: RNScreenProps["onDismissed"] = React.useCallback(
|
|
152
|
+
(e) => {
|
|
153
|
+
dispatch({ type: "POP_SCREEN_BY_KEY", key: screenId });
|
|
154
|
+
onDismissedProp?.(e);
|
|
155
|
+
},
|
|
156
|
+
[onDismissedProp, dispatch, screenId]
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
React.useEffect(() => {
|
|
160
|
+
function backHandler() {
|
|
161
|
+
if (gestureEnabled && isActive && stack?.screens.length > 0) {
|
|
162
|
+
dispatch({ type: "POP_SCREEN_BY_KEY", key: screenId });
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
BackHandler.addEventListener("hardwareBackPress", backHandler);
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
BackHandler.removeEventListener("hardwareBackPress", backHandler);
|
|
173
|
+
};
|
|
174
|
+
}, [gestureEnabled, stack, screenId, isActive, dispatch]);
|
|
175
|
+
|
|
176
|
+
let style = React.useMemo(() => styleProp || defaultScreenStyle, [styleProp]);
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
// @ts-expect-error - Ref typings in RNScreens
|
|
180
|
+
<RNScreen
|
|
181
|
+
{...props}
|
|
182
|
+
style={style}
|
|
183
|
+
// activityState={isActive ? 2 : 0}
|
|
184
|
+
gestureEnabled={gestureEnabled}
|
|
185
|
+
onDismissed={onDismissed}
|
|
186
|
+
>
|
|
187
|
+
{children}
|
|
188
|
+
</RNScreen>
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
let useStackScreens = (stackId = "", slotName: string) => {
|
|
193
|
+
return useNavigationState((state) => {
|
|
194
|
+
let stack = state.stacks.lookup[stackId];
|
|
195
|
+
return (
|
|
196
|
+
stack?.screens
|
|
197
|
+
.map((screenId) => state.screens.lookup[screenId])
|
|
198
|
+
.filter((s) => s && s.slotName === slotName) ?? []
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
let StackSlot = React.memo(function StackSlot({
|
|
204
|
+
slotName = DEFAULT_SLOT_NAME,
|
|
205
|
+
}: {
|
|
206
|
+
slotName?: string;
|
|
207
|
+
}) {
|
|
208
|
+
let stackId = React.useContext(StackIdContext);
|
|
209
|
+
let screens = useStackScreens(stackId, slotName);
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<>
|
|
213
|
+
{screens.map((screen) => {
|
|
214
|
+
return (
|
|
215
|
+
<ScreenIdContext.Provider value={screen.id} key={screen.id}>
|
|
216
|
+
{screen.element}
|
|
217
|
+
</ScreenIdContext.Provider>
|
|
218
|
+
);
|
|
219
|
+
})}
|
|
220
|
+
</>
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
let StackScreenHeader = React.memo(function StackScreenHeader({
|
|
225
|
+
...props
|
|
226
|
+
}: RNScreenStackHeaderConfigProps) {
|
|
227
|
+
return <RNScreenStackHeaderConfig {...props} />;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
type StackNavigatorProps = Omit<StackRootProps, "children"> & {
|
|
231
|
+
rootScreen: React.ReactElement<unknown>;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
let StackNavigator = React.memo(function StackNavigator({
|
|
235
|
+
rootScreen,
|
|
236
|
+
...rootProps
|
|
237
|
+
}: StackNavigatorProps) {
|
|
238
|
+
return (
|
|
239
|
+
<Stack.Root {...rootProps}>
|
|
240
|
+
<Stack.Screens>
|
|
241
|
+
<Stack.Screen>{rootScreen}</Stack.Screen>
|
|
242
|
+
<Stack.Slot />
|
|
243
|
+
</Stack.Screens>
|
|
244
|
+
</Stack.Root>
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
export let Stack = {
|
|
249
|
+
Root: StackRoot,
|
|
250
|
+
Screens: StackScreens,
|
|
251
|
+
Screen: StackScreen,
|
|
252
|
+
Header: StackScreenHeader,
|
|
253
|
+
Slot: StackSlot,
|
|
254
|
+
Navigator: StackNavigator,
|
|
255
|
+
};
|
package/src/tabs.tsx
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
BackHandler,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
View,
|
|
7
|
+
type PressableProps,
|
|
8
|
+
type ViewProps,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import {
|
|
12
|
+
Screen as RNScreen,
|
|
13
|
+
ScreenProps as RNScreenProps,
|
|
14
|
+
ScreenContainer as RNScreenContainer,
|
|
15
|
+
ScreenContainerProps as RNScreenContainerProps,
|
|
16
|
+
} from "react-native-screens";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
ActiveContext,
|
|
20
|
+
DepthContext,
|
|
21
|
+
TabIdContext,
|
|
22
|
+
TabScreenIndexContext,
|
|
23
|
+
} from "./contexts";
|
|
24
|
+
import { useNavigationDispatch, useNavigationState } from "./navigation-store";
|
|
25
|
+
import { generateTabId } from "./utils";
|
|
26
|
+
|
|
27
|
+
type TabsRootProps = {
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
id?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let useTabsInternal = (tabId = "") =>
|
|
33
|
+
useNavigationState((state) => {
|
|
34
|
+
let tab = state.tabs.lookup[tabId];
|
|
35
|
+
|
|
36
|
+
if (!tab) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return tab;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let TabsRoot = React.memo(function TabsRoot({
|
|
44
|
+
children,
|
|
45
|
+
id,
|
|
46
|
+
}: {
|
|
47
|
+
children: React.ReactNode;
|
|
48
|
+
id?: string;
|
|
49
|
+
}) {
|
|
50
|
+
let tabIdRef = React.useRef(id || generateTabId());
|
|
51
|
+
let tabId = tabIdRef.current;
|
|
52
|
+
let tabs = useTabsInternal(tabId);
|
|
53
|
+
let dispatch = useNavigationDispatch();
|
|
54
|
+
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (!tabs) {
|
|
57
|
+
dispatch({ type: "CREATE_TAB_INSTANCE", tabId: tabId });
|
|
58
|
+
}
|
|
59
|
+
}, [tabs, tabId, dispatch]);
|
|
60
|
+
|
|
61
|
+
let depth = React.useContext(DepthContext);
|
|
62
|
+
let isActive = React.useContext(ActiveContext);
|
|
63
|
+
let parentTabId = React.useContext(TabIdContext);
|
|
64
|
+
|
|
65
|
+
React.useEffect(() => {
|
|
66
|
+
if (tabs != null) {
|
|
67
|
+
dispatch({
|
|
68
|
+
type: "REGISTER_TAB",
|
|
69
|
+
depth,
|
|
70
|
+
isActive,
|
|
71
|
+
tabId: tabs.id,
|
|
72
|
+
parentTabId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}, [tabs, depth, isActive, parentTabId, dispatch]);
|
|
76
|
+
|
|
77
|
+
React.useEffect(() => {
|
|
78
|
+
return () => {
|
|
79
|
+
if (tabId != null) {
|
|
80
|
+
dispatch({ type: "UNREGISTER_TAB", tabId });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}, [tabId, dispatch]);
|
|
84
|
+
|
|
85
|
+
if (!tabs) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<TabIdContext.Provider value={tabs.id}>{children}</TabIdContext.Provider>
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let defaultScreenContainerStyle = {
|
|
95
|
+
flex: 1,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function TabsScreens({
|
|
99
|
+
children,
|
|
100
|
+
...props
|
|
101
|
+
}: { children: React.ReactNode } & RNScreenContainerProps) {
|
|
102
|
+
return (
|
|
103
|
+
<RNScreenContainer style={defaultScreenContainerStyle} {...props}>
|
|
104
|
+
{React.Children.map(children, (child, index) => {
|
|
105
|
+
return (
|
|
106
|
+
<TabScreenIndexContext.Provider value={index}>
|
|
107
|
+
{child}
|
|
108
|
+
</TabScreenIndexContext.Provider>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</RNScreenContainer>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let TabsScreen = React.memo(function TabsScreen({
|
|
116
|
+
children,
|
|
117
|
+
style: styleProp,
|
|
118
|
+
...props
|
|
119
|
+
}: { children: React.ReactNode } & RNScreenProps) {
|
|
120
|
+
let dispatch = useNavigationDispatch();
|
|
121
|
+
|
|
122
|
+
let tabId = React.useContext(TabIdContext);
|
|
123
|
+
let tabs = useTabsInternal(tabId);
|
|
124
|
+
let index = React.useContext(TabScreenIndexContext);
|
|
125
|
+
|
|
126
|
+
let parentIsActive = React.useContext(ActiveContext);
|
|
127
|
+
let activeIndex = tabs?.activeIndex;
|
|
128
|
+
let isActive = index === activeIndex;
|
|
129
|
+
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
function backHandler() {
|
|
132
|
+
if (tabs && tabs.history.length > 0) {
|
|
133
|
+
dispatch({ type: "TAB_BACK", tabId });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
BackHandler.addEventListener("hardwareBackPress", backHandler);
|
|
141
|
+
|
|
142
|
+
return () => {
|
|
143
|
+
BackHandler.removeEventListener("hardwareBackPress", backHandler);
|
|
144
|
+
};
|
|
145
|
+
}, [tabId, dispatch, tabs]);
|
|
146
|
+
|
|
147
|
+
let style = React.useMemo(
|
|
148
|
+
() => styleProp || StyleSheet.absoluteFill,
|
|
149
|
+
[styleProp]
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
// @ts-expect-error - Ref typings in RNScreens
|
|
154
|
+
<RNScreen
|
|
155
|
+
active={isActive ? 1 : 0}
|
|
156
|
+
activityState={isActive ? 2 : 0}
|
|
157
|
+
style={style}
|
|
158
|
+
{...props}
|
|
159
|
+
>
|
|
160
|
+
<ActiveContext.Provider value={parentIsActive && isActive}>
|
|
161
|
+
{children}
|
|
162
|
+
</ActiveContext.Provider>
|
|
163
|
+
</RNScreen>
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export let defaultTabbarStyle: ViewStyle = {
|
|
168
|
+
flexDirection: "row",
|
|
169
|
+
backgroundColor: "white",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
let TabsTabbar = React.memo(function TabsTabbar({
|
|
173
|
+
children,
|
|
174
|
+
style: styleProp,
|
|
175
|
+
...props
|
|
176
|
+
}: { children: React.ReactNode } & ViewProps) {
|
|
177
|
+
let style = React.useMemo(() => styleProp || defaultTabbarStyle, [styleProp]);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<View style={style} {...props}>
|
|
181
|
+
{React.Children.map(children, (child, index) => {
|
|
182
|
+
return (
|
|
183
|
+
<TabScreenIndexContext.Provider value={index}>
|
|
184
|
+
{child}
|
|
185
|
+
</TabScreenIndexContext.Provider>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
188
|
+
</View>
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
type TabbarTabProps = {
|
|
193
|
+
activeStyle?: PressableProps["style"];
|
|
194
|
+
inactiveStyle?: PressableProps["style"];
|
|
195
|
+
style?: PressableProps["style"];
|
|
196
|
+
children:
|
|
197
|
+
| React.ReactNode
|
|
198
|
+
| ((props: { isActive: boolean; onPress: () => void }) => React.ReactNode);
|
|
199
|
+
} & Omit<PressableProps, "children">;
|
|
200
|
+
|
|
201
|
+
let defaultTabStyle: ViewStyle = {
|
|
202
|
+
flex: 1,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
let TabsTab = React.memo(function TabsTab({
|
|
206
|
+
children,
|
|
207
|
+
...props
|
|
208
|
+
}: TabbarTabProps) {
|
|
209
|
+
let dispatch = useNavigationDispatch();
|
|
210
|
+
|
|
211
|
+
let tabId = React.useContext(TabIdContext);
|
|
212
|
+
let index = React.useContext(TabScreenIndexContext);
|
|
213
|
+
let tabs = useTabsInternal(tabId);
|
|
214
|
+
|
|
215
|
+
let activeIndex = tabs?.activeIndex;
|
|
216
|
+
let isActive = index === activeIndex;
|
|
217
|
+
|
|
218
|
+
let onPress: () => void = React.useCallback(() => {
|
|
219
|
+
dispatch({ type: "SET_TAB_INDEX", tabId, index });
|
|
220
|
+
}, [tabId, index, dispatch]);
|
|
221
|
+
|
|
222
|
+
let style = React.useMemo(() => {
|
|
223
|
+
let baseStyle = props.style || defaultTabStyle;
|
|
224
|
+
let activeStyle = isActive ? props.activeStyle : props.inactiveStyle;
|
|
225
|
+
return [baseStyle, activeStyle];
|
|
226
|
+
}, [isActive, props.activeStyle, props.inactiveStyle, props.style]);
|
|
227
|
+
|
|
228
|
+
let renderChildren = React.useMemo(() => {
|
|
229
|
+
if (typeof children === "function") {
|
|
230
|
+
return children({ isActive, onPress });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return children;
|
|
234
|
+
}, [isActive, onPress, children]);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
// @ts-expect-error - cleanup typings
|
|
238
|
+
<Pressable onPress={onPress} style={style} {...props}>
|
|
239
|
+
{renderChildren}
|
|
240
|
+
</Pressable>
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
let TabNavigator = React.memo(function TabNavigator({
|
|
246
|
+
screens,
|
|
247
|
+
tabbarPosition = "bottom",
|
|
248
|
+
tabbarStyle,
|
|
249
|
+
...rootProps
|
|
250
|
+
}: TabNavigatorProps) {
|
|
251
|
+
return (
|
|
252
|
+
<Tabs.Root {...rootProps}>
|
|
253
|
+
{tabbarPosition === "top" && (
|
|
254
|
+
<Tabs.Tabbar style={tabbarStyle}>
|
|
255
|
+
{screens.map((screen) => {
|
|
256
|
+
return <Tabs.Tab key={screen.key}>{screen.tab}</Tabs.Tab>;
|
|
257
|
+
})}
|
|
258
|
+
</Tabs.Tabbar>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
<Tabs.Screens>
|
|
262
|
+
{screens.map((screen) => {
|
|
263
|
+
return <Tabs.Screen key={screen.key}>{screen.screen}</Tabs.Screen>;
|
|
264
|
+
})}
|
|
265
|
+
</Tabs.Screens>
|
|
266
|
+
|
|
267
|
+
{tabbarPosition === "bottom" && (
|
|
268
|
+
<Tabs.Tabbar style={tabbarStyle}>
|
|
269
|
+
{screens.map((screen) => {
|
|
270
|
+
return <Tabs.Tab key={screen.key}>{screen.tab}</Tabs.Tab>;
|
|
271
|
+
})}
|
|
272
|
+
</Tabs.Tabbar>
|
|
273
|
+
)}
|
|
274
|
+
</Tabs.Root>
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export let Tabs = {
|
|
279
|
+
Root: TabsRoot,
|
|
280
|
+
Screens: TabsScreens,
|
|
281
|
+
Screen: TabsScreen,
|
|
282
|
+
Tabbar: TabsTabbar,
|
|
283
|
+
Tab: TabsTab,
|
|
284
|
+
Navigator: TabNavigator,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export type TabNavigatorProps = Omit<TabsRootProps, "children"> & {
|
|
288
|
+
screens: TabNavigatorScreenOptions[];
|
|
289
|
+
tabbarPosition?: "top" | "bottom";
|
|
290
|
+
tabbarStyle?: ViewProps["style"];
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type TabNavigatorScreenOptions = {
|
|
294
|
+
key: string;
|
|
295
|
+
screen: React.ReactElement<unknown>;
|
|
296
|
+
tab: (props: { isActive: boolean; onPress: () => void }) => React.ReactNode;
|
|
297
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type PushScreenOptions = {
|
|
2
|
+
stackId?: string;
|
|
3
|
+
slotName?: string;
|
|
4
|
+
screenId?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type StackItem = {
|
|
8
|
+
id: string;
|
|
9
|
+
defaultSlotName?: string;
|
|
10
|
+
screens: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ScreenItem = {
|
|
14
|
+
id: string;
|
|
15
|
+
stackId: string;
|
|
16
|
+
element: React.ReactElement<unknown>;
|
|
17
|
+
slotName?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type TabItem = {
|
|
21
|
+
id: string;
|
|
22
|
+
activeIndex: number;
|
|
23
|
+
history: number[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type NavigationState = {
|
|
27
|
+
stacks: {
|
|
28
|
+
lookup: Record<string, StackItem>;
|
|
29
|
+
ids: string[];
|
|
30
|
+
};
|
|
31
|
+
tabs: {
|
|
32
|
+
lookup: Record<string, TabItem>;
|
|
33
|
+
ids: string[];
|
|
34
|
+
};
|
|
35
|
+
screens: {
|
|
36
|
+
lookup: Record<string, ScreenItem>;
|
|
37
|
+
ids: string[];
|
|
38
|
+
};
|
|
39
|
+
debugModeEnabled: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type RenderCharts = {
|
|
43
|
+
stacksByDepth: Record<string, string[]>;
|
|
44
|
+
tabsByDepth: Record<string, string[]>;
|
|
45
|
+
tabParentsById: Record<string, string>;
|
|
46
|
+
stackParentsById: Record<string, string>;
|
|
47
|
+
stacksByTabIndex: Record<string, string[]>;
|
|
48
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export let generateStackId = createIdGenerator("stack");
|
|
2
|
+
export let generateScreenId = createIdGenerator("screen");
|
|
3
|
+
export let generateTabId = createIdGenerator("tab");
|
|
4
|
+
|
|
5
|
+
function createIdGenerator(name: string) {
|
|
6
|
+
let counter = 0;
|
|
7
|
+
|
|
8
|
+
return function generateId() {
|
|
9
|
+
return name + "-" + counter++;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export let serializeTabIndexKey = (tabId: string, index: number) =>
|
|
14
|
+
`${tabId}-${index}`;
|