@niibase/bottom-sheet-manager 1.1.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/LICENSE +21 -0
- package/README.md +147 -0
- package/lib/commonjs/events.js +35 -0
- package/lib/commonjs/events.js.map +1 -0
- package/lib/commonjs/index.js +85 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/manager.js +171 -0
- package/lib/commonjs/manager.js.map +1 -0
- package/lib/commonjs/provider.js +229 -0
- package/lib/commonjs/provider.js.map +1 -0
- package/lib/commonjs/router/index.js +100 -0
- package/lib/commonjs/router/index.js.map +1 -0
- package/lib/commonjs/router/router.js +72 -0
- package/lib/commonjs/router/router.js.map +1 -0
- package/lib/commonjs/router/types.js +6 -0
- package/lib/commonjs/router/types.js.map +1 -0
- package/lib/commonjs/router/view.js +180 -0
- package/lib/commonjs/router/view.js.map +1 -0
- package/lib/commonjs/sheet.js +240 -0
- package/lib/commonjs/sheet.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/events.js +29 -0
- package/lib/module/events.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/manager.js +165 -0
- package/lib/module/manager.js.map +1 -0
- package/lib/module/provider.js +210 -0
- package/lib/module/provider.js.map +1 -0
- package/lib/module/router/index.js +79 -0
- package/lib/module/router/index.js.map +1 -0
- package/lib/module/router/router.js +65 -0
- package/lib/module/router/router.js.map +1 -0
- package/lib/module/router/types.js +2 -0
- package/lib/module/router/types.js.map +1 -0
- package/lib/module/router/view.js +173 -0
- package/lib/module/router/view.js.map +1 -0
- package/lib/module/sheet.js +232 -0
- package/lib/module/sheet.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/events.d.ts +16 -0
- package/lib/typescript/events.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/manager.d.ts +78 -0
- package/lib/typescript/manager.d.ts.map +1 -0
- package/lib/typescript/provider.d.ts +69 -0
- package/lib/typescript/provider.d.ts.map +1 -0
- package/lib/typescript/router/index.d.ts +59 -0
- package/lib/typescript/router/index.d.ts.map +1 -0
- package/lib/typescript/router/router.d.ts +47 -0
- package/lib/typescript/router/router.d.ts.map +1 -0
- package/lib/typescript/router/types.d.ts +46 -0
- package/lib/typescript/router/types.d.ts.map +1 -0
- package/lib/typescript/router/view.d.ts +11 -0
- package/lib/typescript/router/view.d.ts.map +1 -0
- package/lib/typescript/sheet.d.ts +19 -0
- package/lib/typescript/sheet.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +125 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +102 -0
- package/src/events.ts +40 -0
- package/src/index.ts +11 -0
- package/src/manager.ts +223 -0
- package/src/provider.tsx +293 -0
- package/src/router/index.tsx +130 -0
- package/src/router/router.ts +94 -0
- package/src/router/types.ts +117 -0
- package/src/router/view.tsx +265 -0
- package/src/sheet.tsx +350 -0
- package/src/types.ts +153 -0
package/src/provider.tsx
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
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
|
+
import Animated, {
|
|
6
|
+
interpolate,
|
|
7
|
+
interpolateColor,
|
|
8
|
+
runOnJS,
|
|
9
|
+
SharedValue,
|
|
10
|
+
useAnimatedReaction,
|
|
11
|
+
useAnimatedStyle,
|
|
12
|
+
useSharedValue,
|
|
13
|
+
withSpring,
|
|
14
|
+
withTiming,
|
|
15
|
+
} from "react-native-reanimated";
|
|
16
|
+
|
|
17
|
+
import { BottomSheetInstance, SheetPayload, Sheets } from "./types";
|
|
18
|
+
import { eventManager } from "./events";
|
|
19
|
+
|
|
20
|
+
export const providerRegistryStack: string[] = [];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* An object that holds all the sheet components against their ids.
|
|
24
|
+
*/
|
|
25
|
+
export const sheetsRegistry: {
|
|
26
|
+
[context: string]: { [id: string]: React.ElementType };
|
|
27
|
+
} = {
|
|
28
|
+
global: {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface SheetProps<SheetId extends keyof Sheets = never> {
|
|
32
|
+
sheetId: SheetId;
|
|
33
|
+
payload?: Sheets[SheetId]["payload"];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Registers your Sheet with the SheetProvider.
|
|
37
|
+
export function registerSheet<SheetId extends keyof Sheets = never>(
|
|
38
|
+
id: SheetId | (string & {}),
|
|
39
|
+
Sheet: React.ElementType,
|
|
40
|
+
...contexts: string[]
|
|
41
|
+
) {
|
|
42
|
+
if (!id || !Sheet) return;
|
|
43
|
+
if (!contexts || contexts.length === 0) contexts = ["global"];
|
|
44
|
+
for (let context of contexts) {
|
|
45
|
+
const registry = !sheetsRegistry[context]
|
|
46
|
+
? (sheetsRegistry[context] = {})
|
|
47
|
+
: sheetsRegistry[context];
|
|
48
|
+
registry[id as string] = Sheet;
|
|
49
|
+
eventManager.publish(`${context}-on-register`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The SheetProvider makes available the sheets in a given context. The default context is
|
|
55
|
+
* `global`. However if you want to render a Sheet within another sheet or if you want to render
|
|
56
|
+
* Sheets in a modal. You can use a separate Provider with a custom context value.
|
|
57
|
+
*
|
|
58
|
+
* For example
|
|
59
|
+
* ```ts
|
|
60
|
+
* // Define your SheetProvider in the component/modal where
|
|
61
|
+
* // you want to show some Sheets.
|
|
62
|
+
* <SheetProvider context="local-context" />
|
|
63
|
+
*
|
|
64
|
+
* // Then register your sheet when for example the
|
|
65
|
+
* // Modal component renders.
|
|
66
|
+
*
|
|
67
|
+
* registerSheet('local-sheet', LocalSheet,'local-context');
|
|
68
|
+
*
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function SheetProvider({
|
|
72
|
+
iosModalSheetTypeOfAnimation = false,
|
|
73
|
+
context = "global",
|
|
74
|
+
duration = 300,
|
|
75
|
+
children,
|
|
76
|
+
}: React.PropsWithChildren<{
|
|
77
|
+
context?: string;
|
|
78
|
+
duration?: number;
|
|
79
|
+
iosModalSheetTypeOfAnimation?: boolean;
|
|
80
|
+
}>) {
|
|
81
|
+
const { top } = useSafeAreaInsets();
|
|
82
|
+
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
|
83
|
+
const sheetIds = Object.keys(sheetsRegistry[context] || sheetsRegistry["global"] || {});
|
|
84
|
+
|
|
85
|
+
// Rerender when a new sheet is added.
|
|
86
|
+
const onRegister = React.useCallback(forceUpdate, [forceUpdate]);
|
|
87
|
+
|
|
88
|
+
// IOS modal sheet type of animation
|
|
89
|
+
const isFullScreen = useSharedValue(-1);
|
|
90
|
+
const colorStyle = useAnimatedStyle(() => ({
|
|
91
|
+
flex: 1,
|
|
92
|
+
backgroundColor: interpolateColor(
|
|
93
|
+
isFullScreen.value,
|
|
94
|
+
[0, 1],
|
|
95
|
+
["transparent", "#000"],
|
|
96
|
+
),
|
|
97
|
+
}));
|
|
98
|
+
const animatedStyle = useAnimatedStyle(
|
|
99
|
+
() => ({
|
|
100
|
+
flex: 1,
|
|
101
|
+
overflow: "hidden",
|
|
102
|
+
borderRadius: interpolate(isFullScreen.value, [0, 0.8, 1], [0, 20, 24], "clamp"),
|
|
103
|
+
transform: [
|
|
104
|
+
{
|
|
105
|
+
scaleX: withTiming(
|
|
106
|
+
interpolate(isFullScreen.value, [0, 0.98, 1], [1, 1, 0.92], "clamp"),
|
|
107
|
+
{ duration },
|
|
108
|
+
),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
translateY: withSpring(
|
|
112
|
+
interpolate(isFullScreen.value, [0, 0.99, 1], [0, top, top + 5], "clamp"),
|
|
113
|
+
{ duration, dampingRatio: 1.5 },
|
|
114
|
+
),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
[duration],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Since background color is white, we need to set status bar to light
|
|
122
|
+
const setStatusBar = StatusBar.setBarStyle;
|
|
123
|
+
useAnimatedReaction(
|
|
124
|
+
() => isFullScreen.value,
|
|
125
|
+
(currentValue) => {
|
|
126
|
+
"worklet";
|
|
127
|
+
if (currentValue > -1) {
|
|
128
|
+
runOnJS(setStatusBar)(currentValue >= 0.5 ? "light-content" : "default");
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
[],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
providerRegistryStack.indexOf(context) > -1
|
|
136
|
+
? providerRegistryStack.indexOf(context)
|
|
137
|
+
: providerRegistryStack.push(context) - 1;
|
|
138
|
+
const unsub = eventManager.subscribe(`${context}-on-register`, onRegister);
|
|
139
|
+
return () => {
|
|
140
|
+
providerRegistryStack.splice(providerRegistryStack.indexOf(context), 1);
|
|
141
|
+
unsub?.unsubscribe();
|
|
142
|
+
};
|
|
143
|
+
}, [context, onRegister]);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<SheetAnimationContext.Provider
|
|
147
|
+
value={{ isFullScreen, iosModalSheetTypeOfAnimation }}
|
|
148
|
+
>
|
|
149
|
+
<Animated.View style={colorStyle}>
|
|
150
|
+
<Animated.View style={animatedStyle}>{children}</Animated.View>
|
|
151
|
+
</Animated.View>
|
|
152
|
+
<BottomSheetModalProvider>
|
|
153
|
+
{sheetIds.map((id) => (
|
|
154
|
+
<RenderSheet key={id} id={id} context={context} duration={duration} />
|
|
155
|
+
))}
|
|
156
|
+
</BottomSheetModalProvider>
|
|
157
|
+
</SheetAnimationContext.Provider>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const ProviderContext = React.createContext("global");
|
|
161
|
+
const SheetIDContext = React.createContext<string | undefined>(undefined);
|
|
162
|
+
const SheetAnimationContext = React.createContext<{
|
|
163
|
+
iosModalSheetTypeOfAnimation: boolean;
|
|
164
|
+
isFullScreen: SharedValue<number>;
|
|
165
|
+
}>({ isFullScreen: { value: 0 } as any, iosModalSheetTypeOfAnimation: false });
|
|
166
|
+
|
|
167
|
+
export const SheetRefContext = React.createContext<
|
|
168
|
+
React.RefObject<BottomSheetInstance | null>
|
|
169
|
+
>({} as any);
|
|
170
|
+
|
|
171
|
+
const SheetPayloadContext = React.createContext<any>(undefined);
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get id of the current context.
|
|
175
|
+
*/
|
|
176
|
+
export const useProviderContext = () => React.useContext(ProviderContext);
|
|
177
|
+
/**
|
|
178
|
+
* Get id of the current sheet
|
|
179
|
+
*/
|
|
180
|
+
export const useSheetIDContext = () => React.useContext(SheetIDContext);
|
|
181
|
+
/**
|
|
182
|
+
* Get the current sheet animation context.
|
|
183
|
+
*/
|
|
184
|
+
export const useSheetAnimationContext = () => React.useContext(SheetAnimationContext);
|
|
185
|
+
/**
|
|
186
|
+
* Get the current Sheet's internal ref.
|
|
187
|
+
*/
|
|
188
|
+
export const useSheetRef = <
|
|
189
|
+
SheetId extends keyof Sheets = never,
|
|
190
|
+
>(): React.MutableRefObject<BottomSheetInstance<SheetId>> =>
|
|
191
|
+
React.useContext(SheetRefContext) as React.MutableRefObject<
|
|
192
|
+
BottomSheetInstance<SheetId>
|
|
193
|
+
>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get the payload this sheet was opened with.
|
|
197
|
+
*/
|
|
198
|
+
export function useSheetPayload<SheetId extends keyof Sheets = never>() {
|
|
199
|
+
return React.useContext(SheetPayloadContext) as Sheets[SheetId]["payload"];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Listen to sheet events.
|
|
204
|
+
*/
|
|
205
|
+
export function useOnSheet<SheetId extends keyof Sheets = never>(
|
|
206
|
+
id: SheetId | (string & {}),
|
|
207
|
+
type: "show" | "hide" | "onclose",
|
|
208
|
+
listener: (payload: SheetPayload<SheetId>, context: string, ...args: any[]) => void,
|
|
209
|
+
) {
|
|
210
|
+
React.useEffect(() => {
|
|
211
|
+
const subscription = eventManager.subscribe(`${type}_${id}`, listener);
|
|
212
|
+
return () => subscription.unsubscribe();
|
|
213
|
+
}, [id, listener]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const RenderSheet = ({
|
|
217
|
+
id,
|
|
218
|
+
context,
|
|
219
|
+
duration,
|
|
220
|
+
}: {
|
|
221
|
+
id: string;
|
|
222
|
+
context: string;
|
|
223
|
+
duration: number;
|
|
224
|
+
}) => {
|
|
225
|
+
const [payload, setPayload] = React.useState();
|
|
226
|
+
const [visible, setVisible] = React.useState(false);
|
|
227
|
+
const ref = React.useRef<BottomSheetInstance | null>(null);
|
|
228
|
+
const Sheet = context.startsWith("$$-auto-")
|
|
229
|
+
? sheetsRegistry?.global?.[id]
|
|
230
|
+
: sheetsRegistry[context]
|
|
231
|
+
? sheetsRegistry[context]?.[id]
|
|
232
|
+
: undefined;
|
|
233
|
+
|
|
234
|
+
const onShow = React.useCallback(
|
|
235
|
+
(data: any, ctx = "global", reopened?: boolean) => {
|
|
236
|
+
if (ctx !== context) return;
|
|
237
|
+
if (!reopened) setPayload(data);
|
|
238
|
+
setVisible(true);
|
|
239
|
+
},
|
|
240
|
+
[context],
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const onClose = React.useCallback(
|
|
244
|
+
(_data: any, ctx = "global", reopened?: boolean) => {
|
|
245
|
+
if (context !== ctx) return;
|
|
246
|
+
if (!reopened) {
|
|
247
|
+
setPayload(undefined);
|
|
248
|
+
setTimeout(() => setVisible(false), Math.max(duration ?? 300, 300));
|
|
249
|
+
} else {
|
|
250
|
+
setVisible(false);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
[context],
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const onHide = React.useCallback(
|
|
257
|
+
(data: any, ctx = "global") => {
|
|
258
|
+
eventManager.publish(`hide_${id}`, data, ctx);
|
|
259
|
+
},
|
|
260
|
+
[id],
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
React.useEffect(() => {
|
|
264
|
+
if (visible) {
|
|
265
|
+
eventManager.publish(`show_${id}`, payload, context);
|
|
266
|
+
}
|
|
267
|
+
}, [context, id, payload, visible]);
|
|
268
|
+
|
|
269
|
+
React.useEffect(() => {
|
|
270
|
+
let subs = [
|
|
271
|
+
eventManager.subscribe(`show_wrap_${id}`, onShow),
|
|
272
|
+
eventManager.subscribe(`onclose_${id}`, onClose),
|
|
273
|
+
eventManager.subscribe(`hide_wrap_${id}`, onHide),
|
|
274
|
+
];
|
|
275
|
+
return () => {
|
|
276
|
+
subs.forEach((s) => s.unsubscribe());
|
|
277
|
+
};
|
|
278
|
+
}, [id, context, onShow, onHide, onClose]);
|
|
279
|
+
|
|
280
|
+
if (!Sheet) return null;
|
|
281
|
+
|
|
282
|
+
return visible ? (
|
|
283
|
+
<ProviderContext.Provider value={context}>
|
|
284
|
+
<SheetIDContext.Provider value={id}>
|
|
285
|
+
<SheetRefContext.Provider value={ref}>
|
|
286
|
+
<SheetPayloadContext.Provider value={payload}>
|
|
287
|
+
<Sheet id={id} payload={payload} context={context} />
|
|
288
|
+
</SheetPayloadContext.Provider>
|
|
289
|
+
</SheetRefContext.Provider>
|
|
290
|
+
</SheetIDContext.Provider>
|
|
291
|
+
</ProviderContext.Provider>
|
|
292
|
+
) : null;
|
|
293
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNavigatorFactory,
|
|
3
|
+
NavigatorTypeBagBase,
|
|
4
|
+
ParamListBase,
|
|
5
|
+
StaticConfig,
|
|
6
|
+
TypedNavigator,
|
|
7
|
+
useNavigationBuilder,
|
|
8
|
+
} from "@react-navigation/native";
|
|
9
|
+
import React from "react";
|
|
10
|
+
|
|
11
|
+
import { BottomSheetRouter, BottomSheetRouterOptions } from "./router";
|
|
12
|
+
import type {
|
|
13
|
+
BottomSheetActionHelpers,
|
|
14
|
+
BottomSheetNavigationEventMap,
|
|
15
|
+
BottomSheetNavigationOptions,
|
|
16
|
+
BottomSheetNavigationProp,
|
|
17
|
+
BottomSheetNavigationState,
|
|
18
|
+
BottomSheetNavigatorProps,
|
|
19
|
+
} from "./types";
|
|
20
|
+
import { BottomSheetView } from "./view";
|
|
21
|
+
|
|
22
|
+
function BottomSheetNavigator({
|
|
23
|
+
id,
|
|
24
|
+
children,
|
|
25
|
+
screenListeners,
|
|
26
|
+
screenOptions,
|
|
27
|
+
...rest
|
|
28
|
+
}: BottomSheetNavigatorProps) {
|
|
29
|
+
const { state, descriptors, navigation, NavigationContent } = useNavigationBuilder<
|
|
30
|
+
BottomSheetNavigationState<ParamListBase>,
|
|
31
|
+
BottomSheetRouterOptions,
|
|
32
|
+
BottomSheetActionHelpers<ParamListBase>,
|
|
33
|
+
BottomSheetNavigationOptions,
|
|
34
|
+
BottomSheetNavigationEventMap
|
|
35
|
+
>(BottomSheetRouter, {
|
|
36
|
+
id,
|
|
37
|
+
children,
|
|
38
|
+
screenListeners,
|
|
39
|
+
screenOptions,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<NavigationContent>
|
|
44
|
+
<BottomSheetView
|
|
45
|
+
{...rest}
|
|
46
|
+
state={state}
|
|
47
|
+
navigation={navigation}
|
|
48
|
+
descriptors={descriptors}
|
|
49
|
+
/>
|
|
50
|
+
</NavigationContent>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* To use BottomSheetNavigator with expo-router, the first screen should be your app content
|
|
56
|
+
* and add a border radius of 24px to the root view if want to snap to 100%.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* import {
|
|
61
|
+
* createBottomSheetNavigator,
|
|
62
|
+
* BottomSheetNavigationOptions,
|
|
63
|
+
* BottomSheetNavigationEventMap,
|
|
64
|
+
* BottomSheetNavigationState,
|
|
65
|
+
* } from "@repo/bottom-sheet";
|
|
66
|
+
* import { Slot, withLayoutContext } from "expo-router";
|
|
67
|
+
*
|
|
68
|
+
* const { Navigator } = createBottomSheetNavigator();
|
|
69
|
+
*
|
|
70
|
+
* const BottomSheet = withLayoutContext<
|
|
71
|
+
* BottomSheetNavigationOptions,
|
|
72
|
+
* typeof Navigator,
|
|
73
|
+
* BottomSheetNavigationState<any>,
|
|
74
|
+
* BottomSheetNavigationEventMap
|
|
75
|
+
* >(Navigator);
|
|
76
|
+
*
|
|
77
|
+
* export const unstable_settings = {
|
|
78
|
+
* initialRouteName: "index",
|
|
79
|
+
* };
|
|
80
|
+
*
|
|
81
|
+
* export default function Layout() {
|
|
82
|
+
* if (typeof window === "undefined") return <Slot />;
|
|
83
|
+
* return (
|
|
84
|
+
* <BottomSheet
|
|
85
|
+
* screenOptions={
|
|
86
|
+
* {
|
|
87
|
+
* // API Reference: `@repo/design/bottom-sheet/types.ts`
|
|
88
|
+
* // And: https://gorhom.github.io/react-native-bottom-sheet/modal/props/
|
|
89
|
+
* }
|
|
90
|
+
* }
|
|
91
|
+
*. />
|
|
92
|
+
* );
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function createBottomSheetNavigator<
|
|
97
|
+
const ParamList extends ParamListBase,
|
|
98
|
+
const NavigatorID extends string | undefined = undefined,
|
|
99
|
+
// We'll define a type bag specialized for bottom sheets:
|
|
100
|
+
const TypeBag extends NavigatorTypeBagBase = {
|
|
101
|
+
// The param list from the user
|
|
102
|
+
ParamList: ParamList;
|
|
103
|
+
// Optional ID for this navigator
|
|
104
|
+
NavigatorID: NavigatorID;
|
|
105
|
+
// The state shape
|
|
106
|
+
State: BottomSheetNavigationState<ParamList>;
|
|
107
|
+
// The screen options
|
|
108
|
+
ScreenOptions: BottomSheetNavigationOptions;
|
|
109
|
+
// The event map
|
|
110
|
+
EventMap: BottomSheetNavigationEventMap;
|
|
111
|
+
// The type of the "navigation" object used by each screen in the navigator
|
|
112
|
+
NavigationList: {
|
|
113
|
+
[RouteName in keyof ParamList]: BottomSheetNavigationProp<
|
|
114
|
+
ParamList,
|
|
115
|
+
RouteName,
|
|
116
|
+
NavigatorID
|
|
117
|
+
>;
|
|
118
|
+
};
|
|
119
|
+
// The navigator component
|
|
120
|
+
Navigator: typeof BottomSheetNavigator;
|
|
121
|
+
},
|
|
122
|
+
// The static config allows for "static" route config
|
|
123
|
+
const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>,
|
|
124
|
+
>(config?: Config): TypedNavigator<TypeBag, Config> {
|
|
125
|
+
// We call `createNavigatorFactory` with our un-typed navigator
|
|
126
|
+
// but pass in the config to get the typed container
|
|
127
|
+
return createNavigatorFactory(BottomSheetNavigator)(config);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export * from "./types";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ParamListBase,
|
|
3
|
+
Router,
|
|
4
|
+
StackActions,
|
|
5
|
+
StackActionType,
|
|
6
|
+
StackRouter,
|
|
7
|
+
StackRouterOptions,
|
|
8
|
+
} from "@react-navigation/native";
|
|
9
|
+
import { nanoid } from "nanoid/non-secure";
|
|
10
|
+
|
|
11
|
+
import type { BottomSheetNavigationState } from "./types";
|
|
12
|
+
|
|
13
|
+
export type BottomSheetRouterOptions = StackRouterOptions;
|
|
14
|
+
|
|
15
|
+
export type BottomSheetActionType =
|
|
16
|
+
| StackActionType
|
|
17
|
+
| {
|
|
18
|
+
type: "SNAP_TO";
|
|
19
|
+
index: number;
|
|
20
|
+
source?: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const BottomSheetActions = {
|
|
25
|
+
...StackActions,
|
|
26
|
+
snapTo(index: number): BottomSheetActionType {
|
|
27
|
+
return { type: "SNAP_TO", index };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function BottomSheetRouter(
|
|
32
|
+
routerOptions: StackRouterOptions,
|
|
33
|
+
): Router<BottomSheetNavigationState<ParamListBase>, BottomSheetActionType> {
|
|
34
|
+
const baseRouter = StackRouter(routerOptions) as unknown as Router<
|
|
35
|
+
BottomSheetNavigationState<ParamListBase>,
|
|
36
|
+
BottomSheetActionType
|
|
37
|
+
>;
|
|
38
|
+
return {
|
|
39
|
+
...baseRouter,
|
|
40
|
+
type: "bottom-sheet",
|
|
41
|
+
getInitialState(options) {
|
|
42
|
+
const state = baseRouter.getInitialState(options);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
...state,
|
|
46
|
+
stale: false,
|
|
47
|
+
type: "bottom-sheet",
|
|
48
|
+
key: `bottom-sheet-${nanoid()}`,
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
getStateForAction(state, action, options) {
|
|
52
|
+
switch (action.type) {
|
|
53
|
+
case "SNAP_TO": {
|
|
54
|
+
const index =
|
|
55
|
+
action.target === state.key && action.source
|
|
56
|
+
? state.routes.findIndex((r) => r.key === action.source)
|
|
57
|
+
: state.index;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
...state,
|
|
61
|
+
routes: state.routes.map((route, i) =>
|
|
62
|
+
i === index
|
|
63
|
+
? {
|
|
64
|
+
...route,
|
|
65
|
+
snapToIndex: action.index,
|
|
66
|
+
}
|
|
67
|
+
: route,
|
|
68
|
+
),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
default:
|
|
72
|
+
return baseRouter.getStateForAction(state, action, options);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
getRehydratedState(partialState, { routeNames, routeParamList, routeGetIdList }) {
|
|
76
|
+
if (partialState.stale === false) {
|
|
77
|
+
return partialState;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const state = baseRouter.getRehydratedState(partialState, {
|
|
81
|
+
routeNames,
|
|
82
|
+
routeParamList,
|
|
83
|
+
routeGetIdList,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
...state,
|
|
88
|
+
type: "bottom-sheet",
|
|
89
|
+
key: `bottom-sheet-${nanoid()}`,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
actionCreators: BottomSheetActions,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { BottomSheetModalProps } from "@gorhom/bottom-sheet";
|
|
2
|
+
import type {
|
|
3
|
+
DefaultNavigatorOptions,
|
|
4
|
+
Descriptor,
|
|
5
|
+
NavigationHelpers,
|
|
6
|
+
NavigationProp,
|
|
7
|
+
NavigationState,
|
|
8
|
+
ParamListBase,
|
|
9
|
+
RouteProp,
|
|
10
|
+
StackActionHelpers,
|
|
11
|
+
} from "@react-navigation/native";
|
|
12
|
+
|
|
13
|
+
// TODO: Sheet open / close / snap / events.
|
|
14
|
+
export type BottomSheetNavigationEventMap = {};
|
|
15
|
+
|
|
16
|
+
export type BottomSheetNavigationState<ParamList extends ParamListBase> = Omit<
|
|
17
|
+
NavigationState<ParamList>,
|
|
18
|
+
"routes"
|
|
19
|
+
> & {
|
|
20
|
+
type: "bottom-sheet";
|
|
21
|
+
routes: (NavigationState<ParamList>["routes"][number] & {
|
|
22
|
+
snapToIndex?: number | null;
|
|
23
|
+
})[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type BottomSheetActionHelpers<ParamList extends ParamListBase> =
|
|
27
|
+
StackActionHelpers<ParamList> & {
|
|
28
|
+
/**
|
|
29
|
+
* Snap the drawer to a point.
|
|
30
|
+
*/
|
|
31
|
+
snapTo(index?: number): void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type BottomSheetNavigationProp<
|
|
35
|
+
ParamList extends ParamListBase,
|
|
36
|
+
RouteName extends keyof ParamList = string,
|
|
37
|
+
NavigatorID extends string | undefined = undefined,
|
|
38
|
+
> = NavigationProp<
|
|
39
|
+
ParamList,
|
|
40
|
+
RouteName,
|
|
41
|
+
NavigatorID,
|
|
42
|
+
BottomSheetNavigationState<ParamList>,
|
|
43
|
+
BottomSheetNavigationOptions,
|
|
44
|
+
BottomSheetNavigationEventMap
|
|
45
|
+
> &
|
|
46
|
+
BottomSheetActionHelpers<ParamList>;
|
|
47
|
+
|
|
48
|
+
export type BottomSheetScreenProps<
|
|
49
|
+
ParamList extends ParamListBase,
|
|
50
|
+
RouteName extends keyof ParamList = string,
|
|
51
|
+
NavigatorID extends string | undefined = undefined,
|
|
52
|
+
> = {
|
|
53
|
+
navigation: BottomSheetNavigationProp<ParamList, RouteName, NavigatorID>;
|
|
54
|
+
route: RouteProp<ParamList, RouteName>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type BottomSheetNavigationHelpers = NavigationHelpers<
|
|
58
|
+
ParamListBase,
|
|
59
|
+
BottomSheetNavigationEventMap
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
// We want it to be an empty object because navigator does not have any additional props
|
|
63
|
+
export type BottomSheetNavigationConfig = {};
|
|
64
|
+
|
|
65
|
+
export type BottomSheetNavigationOptions = Omit<
|
|
66
|
+
BottomSheetModalProps,
|
|
67
|
+
// Remove some props that aren't useful as navigation options.
|
|
68
|
+
| "containerHeight"
|
|
69
|
+
| "snapPoints"
|
|
70
|
+
| "gestureEventsHandlersHook"
|
|
71
|
+
| "animatedPosition"
|
|
72
|
+
| "animatedIndex"
|
|
73
|
+
| "topInset"
|
|
74
|
+
| "onChange"
|
|
75
|
+
| "onAnimate"
|
|
76
|
+
| "onClose"
|
|
77
|
+
| "children"
|
|
78
|
+
| "$modal"
|
|
79
|
+
| "waitFor"
|
|
80
|
+
| "simultaneousHandlers"
|
|
81
|
+
> & {
|
|
82
|
+
/**
|
|
83
|
+
* Points for the bottom sheet to snap to. It accepts array of number, string or mix.
|
|
84
|
+
* String values should be a percentage.
|
|
85
|
+
* @example
|
|
86
|
+
* snapPoints={[200, 500]}
|
|
87
|
+
* snapPoints={[200, '%50']}
|
|
88
|
+
* snapPoints={['%100']}
|
|
89
|
+
* @type Array<string | number>
|
|
90
|
+
*/
|
|
91
|
+
snapPoints?: Array<string | number>;
|
|
92
|
+
/**
|
|
93
|
+
* When `true`, tapping on the backdrop will not dismiss the modal.
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
clickThrough?: boolean;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type BottomSheetNavigatorProps = DefaultNavigatorOptions<
|
|
100
|
+
ParamListBase,
|
|
101
|
+
undefined, // or your ID if you want a named ID, e.g. 'BottomSheetNavigator'
|
|
102
|
+
BottomSheetNavigationState<ParamListBase>,
|
|
103
|
+
BottomSheetNavigationOptions,
|
|
104
|
+
BottomSheetNavigationEventMap,
|
|
105
|
+
BottomSheetNavigationHelpers
|
|
106
|
+
> &
|
|
107
|
+
BottomSheetNavigationConfig;
|
|
108
|
+
|
|
109
|
+
export type BottomSheetDescriptor = Descriptor<
|
|
110
|
+
BottomSheetNavigationOptions,
|
|
111
|
+
BottomSheetNavigationProp<ParamListBase>,
|
|
112
|
+
RouteProp<ParamListBase>
|
|
113
|
+
>;
|
|
114
|
+
|
|
115
|
+
export type BottomSheetDescriptorMap = {
|
|
116
|
+
[key: string]: BottomSheetDescriptor;
|
|
117
|
+
};
|