@webority-technologies/mobile 0.0.13 → 0.0.15

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;
@@ -270,10 +287,15 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
270
287
  }, [keyboardBehavior, keyboardOffset]);
271
288
 
272
289
  // ───────── Animated styles ─────────
290
+ const safeAreaTop = insets.top;
273
291
  const sheetStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
274
- // Don't push past the screen top — keyboard offset is clamped to minTopY.
275
292
  const yWithKb = translateY.value + keyboardOffset.value;
276
- const clamped = yWithKb < minTopY ? minTopY : yWithKb;
293
+ // When the keyboard is up, allow the sheet to slide above its natural
294
+ // max-snap-point top (`minTopY`) so the input area stays visible. We still
295
+ // clamp to the safe-area top so the sheet doesn't disappear under the
296
+ // status bar / notch.
297
+ const lowerBound = keyboardOffset.value < 0 ? safeAreaTop : minTopY;
298
+ const clamped = yWithKb < lowerBound ? lowerBound : yWithKb;
277
299
  return {
278
300
  transform: [{
279
301
  translateY: clamped
@@ -300,23 +322,31 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
300
322
  }, [enableBackdropPress, close]);
301
323
 
302
324
  // 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".
325
+ // For inline mode we keep the legacy "mount on first open" behavior; modal
326
+ // mode is gated entirely by `modalVisible`.
305
327
  const everOpenedRef = (0, _react.useRef)(false);
306
328
  if (isExpanded || controlledIndex !== undefined) {
307
329
  everOpenedRef.current = true;
308
330
  }
309
- if (!everOpenedRef.current) {
331
+ if (mode === 'inline' && !everOpenedRef.current) {
332
+ return null;
333
+ }
334
+ if (mode === 'modal' && !modalVisible) {
310
335
  return null;
311
336
  }
312
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
337
+ const sheetTree = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
313
338
  style: _reactNative.StyleSheet.absoluteFill,
314
339
  pointerEvents: "box-none",
315
340
  testID: testID,
316
341
  accessibilityViewIsModal: accessibilityViewIsModal ?? isExpanded,
317
342
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
318
- style: [styles.backdrop, {
319
- backgroundColor: theme.colors.background.overlay
343
+ style: [styles.backdrop,
344
+ // Solid scrim — the theme `background.overlay` token bakes in alpha
345
+ // and double-multiplies with `backdropOpacity`, leaving the backdrop
346
+ // looking washed out. Standard modal scrims are solid black so the
347
+ // prop controls the actual final opacity.
348
+ {
349
+ backgroundColor: '#000'
320
350
  }, backdropStyle],
321
351
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
322
352
  style: _reactNative.StyleSheet.absoluteFill,
@@ -356,6 +386,22 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
356
386
  })
357
387
  })]
358
388
  });
389
+ if (mode === 'modal') {
390
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
391
+ transparent: true,
392
+ visible: modalVisible,
393
+ onRequestClose: close,
394
+ statusBarTranslucent: true,
395
+ presentationStyle: "overFullScreen",
396
+ animationType: "none",
397
+ supportedOrientations: ['portrait', 'landscape'],
398
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureHandlerRootView, {
399
+ style: styles.modalRoot,
400
+ children: sheetTree
401
+ })
402
+ });
403
+ }
404
+ return sheetTree;
359
405
  });
360
406
  BottomSheet.displayName = 'BottomSheet';
361
407
 
@@ -395,6 +441,9 @@ const buildStyles = _theme => _reactNative.StyleSheet.create({
395
441
  },
396
442
  content: {
397
443
  flex: 1
444
+ },
445
+ modalRoot: {
446
+ flex: 1
398
447
  }
399
448
  });
400
449
  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;
@@ -265,10 +282,15 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
265
282
  }, [keyboardBehavior, keyboardOffset]);
266
283
 
267
284
  // ───────── Animated styles ─────────
285
+ const safeAreaTop = insets.top;
268
286
  const sheetStyle = useAnimatedStyle(() => {
269
- // Don't push past the screen top — keyboard offset is clamped to minTopY.
270
287
  const yWithKb = translateY.value + keyboardOffset.value;
271
- const clamped = yWithKb < minTopY ? minTopY : yWithKb;
288
+ // When the keyboard is up, allow the sheet to slide above its natural
289
+ // max-snap-point top (`minTopY`) so the input area stays visible. We still
290
+ // clamp to the safe-area top so the sheet doesn't disappear under the
291
+ // status bar / notch.
292
+ const lowerBound = keyboardOffset.value < 0 ? safeAreaTop : minTopY;
293
+ const clamped = yWithKb < lowerBound ? lowerBound : yWithKb;
272
294
  return {
273
295
  transform: [{
274
296
  translateY: clamped
@@ -295,23 +317,31 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
295
317
  }, [enableBackdropPress, close]);
296
318
 
297
319
  // 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".
320
+ // For inline mode we keep the legacy "mount on first open" behavior; modal
321
+ // mode is gated entirely by `modalVisible`.
300
322
  const everOpenedRef = useRef(false);
301
323
  if (isExpanded || controlledIndex !== undefined) {
302
324
  everOpenedRef.current = true;
303
325
  }
304
- if (!everOpenedRef.current) {
326
+ if (mode === 'inline' && !everOpenedRef.current) {
327
+ return null;
328
+ }
329
+ if (mode === 'modal' && !modalVisible) {
305
330
  return null;
306
331
  }
307
- return /*#__PURE__*/_jsxs(View, {
332
+ const sheetTree = /*#__PURE__*/_jsxs(View, {
308
333
  style: StyleSheet.absoluteFill,
309
334
  pointerEvents: "box-none",
310
335
  testID: testID,
311
336
  accessibilityViewIsModal: accessibilityViewIsModal ?? isExpanded,
312
337
  children: [/*#__PURE__*/_jsx(Animated.View, {
313
- style: [styles.backdrop, {
314
- backgroundColor: theme.colors.background.overlay
338
+ style: [styles.backdrop,
339
+ // Solid scrim — the theme `background.overlay` token bakes in alpha
340
+ // and double-multiplies with `backdropOpacity`, leaving the backdrop
341
+ // looking washed out. Standard modal scrims are solid black so the
342
+ // prop controls the actual final opacity.
343
+ {
344
+ backgroundColor: '#000'
315
345
  }, backdropStyle],
316
346
  children: /*#__PURE__*/_jsx(Pressable, {
317
347
  style: StyleSheet.absoluteFill,
@@ -351,6 +381,22 @@ const BottomSheet = /*#__PURE__*/forwardRef((props, ref) => {
351
381
  })
352
382
  })]
353
383
  });
384
+ if (mode === 'modal') {
385
+ return /*#__PURE__*/_jsx(Modal, {
386
+ transparent: true,
387
+ visible: modalVisible,
388
+ onRequestClose: close,
389
+ statusBarTranslucent: true,
390
+ presentationStyle: "overFullScreen",
391
+ animationType: "none",
392
+ supportedOrientations: ['portrait', 'landscape'],
393
+ children: /*#__PURE__*/_jsx(GestureHandlerRootView, {
394
+ style: styles.modalRoot,
395
+ children: sheetTree
396
+ })
397
+ });
398
+ }
399
+ return sheetTree;
354
400
  });
355
401
  BottomSheet.displayName = 'BottomSheet';
356
402
 
@@ -390,6 +436,9 @@ const buildStyles = _theme => StyleSheet.create({
390
436
  },
391
437
  content: {
392
438
  flex: 1
439
+ },
440
+ modalRoot: {
441
+ flex: 1
393
442
  }
394
443
  });
395
444
  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.15",
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",