@swmansion/react-native-bottom-sheet 0.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/lib/module/BottomSheet.js +8 -0
  4. package/lib/module/BottomSheet.js.map +1 -0
  5. package/lib/module/BottomSheetBase.js +309 -0
  6. package/lib/module/BottomSheetBase.js.map +1 -0
  7. package/lib/module/BottomSheetContext.js +13 -0
  8. package/lib/module/BottomSheetContext.js.map +1 -0
  9. package/lib/module/BottomSheetFlatList.js +29 -0
  10. package/lib/module/BottomSheetFlatList.js.map +1 -0
  11. package/lib/module/BottomSheetProvider.js +73 -0
  12. package/lib/module/BottomSheetProvider.js.map +1 -0
  13. package/lib/module/BottomSheetScrollView.js +29 -0
  14. package/lib/module/BottomSheetScrollView.js.map +1 -0
  15. package/lib/module/ModalBottomSheet.js +13 -0
  16. package/lib/module/ModalBottomSheet.js.map +1 -0
  17. package/lib/module/index.js +8 -0
  18. package/lib/module/index.js.map +1 -0
  19. package/lib/module/package.json +1 -0
  20. package/lib/module/useBottomSheetScrollable.js +56 -0
  21. package/lib/module/useBottomSheetScrollable.js.map +1 -0
  22. package/lib/typescript/package.json +1 -0
  23. package/lib/typescript/src/BottomSheet.d.ts +4 -0
  24. package/lib/typescript/src/BottomSheet.d.ts.map +1 -0
  25. package/lib/typescript/src/BottomSheetBase.d.ts +18 -0
  26. package/lib/typescript/src/BottomSheetBase.d.ts.map +1 -0
  27. package/lib/typescript/src/BottomSheetContext.d.ts +17 -0
  28. package/lib/typescript/src/BottomSheetContext.d.ts.map +1 -0
  29. package/lib/typescript/src/BottomSheetFlatList.d.ts +3 -0
  30. package/lib/typescript/src/BottomSheetFlatList.d.ts.map +1 -0
  31. package/lib/typescript/src/BottomSheetProvider.d.ts +8 -0
  32. package/lib/typescript/src/BottomSheetProvider.d.ts.map +1 -0
  33. package/lib/typescript/src/BottomSheetScrollView.d.ts +3 -0
  34. package/lib/typescript/src/BottomSheetScrollView.d.ts.map +1 -0
  35. package/lib/typescript/src/ModalBottomSheet.d.ts +8 -0
  36. package/lib/typescript/src/ModalBottomSheet.d.ts.map +1 -0
  37. package/lib/typescript/src/index.d.ts +9 -0
  38. package/lib/typescript/src/index.d.ts.map +1 -0
  39. package/lib/typescript/src/useBottomSheetScrollable.d.ts +10 -0
  40. package/lib/typescript/src/useBottomSheetScrollable.d.ts.map +1 -0
  41. package/package.json +117 -0
  42. package/src/BottomSheet.tsx +8 -0
  43. package/src/BottomSheetBase.tsx +378 -0
  44. package/src/BottomSheetContext.tsx +30 -0
  45. package/src/BottomSheetFlatList.tsx +24 -0
  46. package/src/BottomSheetProvider.tsx +83 -0
  47. package/src/BottomSheetScrollView.tsx +24 -0
  48. package/src/ModalBottomSheet.tsx +16 -0
  49. package/src/index.tsx +8 -0
  50. package/src/useBottomSheetScrollable.ts +62 -0
