@webority-technologies/mobile 0.0.13 → 0.0.14
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/lib/commonjs/components/BottomSheet/BottomSheet.js +45 -6
- package/lib/module/components/BottomSheet/BottomSheet.js +47 -8
- package/lib/typescript/commonjs/components/BottomSheet/BottomSheet.d.ts +10 -0
- package/lib/typescript/module/components/BottomSheet/BottomSheet.d.ts +10 -0
- package/package.json +1 -1
|
@@ -49,6 +49,7 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
|
|
|
49
49
|
enableBackdropPress = true,
|
|
50
50
|
backdropOpacity = 0.5,
|
|
51
51
|
keyboardBehavior = 'none',
|
|
52
|
+
mode = 'modal',
|
|
52
53
|
handleIndicatorStyle,
|
|
53
54
|
containerStyle,
|
|
54
55
|
children,
|
|
@@ -84,6 +85,10 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
|
|
|
84
85
|
const currentIndexShared = (0, _reactNativeReanimated.useSharedValue)(-1);
|
|
85
86
|
const [currentIndex, setCurrentIndex] = (0, _react.useState)(-1);
|
|
86
87
|
const isAnimatingRef = (0, _react.useRef)(false);
|
|
88
|
+
// Drives the native <Modal>'s visible prop in modal mode. We mount the modal
|
|
89
|
+
// synchronously on open and unmount only after the close animation finishes
|
|
90
|
+
// so the slide-down stays visible.
|
|
91
|
+
const [modalVisible, setModalVisible] = (0, _react.useState)(false);
|
|
87
92
|
|
|
88
93
|
// Convert a snap-point index → translateY position. -1 = closed.
|
|
89
94
|
const yForIndex = (0, _react.useCallback)(idx => {
|
|
@@ -111,14 +116,26 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
|
|
|
111
116
|
}, [onAnimate, translateY]);
|
|
112
117
|
const markAnimationDone = (0, _react.useCallback)(() => {
|
|
113
118
|
isAnimatingRef.current = false;
|
|
114
|
-
|
|
119
|
+
// If we just finished a close animation, unmount the modal wrapper.
|
|
120
|
+
if (mode === 'modal' && currentIndexShared.value < 0) {
|
|
121
|
+
setModalVisible(false);
|
|
122
|
+
}
|
|
123
|
+
}, [mode, currentIndexShared]);
|
|
115
124
|
const expand = (0, _react.useCallback)(idx => {
|
|
116
125
|
const target = typeof idx === 'number' ? clamp(idx, 0, resolvedSnapPoints.length - 1) : currentIndex >= 0 ? currentIndex : 0;
|
|
117
126
|
const fromIndex = currentIndexShared.value;
|
|
118
127
|
const to = yForIndex(target);
|
|
128
|
+
// Mount the Modal before kicking off the spring so the sheet has a
|
|
129
|
+
// host to animate into.
|
|
130
|
+
if (mode === 'modal') setModalVisible(true);
|
|
131
|
+
// Reset translateY to the closed position so the first open animates
|
|
132
|
+
// up from off-screen rather than snapping in place.
|
|
133
|
+
if (fromIndex < 0) {
|
|
134
|
+
translateY.value = closedY;
|
|
135
|
+
}
|
|
119
136
|
setIndexJS(target);
|
|
120
137
|
animateTo(to, fromIndex, target);
|
|
121
|
-
}, [animateTo, currentIndex, currentIndexShared, resolvedSnapPoints.length, setIndexJS, yForIndex]);
|
|
138
|
+
}, [animateTo, closedY, currentIndex, currentIndexShared, mode, resolvedSnapPoints.length, setIndexJS, translateY, yForIndex]);
|
|
122
139
|
const collapse = (0, _react.useCallback)(() => {
|
|
123
140
|
if (resolvedSnapPoints.length === 0) return;
|
|
124
141
|
const fromIndex = currentIndexShared.value;
|
|
@@ -300,16 +317,19 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
|
|
|
300
317
|
}, [enableBackdropPress, close]);
|
|
301
318
|
|
|
302
319
|
// Don't render the heavy gesture tree at all when nothing's been opened yet.
|
|
303
|
-
//
|
|
304
|
-
//
|
|
320
|
+
// For inline mode we keep the legacy "mount on first open" behavior; modal
|
|
321
|
+
// mode is gated entirely by `modalVisible`.
|
|
305
322
|
const everOpenedRef = (0, _react.useRef)(false);
|
|
306
323
|
if (isExpanded || controlledIndex !== undefined) {
|
|
307
324
|
everOpenedRef.current = true;
|
|
308
325
|
}
|
|
309
|
-
if (!everOpenedRef.current) {
|
|
326
|
+
if (mode === 'inline' && !everOpenedRef.current) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
if (mode === 'modal' && !modalVisible) {
|
|
310
330
|
return null;
|
|
311
331
|
}
|
|
312
|
-
|
|
332
|
+
const sheetTree = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
313
333
|
style: _reactNative.StyleSheet.absoluteFill,
|
|
314
334
|
pointerEvents: "box-none",
|
|
315
335
|
testID: testID,
|
|
@@ -356,6 +376,22 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
|
|
|
356
376
|
})
|
|
357
377
|
})]
|
|
358
378
|
});
|
|
379
|
+
if (mode === 'modal') {
|
|
380
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
|
|
381
|
+
transparent: true,
|
|
382
|
+
visible: modalVisible,
|
|
383
|
+
onRequestClose: close,
|
|
384
|
+
statusBarTranslucent: true,
|
|
385
|
+
presentationStyle: "overFullScreen",
|
|
386
|
+
animationType: "none",
|
|
387
|
+
supportedOrientations: ['portrait', 'landscape'],
|
|
388
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureHandlerRootView, {
|
|
389
|
+
style: styles.modalRoot,
|
|
390
|
+
children: sheetTree
|
|
391
|
+
})
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return sheetTree;
|
|
359
395
|
});
|
|
360
396
|
BottomSheet.displayName = 'BottomSheet';
|
|
361
397
|
|
|
@@ -395,6 +431,9 @@ const buildStyles = _theme => _reactNative.StyleSheet.create({
|
|
|
395
431
|
},
|
|
396
432
|
content: {
|
|
397
433
|
flex: 1
|
|
434
|
+
},
|
|
435
|
+
modalRoot: {
|
|
436
|
+
flex: 1
|
|
398
437
|
}
|
|
399
438
|
});
|
|
400
439
|
var _default = exports.default = BottomSheet;
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
23
|
-
import { Dimensions, Keyboard, Platform, Pressable, StyleSheet, View } from 'react-native';
|
|
24
|
-
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
23
|
+
import { Dimensions, Keyboard, Modal, Platform, Pressable, StyleSheet, View } from 'react-native';
|
|
24
|
+
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
25
25
|
import Animated, { Extrapolation, interpolate, runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
|
|
26
26
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
27
27
|
import { useTheme } from "../../theme/index.js";
|
|
@@ -44,6 +44,7 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
44
44
|
enableBackdropPress = true,
|
|
45
45
|
backdropOpacity = 0.5,
|
|
46
46
|
keyboardBehavior = 'none',
|
|
47
|
+
mode = 'modal',
|
|
47
48
|
handleIndicatorStyle,
|
|
48
49
|
containerStyle,
|
|
49
50
|
children,
|
|
@@ -79,6 +80,10 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
79
80
|
const currentIndexShared = useSharedValue(-1);
|
|
80
81
|
const [currentIndex, setCurrentIndex] = useState(-1);
|
|
81
82
|
const isAnimatingRef = useRef(false);
|
|
83
|
+
// Drives the native <Modal>'s visible prop in modal mode. We mount the modal
|
|
84
|
+
// synchronously on open and unmount only after the close animation finishes
|
|
85
|
+
// so the slide-down stays visible.
|
|
86
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
82
87
|
|
|
83
88
|
// Convert a snap-point index → translateY position. -1 = closed.
|
|
84
89
|
const yForIndex = useCallback(idx => {
|
|
@@ -106,14 +111,26 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
106
111
|
}, [onAnimate, translateY]);
|
|
107
112
|
const markAnimationDone = useCallback(() => {
|
|
108
113
|
isAnimatingRef.current = false;
|
|
109
|
-
|
|
114
|
+
// If we just finished a close animation, unmount the modal wrapper.
|
|
115
|
+
if (mode === 'modal' && currentIndexShared.value < 0) {
|
|
116
|
+
setModalVisible(false);
|
|
117
|
+
}
|
|
118
|
+
}, [mode, currentIndexShared]);
|
|
110
119
|
const expand = useCallback(idx => {
|
|
111
120
|
const target = typeof idx === 'number' ? clamp(idx, 0, resolvedSnapPoints.length - 1) : currentIndex >= 0 ? currentIndex : 0;
|
|
112
121
|
const fromIndex = currentIndexShared.value;
|
|
113
122
|
const to = yForIndex(target);
|
|
123
|
+
// Mount the Modal before kicking off the spring so the sheet has a
|
|
124
|
+
// host to animate into.
|
|
125
|
+
if (mode === 'modal') setModalVisible(true);
|
|
126
|
+
// Reset translateY to the closed position so the first open animates
|
|
127
|
+
// up from off-screen rather than snapping in place.
|
|
128
|
+
if (fromIndex < 0) {
|
|
129
|
+
translateY.value = closedY;
|
|
130
|
+
}
|
|
114
131
|
setIndexJS(target);
|
|
115
132
|
animateTo(to, fromIndex, target);
|
|
116
|
-
}, [animateTo, currentIndex, currentIndexShared, resolvedSnapPoints.length, setIndexJS, yForIndex]);
|
|
133
|
+
}, [animateTo, closedY, currentIndex, currentIndexShared, mode, resolvedSnapPoints.length, setIndexJS, translateY, yForIndex]);
|
|
117
134
|
const collapse = useCallback(() => {
|
|
118
135
|
if (resolvedSnapPoints.length === 0) return;
|
|
119
136
|
const fromIndex = currentIndexShared.value;
|
|
@@ -295,16 +312,19 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
295
312
|
}, [enableBackdropPress, close]);
|
|
296
313
|
|
|
297
314
|
// Don't render the heavy gesture tree at all when nothing's been opened yet.
|
|
298
|
-
//
|
|
299
|
-
//
|
|
315
|
+
// For inline mode we keep the legacy "mount on first open" behavior; modal
|
|
316
|
+
// mode is gated entirely by `modalVisible`.
|
|
300
317
|
const everOpenedRef = useRef(false);
|
|
301
318
|
if (isExpanded || controlledIndex !== undefined) {
|
|
302
319
|
everOpenedRef.current = true;
|
|
303
320
|
}
|
|
304
|
-
if (!everOpenedRef.current) {
|
|
321
|
+
if (mode === 'inline' && !everOpenedRef.current) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
if (mode === 'modal' && !modalVisible) {
|
|
305
325
|
return null;
|
|
306
326
|
}
|
|
307
|
-
|
|
327
|
+
const sheetTree = /*#__PURE__*/_jsxs(View, {
|
|
308
328
|
style: StyleSheet.absoluteFill,
|
|
309
329
|
pointerEvents: "box-none",
|
|
310
330
|
testID: testID,
|
|
@@ -351,6 +371,22 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
351
371
|
})
|
|
352
372
|
})]
|
|
353
373
|
});
|
|
374
|
+
if (mode === 'modal') {
|
|
375
|
+
return /*#__PURE__*/_jsx(Modal, {
|
|
376
|
+
transparent: true,
|
|
377
|
+
visible: modalVisible,
|
|
378
|
+
onRequestClose: close,
|
|
379
|
+
statusBarTranslucent: true,
|
|
380
|
+
presentationStyle: "overFullScreen",
|
|
381
|
+
animationType: "none",
|
|
382
|
+
supportedOrientations: ['portrait', 'landscape'],
|
|
383
|
+
children: /*#__PURE__*/_jsx(GestureHandlerRootView, {
|
|
384
|
+
style: styles.modalRoot,
|
|
385
|
+
children: sheetTree
|
|
386
|
+
})
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return sheetTree;
|
|
354
390
|
});
|
|
355
391
|
BottomSheet.displayName = 'BottomSheet';
|
|
356
392
|
|
|
@@ -390,6 +426,9 @@ const buildStyles = _theme => StyleSheet.create({
|
|
|
390
426
|
},
|
|
391
427
|
content: {
|
|
392
428
|
flex: 1
|
|
429
|
+
},
|
|
430
|
+
modalRoot: {
|
|
431
|
+
flex: 1
|
|
393
432
|
}
|
|
394
433
|
});
|
|
395
434
|
export { BottomSheet };
|
|
@@ -20,6 +20,7 @@ import React from 'react';
|
|
|
20
20
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
21
21
|
export type SnapPoint = number | `${number}%`;
|
|
22
22
|
export type KeyboardBehavior = 'none' | 'shift';
|
|
23
|
+
export type BottomSheetMode = 'modal' | 'inline';
|
|
23
24
|
export interface BottomSheetProps {
|
|
24
25
|
snapPoints: SnapPoint[];
|
|
25
26
|
index?: number;
|
|
@@ -37,6 +38,15 @@ export interface BottomSheetProps {
|
|
|
37
38
|
* sheet itself.
|
|
38
39
|
*/
|
|
39
40
|
keyboardBehavior?: KeyboardBehavior;
|
|
41
|
+
/**
|
|
42
|
+
* How the sheet is mounted in the view tree.
|
|
43
|
+
* - `'modal'` (default): rendered inside a native `<Modal>` so the backdrop
|
|
44
|
+
* covers the entire screen (status bar, headers, tab bars) — the standard
|
|
45
|
+
* bottom-sheet UX.
|
|
46
|
+
* - `'inline'`: rendered as an `absoluteFill` overlay inside the parent
|
|
47
|
+
* component. Useful when embedding a sheet within a bounded area.
|
|
48
|
+
*/
|
|
49
|
+
mode?: BottomSheetMode;
|
|
40
50
|
handleIndicatorStyle?: StyleProp<ViewStyle>;
|
|
41
51
|
containerStyle?: StyleProp<ViewStyle>;
|
|
42
52
|
children?: React.ReactNode;
|
|
@@ -20,6 +20,7 @@ import React from 'react';
|
|
|
20
20
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
21
21
|
export type SnapPoint = number | `${number}%`;
|
|
22
22
|
export type KeyboardBehavior = 'none' | 'shift';
|
|
23
|
+
export type BottomSheetMode = 'modal' | 'inline';
|
|
23
24
|
export interface BottomSheetProps {
|
|
24
25
|
snapPoints: SnapPoint[];
|
|
25
26
|
index?: number;
|
|
@@ -37,6 +38,15 @@ export interface BottomSheetProps {
|
|
|
37
38
|
* sheet itself.
|
|
38
39
|
*/
|
|
39
40
|
keyboardBehavior?: KeyboardBehavior;
|
|
41
|
+
/**
|
|
42
|
+
* How the sheet is mounted in the view tree.
|
|
43
|
+
* - `'modal'` (default): rendered inside a native `<Modal>` so the backdrop
|
|
44
|
+
* covers the entire screen (status bar, headers, tab bars) — the standard
|
|
45
|
+
* bottom-sheet UX.
|
|
46
|
+
* - `'inline'`: rendered as an `absoluteFill` overlay inside the parent
|
|
47
|
+
* component. Useful when embedding a sheet within a bounded area.
|
|
48
|
+
*/
|
|
49
|
+
mode?: BottomSheetMode;
|
|
40
50
|
handleIndicatorStyle?: StyleProp<ViewStyle>;
|
|
41
51
|
containerStyle?: StyleProp<ViewStyle>;
|
|
42
52
|
children?: React.ReactNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority-technologies/mobile",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Beautiful, animated, accessible React Native components plus API/auth/logging/network/storage utilities for Webority projects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|