@mrmeg/expo-ui 0.7.1 → 0.7.2

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.
@@ -40,6 +40,9 @@ import { BottomSheetKeyboardController, useBottomSheetKeyboardAnimation, } from
40
40
  */
41
41
  // Platform-specific overlay wrapper
42
42
  const FullWindowOverlay = Platform.OS === "ios" ? RNFullWindowOverlay : React.Fragment;
43
+ // Floor for the keyboard-avoiding height shrink, so a tall keyboard on a short
44
+ // screen can't collapse the sheet to nothing.
45
+ const MIN_KEYBOARD_SHEET_HEIGHT = 220;
43
46
  // ============================================================================
44
47
  // Context
45
48
  // ============================================================================
@@ -52,19 +55,51 @@ function useBottomSheetContext() {
52
55
  return context;
53
56
  }
54
57
  const DragContext = createContext(null);
55
- function BottomSheetPanel({ accessibilityViewIsModal, children, panHandlers, sheetStyle, styleOverride, translateY, ...props }) {
58
+ function BottomSheetPanel({ accessibilityViewIsModal, animatedHeight, animatedBottom, children, panHandlers, sheetStyle, styleOverride, translateY, ...props }) {
56
59
  return (_jsx(Animated.View, { style: [
57
60
  sheetStyle,
61
+ // Layout overrides (JS-driven) must come after sheetStyle so they win.
62
+ animatedBottom ? { bottom: animatedBottom } : undefined,
63
+ animatedHeight ? { height: animatedHeight } : undefined,
64
+ // Open/close + drag (native-driven) live on transform, a separate node.
58
65
  { transform: [{ translateY }] },
59
66
  styleOverride && typeof styleOverride !== "function"
60
67
  ? StyleSheet.flatten(styleOverride)
61
68
  : undefined,
62
69
  ], accessibilityViewIsModal: accessibilityViewIsModal, ...panHandlers, ...props, children: children }));
63
70
  }
71
+ /**
72
+ * Lifts the sheet above the keyboard while keeping its top edge on-screen.
73
+ *
74
+ * The naive approach translates the whole rigid box up by the keyboard height,
75
+ * which shoves the header (and any inputs near the top) off the top of the
76
+ * screen on tall sheets. Instead we lift the sheet's bottom to sit just above
77
+ * the keyboard and shrink its height by the same amount, so the top edge holds
78
+ * steady. The flex Body soaks up the lost height and scrolls, leaving the
79
+ * header, focused input, and footer all visible.
80
+ *
81
+ * This drives layout props (`bottom`/`height`) with a JS-driven keyboard value,
82
+ * intentionally separate from the native-driven open/close `translateY` — a
83
+ * single Animated.Value can't feed both a native transform and a JS layout
84
+ * prop, but distinct nodes on the same view can.
85
+ */
64
86
  function KeyboardAvoidingBottomSheetPanel(props) {
65
87
  const { height: keyboardHeight } = useBottomSheetKeyboardAnimation();
66
- const composedTranslateY = useMemo(() => Animated.add(props.translateY, keyboardHeight), [keyboardHeight, props.translateY]);
67
- return _jsx(BottomSheetPanel, { ...props, translateY: composedTranslateY });
88
+ // sheetStyle.height is the resolved max snap height (a number, set by Content).
89
+ const baseHeight = typeof props.sheetStyle.height === "number" ? props.sheetStyle.height : undefined;
90
+ const animatedHeight = useMemo(() => {
91
+ if (baseHeight === undefined)
92
+ return undefined;
93
+ // keyboardHeight: 0 (closed) → keyboard px (open). Shrink the sheet by it,
94
+ // clamped to a usable minimum so it never collapses on short screens.
95
+ const shrunk = Animated.subtract(baseHeight, keyboardHeight);
96
+ return shrunk.interpolate({
97
+ inputRange: [MIN_KEYBOARD_SHEET_HEIGHT, baseHeight],
98
+ outputRange: [MIN_KEYBOARD_SHEET_HEIGHT, baseHeight],
99
+ extrapolate: "clamp",
100
+ });
101
+ }, [baseHeight, keyboardHeight]);
102
+ return (_jsx(BottomSheetPanel, { ...props, animatedHeight: animatedHeight, animatedBottom: keyboardHeight }));
68
103
  }
69
104
  // ============================================================================
70
105
  // Utility Functions
@@ -1,10 +1,14 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { Animated, Keyboard, Platform } from "react-native";
3
+ // This value drives layout props (a sheet's `bottom`/`height`), which the
4
+ // native animation driver can't touch — so timings here must stay on the JS
5
+ // driver. It also means the value is a positive keyboard height (0 when
6
+ // hidden), letting consumers both lift and shrink the sheet from one source.
3
7
  function animateKeyboardOffset(value, toValue, duration = 180) {
4
8
  Animated.timing(value, {
5
9
  toValue,
6
10
  duration,
7
- useNativeDriver: true,
11
+ useNativeDriver: false,
8
12
  }).start();
9
13
  }
10
14
  export function useBottomSheetKeyboardAnimation() {
@@ -16,7 +20,7 @@ export function useBottomSheetKeyboardAnimation() {
16
20
  const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
17
21
  const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
18
22
  const showSubscription = Keyboard.addListener(showEvent, (event) => {
19
- animateKeyboardOffset(keyboardHeight, -event.endCoordinates.height, event.duration || 180);
23
+ animateKeyboardOffset(keyboardHeight, event.endCoordinates.height, event.duration || 180);
20
24
  });
21
25
  const hideSubscription = Keyboard.addListener(hideEvent, (event) => {
22
26
  animateKeyboardOffset(keyboardHeight, 0, event.duration || 160);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrmeg/expo-ui",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "private": false,
5
5
  "description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
6
6
  "keywords": [
@@ -24,7 +24,8 @@
24
24
  "homepage": "https://github.com/mrmeg/expo-template/tree/main/packages/ui",
25
25
  "type": "module",
26
26
  "publishConfig": {
27
- "access": "public"
27
+ "access": "public",
28
+ "registry": "https://registry.npmjs.org/"
28
29
  },
29
30
  "main": "./dist/index.js",
30
31
  "types": "./dist/index.d.ts",