@@ -0,0 +1,24 @@
1
+ import { GestureDetector } from 'react-native-gesture-handler';
2
+ import type { FlatListPropsWithLayout } from 'react-native-reanimated';
3
+ import Animated from 'react-native-reanimated';
4
+
5
+ import { useBottomSheetScrollable } from './useBottomSheetScrollable';
6
+
7
+ export const BottomSheetFlatList = <T,>(
8
+ props: Omit<FlatListPropsWithLayout<T>, 'onScroll'>
9
+ ) => {
10
+ const { scrollEnabled, ...rest } = props;
11
+ const { scrollHandler, scrollableRef, nativeGesture, animatedProps } =
12
+ useBottomSheetScrollable(scrollEnabled);
13
+ return (
14
+ <GestureDetector gesture={nativeGesture}>
15
+ <Animated.FlatList
16
+ {...rest}
17
+ animatedProps={animatedProps}
18
+ ref={scrollableRef}
19
+ onScroll={scrollHandler}
20
+ scrollEventThrottle={16}
21
+ />
22
+ </GestureDetector>
23
+ );
24
+ };
@@ -0,0 +1,83 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useId,
6
+ useReducer,
7
+ useState,
8
+ } from 'react';
9
+ import type { ReactNode } from 'react';
10
+ import { StyleSheet, View } from 'react-native';
11
+
12
+ interface PortalContextType {
13
+ addPortal: (key: string, element: ReactNode) => void;
14
+ removePortal: (key: string) => void;
15
+ subscribe: (callback: () => void) => () => void;
16
+ getPortals: () => Map<string, ReactNode>;
17
+ }
18
+
19
+ const PortalContext = createContext<PortalContextType | null>(null);
20
+
21
+ const PortalHost = () => {
22
+ const context = useContext(PortalContext)!;
23
+ const [, forceRender] = useReducer((x: number) => x + 1, 0);
24
+ useEffect(() => {
25
+ return context.subscribe(forceRender);
26
+ }, [context]);
27
+ return Array.from(context.getPortals().entries()).map(([key, element]) => (
28
+ <View key={key} style={StyleSheet.absoluteFill} pointerEvents="box-none">
29
+ {element}
30
+ </View>
31
+ ));
32
+ };
33
+
34
+ export const BottomSheetProvider = ({ children }: { children: ReactNode }) => {
35
+ const [context] = useState<PortalContextType>(() => {
36
+ const portals = new Map<string, ReactNode>();
37
+ const subscribers = new Set<() => void>();
38
+ const notify = () => {
39
+ subscribers.forEach((subscriber) => subscriber());
40
+ };
41
+ return {
42
+ addPortal: (key, element) => {
43
+ portals.set(key, element);
44
+ notify();
45
+ },
46
+ removePortal: (key) => {
47
+ portals.delete(key);
48
+ notify();
49
+ },
50
+ subscribe: (callback) => {
51
+ subscribers.add(callback);
52
+ return () => {
53
+ subscribers.delete(callback);
54
+ };
55
+ },
56
+ getPortals: () => portals,
57
+ };
58
+ });
59
+ return (
60
+ <PortalContext.Provider value={context}>
61
+ {children}
62
+ <PortalHost />
63
+ </PortalContext.Provider>
64
+ );
65
+ };
66
+
67
+ export const Portal = ({ children }: { children: ReactNode }) => {
68
+ const context = useContext(PortalContext);
69
+ if (context === null) {
70
+ throw new Error('`Portal` must be used within `BottomSheetProvider`.');
71
+ }
72
+ const { addPortal, removePortal } = context;
73
+ const id = useId();
74
+ useEffect(() => {
75
+ addPortal(id, children);
76
+ }, [id, children, addPortal]);
77
+ useEffect(() => {
78
+ return () => {
79
+ removePortal(id);
80
+ };
81
+ }, [id, removePortal]);
82
+ return null;
83
+ };
@@ -0,0 +1,24 @@
1
+ import type { ScrollViewProps } from 'react-native';
2
+ import { GestureDetector } from 'react-native-gesture-handler';
3
+ import Animated from 'react-native-reanimated';
4
+
5
+ import { useBottomSheetScrollable } from './useBottomSheetScrollable';
6
+
7
+ export const BottomSheetScrollView = (
8
+ props: Omit<ScrollViewProps, 'onScroll'>
9
+ ) => {
10
+ const { scrollEnabled, ...rest } = props;
11
+ const { scrollHandler, scrollableRef, nativeGesture, animatedProps } =
12
+ useBottomSheetScrollable(scrollEnabled);
13
+ return (
14
+ <GestureDetector gesture={nativeGesture}>
15
+ <Animated.ScrollView
16
+ {...rest}
17
+ animatedProps={animatedProps}
18
+ ref={scrollableRef}
19
+ onScroll={scrollHandler}
20
+ scrollEventThrottle={16}
21
+ />
22
+ </GestureDetector>
23
+ );
24
+ };
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+
4
+ import type { BottomSheetCommonProps } from './BottomSheetBase';
5
+ import { BottomSheetBase } from './BottomSheetBase';
6
+
7
+ export interface ModalBottomSheetProps extends BottomSheetCommonProps {
8
+ scrim?: (progress: SharedValue<number>) => ReactNode;
9
+ }
10
+
11
+ export const ModalBottomSheet = ({
12
+ scrim,
13
+ ...props
14
+ }: ModalBottomSheetProps) => (
15
+ <BottomSheetBase {...props} modal renderScrim={scrim} />
16
+ );
package/src/index.tsx ADDED
@@ -0,0 +1,8 @@
1
+ export { BottomSheet } from './BottomSheet';
2
+ export type { BottomSheetProps } from './BottomSheet';
3
+ export { ModalBottomSheet } from './ModalBottomSheet';
4
+ export type { ModalBottomSheetProps } from './ModalBottomSheet';
5
+ export { BottomSheetProvider } from './BottomSheetProvider';
6
+ export { BottomSheetFlatList } from './BottomSheetFlatList';
7
+ export { BottomSheetScrollView } from './BottomSheetScrollView';
8
+ export type { Detent } from './BottomSheetBase';
@@ -0,0 +1,62 @@
1
+ import { useEffect } from 'react';
2
+ import { Gesture } from 'react-native-gesture-handler';
3
+ import {
4
+ type SharedValue,
5
+ useAnimatedProps,
6
+ useAnimatedScrollHandler,
7
+ } from 'react-native-reanimated';
8
+
9
+ import { useBottomSheetContext } from './BottomSheetContext';
10
+
11
+ export const useBottomSheetScrollable = (
12
+ baseScrollEnabled: boolean | SharedValue<boolean | undefined> = true
13
+ ) => {
14
+ const {
15
+ scrollOffset,
16
+ scrollableRef,
17
+ hasScrollable,
18
+ isScrollableGestureActive,
19
+ isScrollableLocked,
20
+ panGesture,
21
+ } = useBottomSheetContext();
22
+ const scrollHandler = useAnimatedScrollHandler({
23
+ onScroll: (event) => {
24
+ 'worklet';
25
+ scrollOffset.set(Math.max(0, event.contentOffset.y));
26
+ },
27
+ });
28
+ const nativeGesture = Gesture.Native()
29
+ .simultaneousWithExternalGesture(panGesture)
30
+ .onStart(() => {
31
+ 'worklet';
32
+ isScrollableGestureActive.set(true);
33
+ })
34
+ .onFinalize(() => {
35
+ 'worklet';
36
+ isScrollableGestureActive.set(false);
37
+ });
38
+ const animatedProps = useAnimatedProps(() => {
39
+ const resolvedScrollEnabled =
40
+ typeof baseScrollEnabled === 'object' && baseScrollEnabled !== null
41
+ ? baseScrollEnabled.value ?? true
42
+ : baseScrollEnabled ?? true;
43
+ return {
44
+ scrollEnabled: resolvedScrollEnabled && !isScrollableLocked.value,
45
+ };
46
+ });
47
+ useEffect(() => {
48
+ if (__DEV__ && hasScrollable.value) {
49
+ console.warn(
50
+ 'Multiple scrollables within a single bottom sheet. Only one `BottomSheetScrollView` ' +
51
+ 'or `BottomSheetFlatList` is supported per bottom sheet.'
52
+ );
53
+ }
54
+ hasScrollable.set(true);
55
+ return () => {
56
+ hasScrollable.set(false);
57
+ isScrollableGestureActive.set(false);
58
+ isScrollableLocked.set(false);
59
+ };
60
+ }, [hasScrollable, isScrollableGestureActive, isScrollableLocked]);
61
+ return { scrollHandler, scrollableRef, nativeGesture, animatedProps };
62
+ };