react-native-bottom-sheet-modal-lite 1.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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ - Initial public release.
6
+ - Spring animations powered by React Native Reanimated.
7
+ - Pan gesture powered by React Native Gesture Handler.
8
+ - Backdrop press and pan-down dismissal.
9
+ - Keyboard avoidance props for replacing project-specific keyboard hooks.
10
+ - TypeScript declarations included.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nattawat Virunsuntornkul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # react-native-bottom-sheet-modal-lite
2
+
3
+ A spring-animated bottom sheet modal for React Native, powered by `react-native-reanimated` and `react-native-gesture-handler`.
4
+
5
+ The default layout matches the original component:
6
+
7
+ - 300px visible height
8
+ - 30px draggable top area
9
+ - 30px top corner radius
10
+ - Transparent sheet background
11
+ - Dark 50% backdrop
12
+ - 200px hidden section below the screen
13
+ - Spring open, close, and snap-back animation
14
+ - Tap backdrop or drag down to dismiss
15
+ - Keyboard avoidance support
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ npm install react-native-bottom-sheet-modal-lite react-native-reanimated react-native-gesture-handler
21
+ ```
22
+
23
+ For iOS, install CocoaPods after adding native dependencies:
24
+
25
+ ```sh
26
+ cd ios && pod install && cd ..
27
+ ```
28
+
29
+ Follow the Reanimated installation instructions for the version used by your app. Reanimated 4 also requires a compatible `react-native-worklets` version.
30
+
31
+ Wrap the app root with `GestureHandlerRootView`:
32
+
33
+ ```tsx
34
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
35
+
36
+ export default function App() {
37
+ return (
38
+ <GestureHandlerRootView style={{ flex: 1 }}>
39
+ <RootNavigator />
40
+ </GestureHandlerRootView>
41
+ );
42
+ }
43
+ ```
44
+
45
+ The component also places a `GestureHandlerRootView` inside the native modal so gestures continue to work in modal content.
46
+
47
+ ## Basic usage
48
+
49
+ ```tsx
50
+ import React, { useState } from 'react';
51
+ import { Button, Text, View } from 'react-native';
52
+ import { BottomSheetModal } from 'react-native-bottom-sheet-modal-lite';
53
+
54
+ export default function ExampleScreen() {
55
+ const [visible, setVisible] = useState(false);
56
+
57
+ return (
58
+ <View style={{ flex: 1, justifyContent: 'center' }}>
59
+ <Button title="Open" onPress={() => setVisible(true)} />
60
+
61
+ <BottomSheetModal
62
+ visible={visible}
63
+ height={300}
64
+ backgroundColor="transparent"
65
+ onDismiss={() => setVisible(false)}
66
+ >
67
+ <View
68
+ style={{
69
+ flex: 1,
70
+ padding: 20,
71
+ backgroundColor: '#FFFFFF',
72
+ }}
73
+ >
74
+ <Text>Bottom sheet content</Text>
75
+ </View>
76
+ </BottomSheetModal>
77
+ </View>
78
+ );
79
+ }
80
+ ```
81
+
82
+ A default export is also available:
83
+
84
+ ```tsx
85
+ import BottomSheetModal from 'react-native-bottom-sheet-modal-lite';
86
+ ```
87
+
88
+ ## Replacing a project-specific keyboard hook
89
+
90
+ The original component used a local hook such as:
91
+
92
+ ```tsx
93
+ const { behavior, offset } = useKeyboardAvoidConfig({ hasHeader: true });
94
+ ```
95
+
96
+ An npm package cannot import a hook from the consumer's project. Pass its values through props instead:
97
+
98
+ ```tsx
99
+ const { behavior, offset } = useKeyboardAvoidConfig({ hasHeader: true });
100
+
101
+ <BottomSheetModal
102
+ visible={visible}
103
+ onDismiss={() => setVisible(false)}
104
+ keyboardBehavior={behavior}
105
+ keyboardVerticalOffset={offset}
106
+ >
107
+ {children}
108
+ </BottomSheetModal>
109
+ ```
110
+
111
+ Without these props, the package defaults to `padding` on iOS, `height` on Android, and an offset of `0`.
112
+
113
+ ## Props
114
+
115
+ | Prop | Type | Default | Description |
116
+ | --- | --- | --- | --- |
117
+ | `visible` | `boolean` | required | Controls modal visibility. |
118
+ | `onDismiss` | `() => void` | required | Called after a user dismisses the sheet. |
119
+ | `children` | `ReactNode` | required | Sheet content. |
120
+ | `height` | `number` | `300` | Visible sheet height. |
121
+ | `backgroundColor` | `string` | `transparent` | Sheet background color. |
122
+ | `backdropColor` | `string` | `rgba(0, 0, 0, 0.5)` | Backdrop color. |
123
+ | `closeOnBackdropPress` | `boolean` | `true` | Allows backdrop press to dismiss. |
124
+ | `enablePanDownToClose` | `boolean` | `true` | Allows dragging the handle down to dismiss. |
125
+ | `dragHandleHeight` | `number` | `30` | Height of the draggable top area. |
126
+ | `extraHeight` | `number` | `200` | Hidden height rendered below the screen. |
127
+ | `borderRadius` | `number` | `30` | Top corner radius. |
128
+ | `dismissThreshold` | `number` | `0.5` | Drag-distance ratio required to dismiss. |
129
+ | `dismissVelocity` | `number` | `900` | Downward velocity required to dismiss. |
130
+ | `keyboardBehavior` | `height \| position \| padding` | platform default | `KeyboardAvoidingView` behavior. |
131
+ | `keyboardVerticalOffset` | `number` | `0` | Keyboard offset. |
132
+ | `keyboardAvoidingEnabled` | `boolean` | `true` | Enables keyboard avoidance. |
133
+ | `containerStyle` | `StyleProp<ViewStyle>` | — | Sheet container style. |
134
+ | `backdropStyle` | `StyleProp<ViewStyle>` | — | Backdrop style. |
135
+ | `dragHandleStyle` | `StyleProp<ViewStyle>` | — | Draggable area style. |
136
+ | `contentContainerStyle` | `StyleProp<ViewStyle>` | — | Children wrapper style. |
137
+
138
+ ## Local verification
139
+
140
+ ```sh
141
+ npm install
142
+ npm run typecheck
143
+ npm run build
144
+ npm run pack:check
145
+ ```
146
+
147
+ Create a tarball:
148
+
149
+ ```sh
150
+ npm pack
151
+ ```
152
+
153
+ Install it in a React Native app:
154
+
155
+ ```sh
156
+ npm install /absolute/path/react-native-bottom-sheet-modal-lite-1.0.0.tgz
157
+ ```
158
+
159
+ ## Publishing
160
+
161
+ ```sh
162
+ npm login
163
+ npm publish
164
+ ```
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { KeyboardAvoidingView, Modal, Platform, Pressable, StyleSheet, useWindowDimensions, View } from 'react-native';
5
+ import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
6
+ import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const DEFAULT_HEIGHT = 300;
9
+ const DEFAULT_DRAG_HANDLE_HEIGHT = 30;
10
+ const DEFAULT_EXTRA_HEIGHT = 200;
11
+ const DEFAULT_BORDER_RADIUS = 30;
12
+ const DEFAULT_DISMISS_VELOCITY = 900;
13
+ const SPRING_CONFIG = {
14
+ damping: 28,
15
+ stiffness: 300,
16
+ mass: 0.4,
17
+ overshootClamping: true
18
+ };
19
+ /**
20
+ * A spring-animated React Native bottom sheet modal powered by Reanimated and
21
+ * React Native Gesture Handler.
22
+ *
23
+ * The default layout intentionally matches the original component: a 30px
24
+ * draggable top area, 30px top radius, transparent sheet background, and an
25
+ * extra 200px section rendered below the screen.
26
+ */
27
+ export function BottomSheetModal({
28
+ visible,
29
+ onDismiss,
30
+ children,
31
+ backgroundColor = 'transparent',
32
+ height = DEFAULT_HEIGHT,
33
+ backdropColor = 'rgba(0, 0, 0, 0.5)',
34
+ closeOnBackdropPress = true,
35
+ enablePanDownToClose = true,
36
+ dragHandleHeight = DEFAULT_DRAG_HANDLE_HEIGHT,
37
+ extraHeight = DEFAULT_EXTRA_HEIGHT,
38
+ borderRadius = DEFAULT_BORDER_RADIUS,
39
+ dismissThreshold = 0.5,
40
+ dismissVelocity = DEFAULT_DISMISS_VELOCITY,
41
+ keyboardBehavior = Platform.OS === 'ios' ? 'padding' : 'height',
42
+ keyboardVerticalOffset = 0,
43
+ keyboardAvoidingEnabled = true,
44
+ containerStyle,
45
+ backdropStyle,
46
+ dragHandleStyle,
47
+ contentContainerStyle,
48
+ testID,
49
+ backdropAccessibilityLabel = 'Close bottom sheet'
50
+ }) {
51
+ const {
52
+ height: screenHeight
53
+ } = useWindowDimensions();
54
+ const translateY = useSharedValue(screenHeight);
55
+ const [isDragging, setIsDragging] = useState(false);
56
+ const isClosingRef = useRef(false);
57
+ const resolvedHeight = useMemo(() => Math.max(Number.isFinite(height) ? height : DEFAULT_HEIGHT, 0), [height]);
58
+ const resolvedHandleHeight = useMemo(() => Math.max(Number.isFinite(dragHandleHeight) ? dragHandleHeight : 0, 0), [dragHandleHeight]);
59
+ const resolvedExtraHeight = useMemo(() => Math.max(Number.isFinite(extraHeight) ? extraHeight : 0, 0), [extraHeight]);
60
+ const resolvedThreshold = useMemo(() => Math.min(Math.max(dismissThreshold, 0), 1), [dismissThreshold]);
61
+ const finishDismiss = useCallback(() => {
62
+ isClosingRef.current = false;
63
+ setIsDragging(false);
64
+ onDismiss();
65
+ }, [onDismiss]);
66
+ const restoreOpenPosition = useCallback(() => {
67
+ translateY.value = withSpring(0, SPRING_CONFIG);
68
+ setIsDragging(false);
69
+ }, [translateY]);
70
+ const animateDismiss = useCallback(() => {
71
+ if (isClosingRef.current) {
72
+ return;
73
+ }
74
+ isClosingRef.current = true;
75
+ translateY.value = withSpring(screenHeight, SPRING_CONFIG, finished => {
76
+ if (finished) {
77
+ runOnJS(finishDismiss)();
78
+ }
79
+ });
80
+ }, [finishDismiss, screenHeight, translateY]);
81
+ useEffect(() => {
82
+ if (visible) {
83
+ isClosingRef.current = false;
84
+ translateY.value = withSpring(0, SPRING_CONFIG);
85
+ return;
86
+ }
87
+ translateY.value = withSpring(screenHeight, SPRING_CONFIG);
88
+ setIsDragging(false);
89
+ }, [screenHeight, translateY, visible]);
90
+ const animatedStyle = useAnimatedStyle(() => ({
91
+ height: resolvedHeight + resolvedExtraHeight,
92
+ transform: [{
93
+ translateY: translateY.value + resolvedExtraHeight
94
+ }]
95
+ }));
96
+ const panGesture = useMemo(() => Gesture.Pan().enabled(enablePanDownToClose).onBegin(() => {
97
+ runOnJS(setIsDragging)(true);
98
+ }).onUpdate(event => {
99
+ translateY.value = Math.max(0, Math.min(resolvedHeight, event.translationY));
100
+ }).onEnd(event => {
101
+ const shouldDismiss = event.translationY > resolvedHeight * resolvedThreshold || event.velocityY > dismissVelocity;
102
+ if (shouldDismiss) {
103
+ translateY.value = withSpring(screenHeight, SPRING_CONFIG, finished => {
104
+ if (finished) {
105
+ runOnJS(finishDismiss)();
106
+ }
107
+ });
108
+ } else {
109
+ translateY.value = withSpring(0, SPRING_CONFIG);
110
+ runOnJS(setIsDragging)(false);
111
+ }
112
+ }).onFinalize((_event, success) => {
113
+ if (!success) {
114
+ runOnJS(restoreOpenPosition)();
115
+ }
116
+ }), [dismissVelocity, enablePanDownToClose, finishDismiss, resolvedHeight, resolvedThreshold, restoreOpenPosition, screenHeight, translateY]);
117
+ const handleBackdropPress = useCallback(() => {
118
+ if (!closeOnBackdropPress || isDragging) {
119
+ return;
120
+ }
121
+ animateDismiss();
122
+ }, [animateDismiss, closeOnBackdropPress, isDragging]);
123
+ return /*#__PURE__*/_jsx(Modal, {
124
+ visible: visible,
125
+ transparent: true,
126
+ animationType: "fade",
127
+ statusBarTranslucent: true,
128
+ onRequestClose: animateDismiss,
129
+ children: /*#__PURE__*/_jsx(KeyboardAvoidingView, {
130
+ style: styles.flex,
131
+ enabled: keyboardAvoidingEnabled,
132
+ behavior: keyboardBehavior,
133
+ keyboardVerticalOffset: keyboardVerticalOffset,
134
+ children: /*#__PURE__*/_jsxs(GestureHandlerRootView, {
135
+ style: styles.modalBackdrop,
136
+ children: [/*#__PURE__*/_jsx(Pressable, {
137
+ accessibilityRole: "button",
138
+ accessibilityLabel: backdropAccessibilityLabel,
139
+ style: [styles.backdrop, {
140
+ backgroundColor: backdropColor
141
+ }, backdropStyle],
142
+ onPress: handleBackdropPress,
143
+ pointerEvents: isDragging ? 'none' : 'auto'
144
+ }), /*#__PURE__*/_jsxs(Animated.View, {
145
+ testID: testID,
146
+ style: [styles.content, {
147
+ backgroundColor,
148
+ borderTopLeftRadius: borderRadius,
149
+ borderTopRightRadius: borderRadius
150
+ }, animatedStyle, containerStyle],
151
+ children: [/*#__PURE__*/_jsx(GestureDetector, {
152
+ gesture: panGesture,
153
+ children: /*#__PURE__*/_jsx(View, {
154
+ style: [styles.dragHandle, {
155
+ height: resolvedHandleHeight,
156
+ borderTopLeftRadius: borderRadius,
157
+ borderTopRightRadius: borderRadius
158
+ }, dragHandleStyle]
159
+ })
160
+ }), /*#__PURE__*/_jsx(View, {
161
+ style: [styles.childrenContainer, {
162
+ height: Math.max(resolvedHeight - resolvedHandleHeight, 0),
163
+ transform: [{
164
+ translateY: -resolvedHandleHeight
165
+ }]
166
+ }, contentContainerStyle],
167
+ children: children
168
+ })]
169
+ })]
170
+ })
171
+ })
172
+ });
173
+ }
174
+ const styles = StyleSheet.create({
175
+ flex: {
176
+ flex: 1
177
+ },
178
+ modalBackdrop: {
179
+ flex: 1,
180
+ justifyContent: 'flex-end'
181
+ },
182
+ backdrop: {
183
+ ...StyleSheet.absoluteFill
184
+ },
185
+ content: {
186
+ elevation: 5,
187
+ overflow: 'hidden'
188
+ },
189
+ dragHandle: {
190
+ zIndex: 100
191
+ },
192
+ childrenContainer: {
193
+ width: '100%'
194
+ }
195
+ });
196
+ export default BottomSheetModal;
197
+ //# sourceMappingURL=BottomSheetModal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","useCallback","useEffect","useMemo","useRef","useState","KeyboardAvoidingView","Modal","Platform","Pressable","StyleSheet","useWindowDimensions","View","Animated","runOnJS","useAnimatedStyle","useSharedValue","withSpring","Gesture","GestureDetector","GestureHandlerRootView","jsx","_jsx","jsxs","_jsxs","DEFAULT_HEIGHT","DEFAULT_DRAG_HANDLE_HEIGHT","DEFAULT_EXTRA_HEIGHT","DEFAULT_BORDER_RADIUS","DEFAULT_DISMISS_VELOCITY","SPRING_CONFIG","damping","stiffness","mass","overshootClamping","BottomSheetModal","visible","onDismiss","children","backgroundColor","height","backdropColor","closeOnBackdropPress","enablePanDownToClose","dragHandleHeight","extraHeight","borderRadius","dismissThreshold","dismissVelocity","keyboardBehavior","OS","keyboardVerticalOffset","keyboardAvoidingEnabled","containerStyle","backdropStyle","dragHandleStyle","contentContainerStyle","testID","backdropAccessibilityLabel","screenHeight","translateY","isDragging","setIsDragging","isClosingRef","resolvedHeight","Math","max","Number","isFinite","resolvedHandleHeight","resolvedExtraHeight","resolvedThreshold","min","finishDismiss","current","restoreOpenPosition","value","animateDismiss","finished","animatedStyle","transform","panGesture","Pan","enabled","onBegin","onUpdate","event","translationY","onEnd","shouldDismiss","velocityY","onFinalize","_event","success","handleBackdropPress","transparent","animationType","statusBarTranslucent","onRequestClose","style","styles","flex","behavior","modalBackdrop","accessibilityRole","accessibilityLabel","backdrop","onPress","pointerEvents","content","borderTopLeftRadius","borderTopRightRadius","gesture","dragHandle","childrenContainer","create","justifyContent","absoluteFill","elevation","overflow","zIndex","width"],"sourceRoot":"../../src","sources":["BottomSheetModal.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export { BottomSheetModal } from "./BottomSheetModal.js";
4
+ export { default } from "./BottomSheetModal.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["BottomSheetModal","default"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,gBAAgB,QAAQ,uBAAoB;AACrD,SAASC,OAAO,QAAQ,uBAAoB","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,59 @@
1
+ import React, { type ReactNode } from 'react';
2
+ import { type KeyboardAvoidingViewProps, type StyleProp, type ViewStyle } from 'react-native';
3
+ export type BottomSheetModalProps = {
4
+ /** Controls whether the modal is visible. */
5
+ visible: boolean;
6
+ /** Called after a user dismisses the sheet. Set visible to false here. */
7
+ onDismiss: () => void;
8
+ /** Content rendered inside the sheet. */
9
+ children: ReactNode;
10
+ /** Visible sheet height in logical pixels. */
11
+ height?: number;
12
+ /** Sheet background color. Defaults to transparent to match the original component. */
13
+ backgroundColor?: string;
14
+ /** Backdrop color. */
15
+ backdropColor?: string;
16
+ /** Allows tapping the backdrop to dismiss the sheet. */
17
+ closeOnBackdropPress?: boolean;
18
+ /** Allows dragging the top area downward to dismiss the sheet. */
19
+ enablePanDownToClose?: boolean;
20
+ /** Height of the draggable top area. */
21
+ dragHandleHeight?: number;
22
+ /** Extra hidden height rendered below the screen. */
23
+ extraHeight?: number;
24
+ /** Top-left and top-right corner radius. */
25
+ borderRadius?: number;
26
+ /** Drag distance ratio required to dismiss. */
27
+ dismissThreshold?: number;
28
+ /** Downward gesture velocity required to dismiss. */
29
+ dismissVelocity?: number;
30
+ /** KeyboardAvoidingView behavior. */
31
+ keyboardBehavior?: KeyboardAvoidingViewProps['behavior'];
32
+ /** KeyboardAvoidingView vertical offset. */
33
+ keyboardVerticalOffset?: number;
34
+ /** Enables KeyboardAvoidingView. */
35
+ keyboardAvoidingEnabled?: boolean;
36
+ /** Style applied to the bottom sheet container. */
37
+ containerStyle?: StyleProp<ViewStyle>;
38
+ /** Style applied to the backdrop. */
39
+ backdropStyle?: StyleProp<ViewStyle>;
40
+ /** Style applied to the draggable top area. */
41
+ dragHandleStyle?: StyleProp<ViewStyle>;
42
+ /** Style applied to the children container. */
43
+ contentContainerStyle?: StyleProp<ViewStyle>;
44
+ /** Optional test id applied to the sheet. */
45
+ testID?: string;
46
+ /** Accessibility label for the backdrop close button. */
47
+ backdropAccessibilityLabel?: string;
48
+ };
49
+ /**
50
+ * A spring-animated React Native bottom sheet modal powered by Reanimated and
51
+ * React Native Gesture Handler.
52
+ *
53
+ * The default layout intentionally matches the original component: a 30px
54
+ * draggable top area, 30px top radius, transparent sheet background, and an
55
+ * extra 200px section rendered below the screen.
56
+ */
57
+ export declare function BottomSheetModal({ visible, onDismiss, children, backgroundColor, height, backdropColor, closeOnBackdropPress, enablePanDownToClose, dragHandleHeight, extraHeight, borderRadius, dismissThreshold, dismissVelocity, keyboardBehavior, keyboardVerticalOffset, keyboardAvoidingEnabled, containerStyle, backdropStyle, dragHandleStyle, contentContainerStyle, testID, backdropAccessibilityLabel, }: BottomSheetModalProps): React.JSX.Element;
58
+ export default BottomSheetModal;
59
+ //# sourceMappingURL=BottomSheetModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomSheetModal.d.ts","sourceRoot":"","sources":["../../src/BottomSheetModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EACZ,KAAK,SAAS,EAMf,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,yBAAyB,EAI9B,KAAK,SAAS,EAId,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AA0BtB,MAAM,MAAM,qBAAqB,GAAG;IAClC,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAC;IAEjB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,IAAI,CAAC;IAEtB,yCAAyC;IACzC,QAAQ,EAAE,SAAS,CAAC;IAEpB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,sBAAsB;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,wDAAwD;IACxD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B,kEAAkE;IAClE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,qCAAqC;IACrC,gBAAgB,CAAC,EAAE,yBAAyB,CAAC,UAAU,CAAC,CAAC;IAEzD,4CAA4C;IAC5C,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC,oCAAoC;IACpC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,mDAAmD;IACnD,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAEtC,qCAAqC;IACrC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAErC,+CAA+C;IAC/C,eAAe,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAEvC,+CAA+C;IAC/C,qBAAqB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7C,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,yDAAyD;IACzD,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,SAAS,EACT,QAAQ,EACR,eAA+B,EAC/B,MAAuB,EACvB,aAAoC,EACpC,oBAA2B,EAC3B,oBAA2B,EAC3B,gBAA6C,EAC7C,WAAkC,EAClC,YAAoC,EACpC,gBAAsB,EACtB,eAA0C,EAC1C,gBAA+D,EAC/D,sBAA0B,EAC1B,uBAA8B,EAC9B,cAAc,EACd,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,MAAM,EACN,0BAAiD,GAClD,EAAE,qBAAqB,qBAuMvB;AAyBD,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { BottomSheetModal } from './BottomSheetModal.js';
2
+ export { default } from './BottomSheetModal.js';
3
+ export type { BottomSheetModalProps } from './BottomSheetModal.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAoB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAoB,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "react-native-bottom-sheet-modal-lite",
3
+ "version": "1.0.0",
4
+ "description": "A spring-animated bottom sheet modal for React Native using Reanimated and Gesture Handler.",
5
+ "keywords": [
6
+ "react-native",
7
+ "bottom-sheet",
8
+ "modal",
9
+ "gesture",
10
+ "reanimated",
11
+ "gesture-handler",
12
+ "typescript",
13
+ "ios",
14
+ "android"
15
+ ],
16
+ "author": "Nattawat Virunsuntornkul",
17
+ "license": "MIT",
18
+ "main": "./lib/module/index.js",
19
+ "module": "./lib/module/index.js",
20
+ "types": "./lib/typescript/index.d.ts",
21
+ "react-native": "./src/index.ts",
22
+ "source": "./src/index.ts",
23
+ "exports": {
24
+ ".": {
25
+ "source": "./src/index.ts",
26
+ "types": "./lib/typescript/index.d.ts",
27
+ "default": "./lib/module/index.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": [
32
+ "lib",
33
+ "src",
34
+ "README.md",
35
+ "LICENSE",
36
+ "CHANGELOG.md"
37
+ ],
38
+ "sideEffects": false,
39
+ "scripts": {
40
+ "clean": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\"",
41
+ "typecheck": "tsc --noEmit -p tsconfig.json",
42
+ "build": "bob build",
43
+ "prepare": "npm run clean && npm run build",
44
+ "pack:check": "npm pack --dry-run"
45
+ },
46
+ "react-native-builder-bob": {
47
+ "source": "src",
48
+ "output": "lib",
49
+ "targets": [
50
+ [
51
+ "module",
52
+ {
53
+ "esm": true
54
+ }
55
+ ],
56
+ [
57
+ "typescript",
58
+ {
59
+ "project": "tsconfig.build.json"
60
+ }
61
+ ]
62
+ ]
63
+ },
64
+ "peerDependencies": {
65
+ "react": ">=18.2.0",
66
+ "react-native": ">=0.72.0",
67
+ "react-native-reanimated": ">=3.0.0 <5.0.0",
68
+ "react-native-gesture-handler": ">=2.9.0 <4.0.0"
69
+ },
70
+ "devDependencies": {
71
+ "@types/react": "^19.2.14",
72
+ "react": "19.2.7",
73
+ "react-native": "0.86.0",
74
+ "react-native-builder-bob": "^0.42.1",
75
+ "typescript": "^6.0.3",
76
+ "react-native-reanimated": "^4.4.1",
77
+ "react-native-gesture-handler": "^3.0.1",
78
+ "react-native-worklets": "^0.9.2"
79
+ },
80
+ "publishConfig": {
81
+ "access": "public"
82
+ }
83
+ }
@@ -0,0 +1,369 @@
1
+ import React, {
2
+ type ReactNode,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import {
10
+ KeyboardAvoidingView,
11
+ type KeyboardAvoidingViewProps,
12
+ Modal,
13
+ Platform,
14
+ Pressable,
15
+ type StyleProp,
16
+ StyleSheet,
17
+ useWindowDimensions,
18
+ View,
19
+ type ViewStyle,
20
+ } from 'react-native';
21
+ import Animated, {
22
+ runOnJS,
23
+ useAnimatedStyle,
24
+ useSharedValue,
25
+ withSpring,
26
+ } from 'react-native-reanimated';
27
+ import {
28
+ Gesture,
29
+ GestureDetector,
30
+ GestureHandlerRootView,
31
+ } from 'react-native-gesture-handler';
32
+
33
+ const DEFAULT_HEIGHT = 300;
34
+ const DEFAULT_DRAG_HANDLE_HEIGHT = 30;
35
+ const DEFAULT_EXTRA_HEIGHT = 200;
36
+ const DEFAULT_BORDER_RADIUS = 30;
37
+ const DEFAULT_DISMISS_VELOCITY = 900;
38
+
39
+ const SPRING_CONFIG = {
40
+ damping: 28,
41
+ stiffness: 300,
42
+ mass: 0.4,
43
+ overshootClamping: true,
44
+ } as const;
45
+
46
+ export type BottomSheetModalProps = {
47
+ /** Controls whether the modal is visible. */
48
+ visible: boolean;
49
+
50
+ /** Called after a user dismisses the sheet. Set visible to false here. */
51
+ onDismiss: () => void;
52
+
53
+ /** Content rendered inside the sheet. */
54
+ children: ReactNode;
55
+
56
+ /** Visible sheet height in logical pixels. */
57
+ height?: number;
58
+
59
+ /** Sheet background color. Defaults to transparent to match the original component. */
60
+ backgroundColor?: string;
61
+
62
+ /** Backdrop color. */
63
+ backdropColor?: string;
64
+
65
+ /** Allows tapping the backdrop to dismiss the sheet. */
66
+ closeOnBackdropPress?: boolean;
67
+
68
+ /** Allows dragging the top area downward to dismiss the sheet. */
69
+ enablePanDownToClose?: boolean;
70
+
71
+ /** Height of the draggable top area. */
72
+ dragHandleHeight?: number;
73
+
74
+ /** Extra hidden height rendered below the screen. */
75
+ extraHeight?: number;
76
+
77
+ /** Top-left and top-right corner radius. */
78
+ borderRadius?: number;
79
+
80
+ /** Drag distance ratio required to dismiss. */
81
+ dismissThreshold?: number;
82
+
83
+ /** Downward gesture velocity required to dismiss. */
84
+ dismissVelocity?: number;
85
+
86
+ /** KeyboardAvoidingView behavior. */
87
+ keyboardBehavior?: KeyboardAvoidingViewProps['behavior'];
88
+
89
+ /** KeyboardAvoidingView vertical offset. */
90
+ keyboardVerticalOffset?: number;
91
+
92
+ /** Enables KeyboardAvoidingView. */
93
+ keyboardAvoidingEnabled?: boolean;
94
+
95
+ /** Style applied to the bottom sheet container. */
96
+ containerStyle?: StyleProp<ViewStyle>;
97
+
98
+ /** Style applied to the backdrop. */
99
+ backdropStyle?: StyleProp<ViewStyle>;
100
+
101
+ /** Style applied to the draggable top area. */
102
+ dragHandleStyle?: StyleProp<ViewStyle>;
103
+
104
+ /** Style applied to the children container. */
105
+ contentContainerStyle?: StyleProp<ViewStyle>;
106
+
107
+ /** Optional test id applied to the sheet. */
108
+ testID?: string;
109
+
110
+ /** Accessibility label for the backdrop close button. */
111
+ backdropAccessibilityLabel?: string;
112
+ };
113
+
114
+ /**
115
+ * A spring-animated React Native bottom sheet modal powered by Reanimated and
116
+ * React Native Gesture Handler.
117
+ *
118
+ * The default layout intentionally matches the original component: a 30px
119
+ * draggable top area, 30px top radius, transparent sheet background, and an
120
+ * extra 200px section rendered below the screen.
121
+ */
122
+ export function BottomSheetModal({
123
+ visible,
124
+ onDismiss,
125
+ children,
126
+ backgroundColor = 'transparent',
127
+ height = DEFAULT_HEIGHT,
128
+ backdropColor = 'rgba(0, 0, 0, 0.5)',
129
+ closeOnBackdropPress = true,
130
+ enablePanDownToClose = true,
131
+ dragHandleHeight = DEFAULT_DRAG_HANDLE_HEIGHT,
132
+ extraHeight = DEFAULT_EXTRA_HEIGHT,
133
+ borderRadius = DEFAULT_BORDER_RADIUS,
134
+ dismissThreshold = 0.5,
135
+ dismissVelocity = DEFAULT_DISMISS_VELOCITY,
136
+ keyboardBehavior = Platform.OS === 'ios' ? 'padding' : 'height',
137
+ keyboardVerticalOffset = 0,
138
+ keyboardAvoidingEnabled = true,
139
+ containerStyle,
140
+ backdropStyle,
141
+ dragHandleStyle,
142
+ contentContainerStyle,
143
+ testID,
144
+ backdropAccessibilityLabel = 'Close bottom sheet',
145
+ }: BottomSheetModalProps) {
146
+ const { height: screenHeight } = useWindowDimensions();
147
+ const translateY = useSharedValue(screenHeight);
148
+ const [isDragging, setIsDragging] = useState(false);
149
+ const isClosingRef = useRef(false);
150
+
151
+ const resolvedHeight = useMemo(
152
+ () => Math.max(Number.isFinite(height) ? height : DEFAULT_HEIGHT, 0),
153
+ [height]
154
+ );
155
+ const resolvedHandleHeight = useMemo(
156
+ () => Math.max(Number.isFinite(dragHandleHeight) ? dragHandleHeight : 0, 0),
157
+ [dragHandleHeight]
158
+ );
159
+ const resolvedExtraHeight = useMemo(
160
+ () => Math.max(Number.isFinite(extraHeight) ? extraHeight : 0, 0),
161
+ [extraHeight]
162
+ );
163
+ const resolvedThreshold = useMemo(
164
+ () => Math.min(Math.max(dismissThreshold, 0), 1),
165
+ [dismissThreshold]
166
+ );
167
+
168
+ const finishDismiss = useCallback(() => {
169
+ isClosingRef.current = false;
170
+ setIsDragging(false);
171
+ onDismiss();
172
+ }, [onDismiss]);
173
+
174
+ const restoreOpenPosition = useCallback(() => {
175
+ translateY.value = withSpring(0, SPRING_CONFIG);
176
+ setIsDragging(false);
177
+ }, [translateY]);
178
+
179
+ const animateDismiss = useCallback(() => {
180
+ if (isClosingRef.current) {
181
+ return;
182
+ }
183
+
184
+ isClosingRef.current = true;
185
+ translateY.value = withSpring(screenHeight, SPRING_CONFIG, (finished) => {
186
+ if (finished) {
187
+ runOnJS(finishDismiss)();
188
+ }
189
+ });
190
+ }, [finishDismiss, screenHeight, translateY]);
191
+
192
+ useEffect(() => {
193
+ if (visible) {
194
+ isClosingRef.current = false;
195
+ translateY.value = withSpring(0, SPRING_CONFIG);
196
+ return;
197
+ }
198
+
199
+ translateY.value = withSpring(screenHeight, SPRING_CONFIG);
200
+ setIsDragging(false);
201
+ }, [screenHeight, translateY, visible]);
202
+
203
+ const animatedStyle = useAnimatedStyle(() => ({
204
+ height: resolvedHeight + resolvedExtraHeight,
205
+ transform: [
206
+ {
207
+ translateY: translateY.value + resolvedExtraHeight,
208
+ },
209
+ ],
210
+ }));
211
+
212
+ const panGesture = useMemo(
213
+ () =>
214
+ Gesture.Pan()
215
+ .enabled(enablePanDownToClose)
216
+ .onBegin(() => {
217
+ runOnJS(setIsDragging)(true);
218
+ })
219
+ .onUpdate((event) => {
220
+ translateY.value = Math.max(
221
+ 0,
222
+ Math.min(resolvedHeight, event.translationY)
223
+ );
224
+ })
225
+ .onEnd((event) => {
226
+ const shouldDismiss =
227
+ event.translationY > resolvedHeight * resolvedThreshold ||
228
+ event.velocityY > dismissVelocity;
229
+
230
+ if (shouldDismiss) {
231
+ translateY.value = withSpring(
232
+ screenHeight,
233
+ SPRING_CONFIG,
234
+ (finished) => {
235
+ if (finished) {
236
+ runOnJS(finishDismiss)();
237
+ }
238
+ }
239
+ );
240
+ } else {
241
+ translateY.value = withSpring(0, SPRING_CONFIG);
242
+ runOnJS(setIsDragging)(false);
243
+ }
244
+ })
245
+ .onFinalize((_event, success) => {
246
+ if (!success) {
247
+ runOnJS(restoreOpenPosition)();
248
+ }
249
+ }),
250
+ [
251
+ dismissVelocity,
252
+ enablePanDownToClose,
253
+ finishDismiss,
254
+ resolvedHeight,
255
+ resolvedThreshold,
256
+ restoreOpenPosition,
257
+ screenHeight,
258
+ translateY,
259
+ ]
260
+ );
261
+
262
+ const handleBackdropPress = useCallback(() => {
263
+ if (!closeOnBackdropPress || isDragging) {
264
+ return;
265
+ }
266
+
267
+ animateDismiss();
268
+ }, [animateDismiss, closeOnBackdropPress, isDragging]);
269
+
270
+ return (
271
+ <Modal
272
+ visible={visible}
273
+ transparent
274
+ animationType="fade"
275
+ statusBarTranslucent
276
+ onRequestClose={animateDismiss}
277
+ >
278
+ <KeyboardAvoidingView
279
+ style={styles.flex}
280
+ enabled={keyboardAvoidingEnabled}
281
+ behavior={keyboardBehavior}
282
+ keyboardVerticalOffset={keyboardVerticalOffset}
283
+ >
284
+ <GestureHandlerRootView style={styles.modalBackdrop}>
285
+ <Pressable
286
+ accessibilityRole="button"
287
+ accessibilityLabel={backdropAccessibilityLabel}
288
+ style={[
289
+ styles.backdrop,
290
+ { backgroundColor: backdropColor },
291
+ backdropStyle,
292
+ ]}
293
+ onPress={handleBackdropPress}
294
+ pointerEvents={isDragging ? 'none' : 'auto'}
295
+ />
296
+
297
+ <Animated.View
298
+ testID={testID}
299
+ style={[
300
+ styles.content,
301
+ {
302
+ backgroundColor,
303
+ borderTopLeftRadius: borderRadius,
304
+ borderTopRightRadius: borderRadius,
305
+ },
306
+ animatedStyle,
307
+ containerStyle,
308
+ ]}
309
+ >
310
+ <GestureDetector gesture={panGesture}>
311
+ <View
312
+ style={[
313
+ styles.dragHandle,
314
+ {
315
+ height: resolvedHandleHeight,
316
+ borderTopLeftRadius: borderRadius,
317
+ borderTopRightRadius: borderRadius,
318
+ },
319
+ dragHandleStyle,
320
+ ]}
321
+ />
322
+ </GestureDetector>
323
+
324
+ <View
325
+ style={[
326
+ styles.childrenContainer,
327
+ {
328
+ height: Math.max(
329
+ resolvedHeight - resolvedHandleHeight,
330
+ 0
331
+ ),
332
+ transform: [{ translateY: -resolvedHandleHeight }],
333
+ },
334
+ contentContainerStyle,
335
+ ]}
336
+ >
337
+ {children}
338
+ </View>
339
+ </Animated.View>
340
+ </GestureHandlerRootView>
341
+ </KeyboardAvoidingView>
342
+ </Modal>
343
+ );
344
+ }
345
+
346
+ const styles = StyleSheet.create({
347
+ flex: {
348
+ flex: 1,
349
+ },
350
+ modalBackdrop: {
351
+ flex: 1,
352
+ justifyContent: 'flex-end',
353
+ },
354
+ backdrop: {
355
+ ...StyleSheet.absoluteFill,
356
+ },
357
+ content: {
358
+ elevation: 5,
359
+ overflow: 'hidden',
360
+ },
361
+ dragHandle: {
362
+ zIndex: 100,
363
+ },
364
+ childrenContainer: {
365
+ width: '100%',
366
+ },
367
+ });
368
+
369
+ export default BottomSheetModal;
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { BottomSheetModal } from './BottomSheetModal';
2
+ export { default } from './BottomSheetModal';
3
+ export type { BottomSheetModalProps } from './BottomSheetModal';