@niibase/bottom-sheet-manager 1.2.0 → 1.4.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 +414 -69
- package/lib/commonjs/events.js +100 -15
- package/lib/commonjs/events.js.map +1 -1
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/manager.js +153 -35
- package/lib/commonjs/manager.js.map +1 -1
- package/lib/commonjs/provider.js +92 -54
- package/lib/commonjs/provider.js.map +1 -1
- package/lib/commonjs/router/index.js +80 -21
- package/lib/commonjs/router/index.js.map +1 -1
- package/lib/commonjs/router/router.js +137 -12
- package/lib/commonjs/router/router.js.map +1 -1
- package/lib/commonjs/router/view.js +93 -126
- package/lib/commonjs/router/view.js.map +1 -1
- package/lib/commonjs/sheet.js +122 -98
- package/lib/commonjs/sheet.js.map +1 -1
- package/lib/module/events.js +100 -15
- package/lib/module/events.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/manager.js +154 -35
- package/lib/module/manager.js.map +1 -1
- package/lib/module/provider.js +87 -50
- package/lib/module/provider.js.map +1 -1
- package/lib/module/router/index.js +66 -19
- package/lib/module/router/index.js.map +1 -1
- package/lib/module/router/router.js +135 -11
- package/lib/module/router/router.js.map +1 -1
- package/lib/module/router/view.js +92 -126
- package/lib/module/router/view.js.map +1 -1
- package/lib/module/sheet.js +124 -100
- package/lib/module/sheet.js.map +1 -1
- package/lib/typescript/events.d.ts +46 -12
- package/lib/typescript/events.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/manager.d.ts +73 -7
- package/lib/typescript/manager.d.ts.map +1 -1
- package/lib/typescript/provider.d.ts +22 -16
- package/lib/typescript/provider.d.ts.map +1 -1
- package/lib/typescript/router/index.d.ts +47 -17
- package/lib/typescript/router/index.d.ts.map +1 -1
- package/lib/typescript/router/router.d.ts +44 -5
- package/lib/typescript/router/router.d.ts.map +1 -1
- package/lib/typescript/router/types.d.ts +142 -32
- package/lib/typescript/router/types.d.ts.map +1 -1
- package/lib/typescript/router/view.d.ts +3 -3
- package/lib/typescript/router/view.d.ts.map +1 -1
- package/lib/typescript/sheet.d.ts +1 -1
- package/lib/typescript/sheet.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +52 -21
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +14 -15
- package/src/events.ts +118 -27
- package/src/index.ts +2 -1
- package/src/manager.ts +209 -42
- package/src/provider.tsx +144 -71
- package/src/router/index.tsx +77 -33
- package/src/router/router.ts +188 -15
- package/src/router/types.ts +172 -57
- package/src/router/view.tsx +111 -213
- package/src/sheet.tsx +192 -124
- package/src/types.ts +51 -24
package/src/provider.tsx
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
2
|
-
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
3
|
-
import { StatusBar } from "react-native";
|
|
4
|
-
import React from "react";
|
|
5
1
|
import Animated, {
|
|
6
2
|
interpolate,
|
|
7
3
|
interpolateColor,
|
|
@@ -13,8 +9,18 @@ import Animated, {
|
|
|
13
9
|
withSpring,
|
|
14
10
|
withTiming,
|
|
15
11
|
} from "react-native-reanimated";
|
|
12
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
13
|
+
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
14
|
+
import { StatusBar } from "react-native";
|
|
15
|
+
import React from "react";
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
BottomSheetInstance,
|
|
19
|
+
SheetPayload,
|
|
20
|
+
SheetProviderProps,
|
|
21
|
+
Sheets,
|
|
22
|
+
StackBehavior,
|
|
23
|
+
} from "./types";
|
|
18
24
|
import { eventManager } from "./events";
|
|
19
25
|
|
|
20
26
|
export const providerRegistryStack: string[] = [];
|
|
@@ -46,7 +52,7 @@ export function registerSheet<SheetId extends keyof Sheets = never>(
|
|
|
46
52
|
? (sheetsRegistry[context] = {})
|
|
47
53
|
: sheetsRegistry[context];
|
|
48
54
|
registry[id as string] = Sheet;
|
|
49
|
-
eventManager.
|
|
55
|
+
eventManager.publishAsync(`${context}-on-register`);
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -55,36 +61,28 @@ export function registerSheet<SheetId extends keyof Sheets = never>(
|
|
|
55
61
|
* `global`. However if you want to render a Sheet within another sheet or if you want to render
|
|
56
62
|
* Sheets in a modal. You can use a separate Provider with a custom context value.
|
|
57
63
|
*
|
|
58
|
-
*
|
|
64
|
+
* > **Note:** Context names must be unique across all `SheetProvider` instances.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
59
67
|
* ```ts
|
|
60
68
|
* // Define your SheetProvider in the component/modal where
|
|
61
69
|
* // you want to show some Sheets.
|
|
62
70
|
* <SheetProvider context="local-context" />
|
|
63
71
|
*
|
|
64
|
-
* // Then register your sheet
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* registerSheet('local-sheet', LocalSheet,'local-context');
|
|
68
|
-
*
|
|
72
|
+
* // Then register your sheet at module level (outside JSX):
|
|
73
|
+
* registerSheet('local-sheet', LocalSheet, 'local-context');
|
|
69
74
|
* ```
|
|
70
75
|
*/
|
|
71
76
|
export function SheetProvider({
|
|
72
|
-
iosModalSheetTypeOfAnimation = false,
|
|
73
77
|
context = "global",
|
|
74
|
-
|
|
78
|
+
statusBar,
|
|
79
|
+
scaleConfig,
|
|
75
80
|
children,
|
|
76
|
-
}:
|
|
77
|
-
context?: string;
|
|
78
|
-
duration?: number;
|
|
79
|
-
iosModalSheetTypeOfAnimation?: boolean;
|
|
80
|
-
}>) {
|
|
81
|
+
}: SheetProviderProps) {
|
|
81
82
|
const { top } = useSafeAreaInsets();
|
|
82
83
|
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
|
83
84
|
const sheetIds = Object.keys(sheetsRegistry[context] || sheetsRegistry["global"] || {});
|
|
84
85
|
|
|
85
|
-
// Rerender when a new sheet is added.
|
|
86
|
-
const onRegister = React.useCallback(forceUpdate, [forceUpdate]);
|
|
87
|
-
|
|
88
86
|
// IOS modal sheet type of animation
|
|
89
87
|
const isFullScreen = useSharedValue(-1);
|
|
90
88
|
const colorStyle = useAnimatedStyle(() => ({
|
|
@@ -95,28 +93,52 @@ export function SheetProvider({
|
|
|
95
93
|
["transparent", "#000"],
|
|
96
94
|
),
|
|
97
95
|
}));
|
|
98
|
-
const animatedStyle = useAnimatedStyle(
|
|
99
|
-
|
|
96
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
97
|
+
const radius = interpolate(
|
|
98
|
+
isFullScreen.value,
|
|
99
|
+
[0, 0.3],
|
|
100
|
+
[0, scaleConfig?.borderRadius ?? 24],
|
|
101
|
+
"clamp",
|
|
102
|
+
);
|
|
103
|
+
const scale = interpolate(
|
|
104
|
+
isFullScreen.value,
|
|
105
|
+
[0.5, 0.8],
|
|
106
|
+
[1, scaleConfig?.scale ?? 0.92],
|
|
107
|
+
"clamp",
|
|
108
|
+
);
|
|
109
|
+
const translateY = interpolate(
|
|
110
|
+
isFullScreen.value,
|
|
111
|
+
[0.5, 0.7],
|
|
112
|
+
[0, top + (scaleConfig?.translateY ?? 5)],
|
|
113
|
+
"clamp",
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return {
|
|
100
117
|
flex: 1,
|
|
101
118
|
overflow: "hidden",
|
|
102
|
-
borderRadius:
|
|
119
|
+
borderRadius:
|
|
120
|
+
scaleConfig?.animation?.type === "spring"
|
|
121
|
+
? withSpring(radius, scaleConfig.animation.config)
|
|
122
|
+
: withTiming(radius, scaleConfig?.animation?.config ?? { duration: 200 }),
|
|
103
123
|
transform: [
|
|
104
124
|
{
|
|
105
|
-
scaleX:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
scaleX:
|
|
126
|
+
scaleConfig?.animation?.type === "spring"
|
|
127
|
+
? withSpring(scale, scaleConfig.animation.config)
|
|
128
|
+
: withTiming(scale, scaleConfig?.animation?.config ?? { duration: 200 }),
|
|
109
129
|
},
|
|
110
130
|
{
|
|
111
|
-
translateY:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
translateY:
|
|
132
|
+
scaleConfig?.animation?.type === "spring"
|
|
133
|
+
? withSpring(translateY, scaleConfig.animation.config)
|
|
134
|
+
: withTiming(
|
|
135
|
+
translateY,
|
|
136
|
+
scaleConfig?.animation?.config ?? { duration: 200 },
|
|
137
|
+
),
|
|
115
138
|
},
|
|
116
139
|
],
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
);
|
|
140
|
+
};
|
|
141
|
+
}, [top, scaleConfig]);
|
|
120
142
|
|
|
121
143
|
// Since background color is white, we need to set status bar to light
|
|
122
144
|
const setStatusBar = StatusBar.setBarStyle;
|
|
@@ -124,8 +146,11 @@ export function SheetProvider({
|
|
|
124
146
|
() => isFullScreen.value,
|
|
125
147
|
(currentValue) => {
|
|
126
148
|
"worklet";
|
|
127
|
-
if (currentValue
|
|
128
|
-
runOnJS(setStatusBar)(
|
|
149
|
+
if (currentValue >= 0) {
|
|
150
|
+
runOnJS(setStatusBar)(
|
|
151
|
+
currentValue >= 0.5 ? "light-content" : (statusBar ?? "default"),
|
|
152
|
+
true,
|
|
153
|
+
);
|
|
129
154
|
}
|
|
130
155
|
},
|
|
131
156
|
[],
|
|
@@ -135,40 +160,55 @@ export function SheetProvider({
|
|
|
135
160
|
providerRegistryStack.indexOf(context) > -1
|
|
136
161
|
? providerRegistryStack.indexOf(context)
|
|
137
162
|
: providerRegistryStack.push(context) - 1;
|
|
138
|
-
const unsub = eventManager.subscribe(`${context}-on-register`,
|
|
163
|
+
const unsub = eventManager.subscribe(`${context}-on-register`, forceUpdate);
|
|
139
164
|
return () => {
|
|
140
165
|
providerRegistryStack.splice(providerRegistryStack.indexOf(context), 1);
|
|
141
166
|
unsub?.unsubscribe();
|
|
142
167
|
};
|
|
143
|
-
}, [context,
|
|
168
|
+
}, [context, forceUpdate]);
|
|
144
169
|
|
|
145
170
|
return (
|
|
146
|
-
<
|
|
147
|
-
value={{ isFullScreen, iosModalSheetTypeOfAnimation }}
|
|
148
|
-
>
|
|
171
|
+
<SheetSharedContext.Provider value={{ isFullScreen, topInset: top }}>
|
|
149
172
|
<Animated.View style={colorStyle}>
|
|
150
173
|
<Animated.View style={animatedStyle}>{children}</Animated.View>
|
|
151
174
|
</Animated.View>
|
|
152
175
|
<BottomSheetModalProvider>
|
|
153
176
|
{sheetIds.map((id) => (
|
|
154
|
-
<RenderSheet key={id} id={id} context={context}
|
|
177
|
+
<RenderSheet key={id} id={id} context={context} />
|
|
155
178
|
))}
|
|
156
179
|
</BottomSheetModalProvider>
|
|
157
|
-
</
|
|
180
|
+
</SheetSharedContext.Provider>
|
|
158
181
|
);
|
|
159
182
|
}
|
|
183
|
+
|
|
160
184
|
const ProviderContext = React.createContext("global");
|
|
161
185
|
const SheetIDContext = React.createContext<string | undefined>(undefined);
|
|
162
|
-
const
|
|
163
|
-
iosModalSheetTypeOfAnimation: boolean;
|
|
186
|
+
const SheetSharedContext = React.createContext<{
|
|
164
187
|
isFullScreen: SharedValue<number>;
|
|
165
|
-
|
|
188
|
+
topInset: number;
|
|
189
|
+
}>({
|
|
190
|
+
isFullScreen: { value: 0 } as SharedValue<number>,
|
|
191
|
+
topInset: 0,
|
|
192
|
+
});
|
|
166
193
|
|
|
167
194
|
export const SheetRefContext = React.createContext<
|
|
168
195
|
React.RefObject<BottomSheetInstance | null>
|
|
169
|
-
>({} as
|
|
196
|
+
>({} as React.RefObject<BottomSheetInstance | null>);
|
|
170
197
|
|
|
171
|
-
const SheetPayloadContext = React.createContext<
|
|
198
|
+
const SheetPayloadContext = React.createContext<unknown>(undefined);
|
|
199
|
+
|
|
200
|
+
// Stack behavior context for managing sheet transitions
|
|
201
|
+
interface StackBehaviorContextValue {
|
|
202
|
+
behavior: StackBehavior;
|
|
203
|
+
isTransitioning: boolean;
|
|
204
|
+
previousSheetId: string | null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const StackBehaviorContext = React.createContext<StackBehaviorContextValue>({
|
|
208
|
+
behavior: "switch",
|
|
209
|
+
isTransitioning: false,
|
|
210
|
+
previousSheetId: null,
|
|
211
|
+
});
|
|
172
212
|
|
|
173
213
|
/**
|
|
174
214
|
* Get id of the current context.
|
|
@@ -181,9 +221,14 @@ export const useSheetIDContext = () => React.useContext(SheetIDContext);
|
|
|
181
221
|
/**
|
|
182
222
|
* Get the current sheet animation context.
|
|
183
223
|
*/
|
|
184
|
-
export const
|
|
224
|
+
export const useSheetSharedContext = () => React.useContext(SheetSharedContext);
|
|
225
|
+
/**
|
|
226
|
+
* Get stack behavior context for the current sheet.
|
|
227
|
+
*/
|
|
228
|
+
export const useStackBehaviorContext = () => React.useContext(StackBehaviorContext);
|
|
185
229
|
/**
|
|
186
230
|
* Get the current Sheet's internal ref.
|
|
231
|
+
* Note: `current` may be null before the sheet is fully mounted.
|
|
187
232
|
*/
|
|
188
233
|
export const useSheetRef = <
|
|
189
234
|
SheetId extends keyof Sheets = never,
|
|
@@ -205,25 +250,26 @@ export function useSheetPayload<SheetId extends keyof Sheets = never>() {
|
|
|
205
250
|
export function useOnSheet<SheetId extends keyof Sheets = never>(
|
|
206
251
|
id: SheetId | (string & {}),
|
|
207
252
|
type: "show" | "hide" | "onclose",
|
|
208
|
-
listener: (payload: SheetPayload<SheetId>, context: string, ...args:
|
|
253
|
+
listener: (payload: SheetPayload<SheetId>, context: string, ...args: unknown[]) => void,
|
|
209
254
|
) {
|
|
210
255
|
React.useEffect(() => {
|
|
211
256
|
const subscription = eventManager.subscribe(`${type}_${id}`, listener);
|
|
212
257
|
return () => subscription.unsubscribe();
|
|
213
|
-
}, [id, listener]);
|
|
258
|
+
}, [id, listener, type]);
|
|
214
259
|
}
|
|
215
260
|
|
|
216
|
-
|
|
217
|
-
id,
|
|
218
|
-
context,
|
|
219
|
-
duration,
|
|
220
|
-
}: {
|
|
261
|
+
interface RenderSheetProps {
|
|
221
262
|
id: string;
|
|
222
263
|
context: string;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const RenderSheet = ({ id, context }: RenderSheetProps) => {
|
|
267
|
+
const [payload, setPayload] = React.useState<unknown>();
|
|
226
268
|
const [visible, setVisible] = React.useState(false);
|
|
269
|
+
const [stackBehavior, setStackBehavior] = React.useState<StackBehavior>("switch");
|
|
270
|
+
const [isPending, startTransition] = React.useTransition();
|
|
271
|
+
const [previousSheetId, setPreviousSheetId] = React.useState<string | null>(null);
|
|
272
|
+
|
|
227
273
|
const ref = React.useRef<BottomSheetInstance | null>(null);
|
|
228
274
|
const Sheet = context.startsWith("$$-auto-")
|
|
229
275
|
? sheetsRegistry?.global?.[id]
|
|
@@ -232,29 +278,46 @@ const RenderSheet = ({
|
|
|
232
278
|
: undefined;
|
|
233
279
|
|
|
234
280
|
const onShow = React.useCallback(
|
|
235
|
-
(data:
|
|
281
|
+
(data: unknown, ctx = "global", reopened?: boolean, behavior?: StackBehavior) => {
|
|
236
282
|
if (ctx !== context) return;
|
|
237
|
-
|
|
238
|
-
|
|
283
|
+
|
|
284
|
+
if (behavior) {
|
|
285
|
+
setStackBehavior(behavior);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!reopened) {
|
|
289
|
+
setPayload(data);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Smooth transition handling using React's useTransition
|
|
293
|
+
startTransition(() => {
|
|
294
|
+
setVisible(true);
|
|
295
|
+
});
|
|
239
296
|
},
|
|
240
297
|
[context],
|
|
241
298
|
);
|
|
242
299
|
|
|
243
300
|
const onClose = React.useCallback(
|
|
244
|
-
(_data:
|
|
301
|
+
(_data: unknown, ctx = "global", reopened?: boolean, nextSheetId?: string) => {
|
|
245
302
|
if (context !== ctx) return;
|
|
303
|
+
|
|
304
|
+
if (nextSheetId) {
|
|
305
|
+
setPreviousSheetId(nextSheetId);
|
|
306
|
+
}
|
|
307
|
+
|
|
246
308
|
if (!reopened) {
|
|
247
309
|
setPayload(undefined);
|
|
248
|
-
|
|
310
|
+
setVisible(false);
|
|
249
311
|
} else {
|
|
250
312
|
setVisible(false);
|
|
313
|
+
setPreviousSheetId(null);
|
|
251
314
|
}
|
|
252
315
|
},
|
|
253
316
|
[context],
|
|
254
317
|
);
|
|
255
318
|
|
|
256
319
|
const onHide = React.useCallback(
|
|
257
|
-
(data:
|
|
320
|
+
(data: unknown, ctx = "global") => {
|
|
258
321
|
eventManager.publish(`hide_${id}`, data, ctx);
|
|
259
322
|
},
|
|
260
323
|
[id],
|
|
@@ -267,7 +330,7 @@ const RenderSheet = ({
|
|
|
267
330
|
}, [context, id, payload, visible]);
|
|
268
331
|
|
|
269
332
|
React.useEffect(() => {
|
|
270
|
-
|
|
333
|
+
const subs = [
|
|
271
334
|
eventManager.subscribe(`show_wrap_${id}`, onShow),
|
|
272
335
|
eventManager.subscribe(`onclose_${id}`, onClose),
|
|
273
336
|
eventManager.subscribe(`hide_wrap_${id}`, onHide),
|
|
@@ -279,15 +342,25 @@ const RenderSheet = ({
|
|
|
279
342
|
|
|
280
343
|
if (!Sheet) return null;
|
|
281
344
|
|
|
282
|
-
|
|
345
|
+
const stackContextValue: StackBehaviorContextValue = {
|
|
346
|
+
behavior: stackBehavior,
|
|
347
|
+
isTransitioning: isPending,
|
|
348
|
+
previousSheetId,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
if (!visible) return null;
|
|
352
|
+
|
|
353
|
+
return (
|
|
283
354
|
<ProviderContext.Provider value={context}>
|
|
284
355
|
<SheetIDContext.Provider value={id}>
|
|
285
356
|
<SheetRefContext.Provider value={ref}>
|
|
286
357
|
<SheetPayloadContext.Provider value={payload}>
|
|
287
|
-
<
|
|
358
|
+
<StackBehaviorContext.Provider value={stackContextValue}>
|
|
359
|
+
<Sheet id={id} payload={payload} context={context} />
|
|
360
|
+
</StackBehaviorContext.Provider>
|
|
288
361
|
</SheetPayloadContext.Provider>
|
|
289
362
|
</SheetRefContext.Provider>
|
|
290
363
|
</SheetIDContext.Provider>
|
|
291
364
|
</ProviderContext.Provider>
|
|
292
|
-
)
|
|
365
|
+
);
|
|
293
366
|
};
|
package/src/router/index.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createNavigatorFactory,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
StaticConfig,
|
|
6
|
-
TypedNavigator,
|
|
3
|
+
EventArg,
|
|
4
|
+
StackActions,
|
|
7
5
|
useNavigationBuilder,
|
|
6
|
+
type NavigatorTypeBagBase,
|
|
7
|
+
type ParamListBase,
|
|
8
|
+
type StaticConfig,
|
|
9
|
+
type TypedNavigator,
|
|
8
10
|
} from "@react-navigation/native";
|
|
9
|
-
import React from "react";
|
|
11
|
+
import * as React from "react";
|
|
10
12
|
|
|
11
|
-
import { BottomSheetRouter, BottomSheetRouterOptions } from "./router";
|
|
12
13
|
import type {
|
|
13
14
|
BottomSheetActionHelpers,
|
|
14
15
|
BottomSheetNavigationEventMap,
|
|
@@ -17,13 +18,23 @@ import type {
|
|
|
17
18
|
BottomSheetNavigationState,
|
|
18
19
|
BottomSheetNavigatorProps,
|
|
19
20
|
} from "./types";
|
|
21
|
+
import { BottomSheetRouter, type BottomSheetRouterOptions } from "./router";
|
|
20
22
|
import { BottomSheetView } from "./view";
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Unified Navigator that renders the first screen as the base content
|
|
26
|
+
* and all subsequent screens as bottom sheet overlays.
|
|
27
|
+
*
|
|
28
|
+
* Uses BottomSheetRouter (extending StackRouter) to manage navigation state,
|
|
29
|
+
* with react-native-screens ScreenStack for the base screen rendering
|
|
30
|
+
* and BottomSheetView for the sheet overlays.
|
|
31
|
+
*/
|
|
32
|
+
function Navigator({
|
|
23
33
|
id,
|
|
24
34
|
children,
|
|
25
35
|
screenListeners,
|
|
26
36
|
screenOptions,
|
|
37
|
+
initialRouteName,
|
|
27
38
|
...rest
|
|
28
39
|
}: BottomSheetNavigatorProps) {
|
|
29
40
|
const { state, descriptors, navigation, NavigationContent } = useNavigationBuilder<
|
|
@@ -37,8 +48,30 @@ function BottomSheetNavigator({
|
|
|
37
48
|
children,
|
|
38
49
|
screenListeners,
|
|
39
50
|
screenOptions,
|
|
51
|
+
initialRouteName,
|
|
40
52
|
});
|
|
41
53
|
|
|
54
|
+
// Handle tab press → popToTop behavior
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
// @ts-expect-error: there may not be a tab navigator in parent
|
|
57
|
+
return navigation?.addListener?.("tabPress", (e: any) => {
|
|
58
|
+
const isFocused = navigation.isFocused();
|
|
59
|
+
|
|
60
|
+
requestAnimationFrame(() => {
|
|
61
|
+
if (
|
|
62
|
+
state.index > 0 &&
|
|
63
|
+
isFocused &&
|
|
64
|
+
!(e as EventArg<"tabPress", true>).defaultPrevented
|
|
65
|
+
) {
|
|
66
|
+
navigation.dispatch({
|
|
67
|
+
...StackActions.popToTop(),
|
|
68
|
+
target: state.key,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}, [navigation, state.index, state.key]);
|
|
74
|
+
|
|
42
75
|
return (
|
|
43
76
|
<NavigationContent>
|
|
44
77
|
<BottomSheetView
|
|
@@ -52,18 +85,38 @@ function BottomSheetNavigator({
|
|
|
52
85
|
}
|
|
53
86
|
|
|
54
87
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
88
|
+
* Creates a bottom sheet navigator that renders the first screen as the
|
|
89
|
+
* main content and all subsequent screens as bottom sheet modals.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* // With React Navigation
|
|
94
|
+
* const { Navigator, Screen } = createBottomSheetNavigator();
|
|
95
|
+
*
|
|
96
|
+
* function App() {
|
|
97
|
+
* return (
|
|
98
|
+
* <Navigator>
|
|
99
|
+
* <Screen name="Home" component={HomeScreen} />
|
|
100
|
+
* <Screen
|
|
101
|
+
* name="Details"
|
|
102
|
+
* component={DetailsSheet}
|
|
103
|
+
* options={{ snapPoints: ['50%', '100%'] }}
|
|
104
|
+
* />
|
|
105
|
+
* </Navigator>
|
|
106
|
+
* );
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
57
109
|
*
|
|
58
110
|
* @example
|
|
59
111
|
* ```tsx
|
|
112
|
+
* // With Expo Router
|
|
113
|
+
* import { Slot, withLayoutContext } from "expo-router";
|
|
60
114
|
* import {
|
|
61
115
|
* createBottomSheetNavigator,
|
|
62
116
|
* BottomSheetNavigationOptions,
|
|
63
117
|
* BottomSheetNavigationEventMap,
|
|
64
118
|
* BottomSheetNavigationState,
|
|
65
|
-
* } from "@
|
|
66
|
-
* import { Slot, withLayoutContext } from "expo-router";
|
|
119
|
+
* } from "@niibase/bottom-sheet-manager";
|
|
67
120
|
*
|
|
68
121
|
* const { Navigator } = createBottomSheetNavigator();
|
|
69
122
|
*
|
|
@@ -79,36 +132,30 @@ function BottomSheetNavigator({
|
|
|
79
132
|
* };
|
|
80
133
|
*
|
|
81
134
|
* export default function Layout() {
|
|
135
|
+
* // SSR guard - navigator doesn't work on server
|
|
82
136
|
* if (typeof window === "undefined") return <Slot />;
|
|
137
|
+
*
|
|
83
138
|
* return (
|
|
84
|
-
* <BottomSheet
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*. />
|
|
139
|
+
* <BottomSheet>
|
|
140
|
+
* <BottomSheet.Screen name="index" />
|
|
141
|
+
* <BottomSheet.Screen
|
|
142
|
+
* name="details"
|
|
143
|
+
* options={{ snapPoints: ["50%"] }}
|
|
144
|
+
* />
|
|
145
|
+
* </BottomSheet>
|
|
92
146
|
* );
|
|
93
147
|
* }
|
|
94
148
|
* ```
|
|
95
149
|
*/
|
|
96
150
|
export function createBottomSheetNavigator<
|
|
97
151
|
const ParamList extends ParamListBase,
|
|
98
|
-
const NavigatorID extends string | undefined = undefined,
|
|
99
|
-
// We'll define a type bag specialized for bottom sheets:
|
|
152
|
+
const NavigatorID extends string | undefined = string | undefined,
|
|
100
153
|
const TypeBag extends NavigatorTypeBagBase = {
|
|
101
|
-
// The param list from the user
|
|
102
154
|
ParamList: ParamList;
|
|
103
|
-
// Optional ID for this navigator
|
|
104
155
|
NavigatorID: NavigatorID;
|
|
105
|
-
// The state shape
|
|
106
156
|
State: BottomSheetNavigationState<ParamList>;
|
|
107
|
-
// The screen options
|
|
108
157
|
ScreenOptions: BottomSheetNavigationOptions;
|
|
109
|
-
// The event map
|
|
110
158
|
EventMap: BottomSheetNavigationEventMap;
|
|
111
|
-
// The type of the "navigation" object used by each screen in the navigator
|
|
112
159
|
NavigationList: {
|
|
113
160
|
[RouteName in keyof ParamList]: BottomSheetNavigationProp<
|
|
114
161
|
ParamList,
|
|
@@ -116,15 +163,12 @@ export function createBottomSheetNavigator<
|
|
|
116
163
|
NavigatorID
|
|
117
164
|
>;
|
|
118
165
|
};
|
|
119
|
-
|
|
120
|
-
Navigator: typeof BottomSheetNavigator;
|
|
166
|
+
Navigator: typeof Navigator;
|
|
121
167
|
},
|
|
122
|
-
// The static config allows for "static" route config
|
|
123
168
|
const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>,
|
|
124
169
|
>(config?: Config): TypedNavigator<TypeBag, Config> {
|
|
125
|
-
|
|
126
|
-
// but pass in the config to get the typed container
|
|
127
|
-
return createNavigatorFactory(BottomSheetNavigator)(config);
|
|
170
|
+
return createNavigatorFactory(Navigator)(config);
|
|
128
171
|
}
|
|
129
172
|
|
|
130
173
|
export * from "./types";
|
|
174
|
+
export { BottomSheetActions, useBottomSheetNavigation } from "./router";
|