@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.
@@ -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
- // We still mount once `currentIndex` 0 OR an animation has started.
304
- // For controlled `index`, we treat any value ≠ -1 as "opened".
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
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
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
- // We still mount once `currentIndex` 0 OR an animation has started.
299
- // For controlled `index`, we treat any value ≠ -1 as "opened".
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
- return /*#__PURE__*/_jsxs(View, {
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.13",
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",