@momo-kits/foundation 0.163.1-beta.14 → 0.163.1-beta.16

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/Layout/Screen.tsx CHANGED
@@ -25,13 +25,7 @@ import {
25
25
  View,
26
26
  ViewProps,
27
27
  } from 'react-native';
28
- import Reanimated, {
29
- useAnimatedKeyboard,
30
- useAnimatedStyle,
31
- useSharedValue,
32
- withTiming,
33
- } from 'react-native-reanimated';
34
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
28
+ import { useSharedValue, withTiming } from 'react-native-reanimated';
35
29
  import { ApplicationContext, ScreenContext } from '../Context';
36
30
  import Navigation from '../Application/Navigation';
37
31
  import {
@@ -46,7 +40,7 @@ import styles from './styles';
46
40
  import { HeaderType } from './types';
47
41
  import { InputRef } from '../Input';
48
42
  import { exportHeaderTitle, getOptions } from '../Application/utils';
49
- import { validateChildren } from './utils';
43
+ import { useBottomSafeAreaInset, validateChildren } from './utils';
50
44
  import Section from './Section';
51
45
  import {
52
46
  HeaderBackground,
@@ -200,27 +194,11 @@ const Screen = forwardRef(
200
194
  const { width: widthDevice } = useWindowDimensions();
201
195
  const { theme, navigator } = useContext(ApplicationContext);
202
196
  const screen: any = useContext(ScreenContext);
203
- const insets = useSafeAreaInsets();
197
+ // AI-GENERATED START: keyboard-aware bottom inset (DS-747)
198
+ const bottomInset = useBottomSafeAreaInset();
199
+ // AI-GENERATED END: keyboard-aware bottom inset (DS-747)
204
200
  const heightHeader = useHeaderHeight();
205
201
 
206
- /**
207
- * On Android, the native root view already pads for the full IME inset
208
- * (which itself includes the navigation bar's height) as soon as the
209
- * keyboard shows. `useSafeAreaInsets()`'s own bottom recompute is JS-thread
210
- * work (its own native listener, then a bridge crossing, then a React
211
- * re-render) and always lands at least one frame after that native padding
212
- * change, so any JS-driven correction (state + re-render, regardless of
213
- * which event triggers it) has the same lag — the nav bar height gets
214
- * counted twice for that window, visible as the footer overshooting
215
- * before settling back down.
216
- * `useAnimatedKeyboard()` sidesteps this: its worklet callback updates a
217
- * shared value directly from the same native WindowInsetsAnimation
218
- * callback driving the keyboard's own motion, on the UI thread, with no
219
- * bridge crossing — so the footer's compensating style below can react
220
- * in the same frame instead of a JS tick later.
221
- */
222
- const keyboard = useAnimatedKeyboard();
223
-
224
202
  /**
225
203
  * Dual animation source:
226
204
  * - `animatedValue` is a react-native `Animated.Value` driven natively by the
@@ -261,31 +239,11 @@ const Screen = forwardRef(
261
239
  let handleScroll;
262
240
  let Component: any = View;
263
241
 
264
- const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
265
242
  let keyboardOffset = heightHeader - bottomInset;
266
243
  if (headerType === 'extended' || animatedHeader || inputSearchProps) {
267
244
  keyboardOffset = -bottomInset;
268
245
  }
269
246
 
270
- /**
271
- * Android only: while the keyboard is up, MainActivity's own native inset
272
- * listener already accounts for the full space it needs (keyboard height
273
- * plus nav bar) on the root view, so the footer here should only add its
274
- * usual Spacing.S gap — not `bottomInset` on top, which would double-count
275
- * the nav bar for as long as the keyboard is visible.
276
- *
277
- * `isAndroid` is read here (not inside the worklet) because referencing
278
- * `Platform.OS` directly inside `useAnimatedStyle` captures the whole
279
- * `Platform` module into the worklet closure — Reanimated then fails
280
- * trying to serialize that module object for the UI thread.
281
- */
282
- const isAndroid = Platform.OS === 'android';
283
- const footerAnimatedStyle = useAnimatedStyle(() => {
284
- const paddingBottom =
285
- isAndroid && keyboard.height.value > 0 ? Spacing.S : bottomInset + Spacing.S;
286
- return { paddingBottom };
287
- });
288
-
289
247
  /**
290
248
  * inject params for screen tracking
291
249
  */
@@ -712,15 +670,17 @@ const Screen = forwardRef(
712
670
  )}
713
671
 
714
672
  {Footer && (
715
- <Reanimated.View
673
+ <View
716
674
  style={[
717
675
  styles.shadow,
718
- { backgroundColor: theme.colors.background.surface },
719
- footerAnimatedStyle,
676
+ {
677
+ paddingBottom: bottomInset + Spacing.S,
678
+ backgroundColor: theme.colors.background.surface,
679
+ },
720
680
  ]}
721
681
  >
722
682
  <Section>{Footer}</Section>
723
- </Reanimated.View>
683
+ </View>
724
684
  )}
725
685
  </KeyboardAvoidingView>
726
686
  </View>
package/Layout/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
- import React, { Fragment, ReactElement } from 'react';
2
- import { useWindowDimensions } from 'react-native';
1
+ import React, { Fragment, ReactElement, useEffect, useState } from 'react';
2
+ import { Keyboard, Platform, useWindowDimensions } from 'react-native';
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
3
4
 
4
5
  /**
5
6
  * validate children type
@@ -44,4 +45,72 @@ const useGridSystem = () => {
44
45
  return { numberOfColumns };
45
46
  };
46
47
 
47
- export { useGridSystem, validateChildren };
48
+ // AI-GENERATED START: keyboard-aware bottom safe-area inset (DS-747)
49
+ /**
50
+ * Android 16 (API 36) ignores `android:windowOptOutEdgeToEdgeEnforcement`, so the host
51
+ * app is always edge-to-edge and `insets.bottom` reports the navigation bar height
52
+ * instead of 0. Android <= 15 still honours the opt-out, which is why the footer only
53
+ * jitters on Android 16.
54
+ */
55
+ const IS_EDGE_TO_EDGE_ENFORCED =
56
+ Platform.OS === 'android' && Number(Platform.Version) >= 36;
57
+
58
+ /**
59
+ * Tracks IME visibility. On Android `keyboardDidShow` is emitted as soon as the IME
60
+ * becomes visible, ahead of the root view being resized for it.
61
+ */
62
+ const useIsKeyboardVisible = () => {
63
+ const [isKeyboardVisible, setIsKeyboardVisible] = useState(
64
+ () => IS_EDGE_TO_EDGE_ENFORCED && Keyboard.isVisible(),
65
+ );
66
+
67
+ useEffect(() => {
68
+ if (!IS_EDGE_TO_EDGE_ENFORCED) {
69
+ return;
70
+ }
71
+
72
+ const showSubscription = Keyboard.addListener('keyboardDidShow', () =>
73
+ setIsKeyboardVisible(true),
74
+ );
75
+ const hideSubscription = Keyboard.addListener('keyboardDidHide', () =>
76
+ setIsKeyboardVisible(false),
77
+ );
78
+
79
+ return () => {
80
+ showSubscription.remove();
81
+ hideSubscription.remove();
82
+ };
83
+ }, []);
84
+
85
+ return isKeyboardVisible;
86
+ };
87
+
88
+ /**
89
+ * Bottom safe-area inset for chrome anchored to the bottom edge (footer, floating
90
+ * button, scroll padding).
91
+ *
92
+ * `react-native-safe-area-context` recomputes its insets from an `onPreDraw` pass, so
93
+ * the drop to 0 that happens once the IME covers the navigation bar only reaches JS
94
+ * *after* the root view has already shrunk, and then only as a state update. For those
95
+ * frames the footer keeps padding for a navigation bar the keyboard already covers, so
96
+ * it lifts off the keyboard and snaps back down when the new inset lands. Zeroing the
97
+ * inset on keyboard visibility removes that intermediate state.
98
+ *
99
+ * The keyboard flag is only allowed to *remove* inset, never to restore it: on hide it
100
+ * flips back before the root view grows again, and `insets.bottom` is still 0 at that
101
+ * point, so the restore stays driven by the inset itself and the footer does not jump
102
+ * the other way.
103
+ */
104
+ const useBottomSafeAreaInset = () => {
105
+ const insets = useSafeAreaInsets();
106
+ const isKeyboardVisible = useIsKeyboardVisible();
107
+
108
+ if (Platform.OS === 'ios') {
109
+ return Math.min(insets.bottom, 21);
110
+ }
111
+
112
+ return isKeyboardVisible ? 0 : insets.bottom;
113
+ };
114
+ // AI-GENERATED END: keyboard-aware bottom safe-area inset (DS-747)
115
+
116
+ export { useBottomSafeAreaInset, useGridSystem, validateChildren };
package/package.json CHANGED
@@ -1,35 +1,35 @@
1
1
  {
2
- "name": "@momo-kits/foundation",
3
- "version": "0.163.1-beta.14",
4
- "description": "React Native Component Kits",
5
- "main": "index.ts",
6
- "scripts": {},
7
- "keywords": [
8
- "@momo-kits/foundation"
9
- ],
10
- "dependencies": {
11
- "react-native-fast-image": "git+https://oauth2:TGi6oBj-LdzsKpLXQSoH@gitlab.mservice.com.vn/momo-platform/public/react-native-fast-image.git#v8.11.0",
12
- "@react-navigation/bottom-tabs": "7.4.2",
13
- "@react-navigation/core": "7.12.1",
14
- "@react-navigation/elements": "2.5.2",
15
- "@react-navigation/native": "7.1.14",
16
- "@react-navigation/routers": "7.4.1",
17
- "@react-navigation/stack": "7.4.2",
18
- "react-native-gesture-handler": "2.27.1",
19
- "react-native-linear-gradient": "git+https://oauth2:TGi6oBj-LdzsKpLXQSoH@gitlab.mservice.com.vn/momo-platform/public/react-native-linear-gradient#v3.0.0",
20
- "react-native-reanimated": "4.2.2",
21
- "react-native-safe-area-context": "5.5.2",
22
- "@shopify/flash-list": "2.1.0",
23
- "lottie-react-native": "7.2.4"
24
- },
25
- "peerDependencies": {
26
- "react-native": "*"
27
- },
28
- "devDependencies": {
29
- "@types/color": "3.0.6"
30
- },
31
- "publishConfig": {
32
- "registry": "https://registry.npmjs.org/"
33
- },
34
- "license": "MoMo"
35
- }
2
+ "name": "@momo-kits/foundation",
3
+ "version": "0.163.1-beta.16",
4
+ "description": "React Native Component Kits",
5
+ "main": "index.ts",
6
+ "scripts": {},
7
+ "keywords": [
8
+ "@momo-kits/foundation"
9
+ ],
10
+ "dependencies": {
11
+ "react-native-fast-image": "git+https://oauth2:TGi6oBj-LdzsKpLXQSoH@gitlab.mservice.com.vn/momo-platform/public/react-native-fast-image.git#v8.11.0",
12
+ "@react-navigation/bottom-tabs": "7.4.2",
13
+ "@react-navigation/core": "7.12.1",
14
+ "@react-navigation/elements": "2.5.2",
15
+ "@react-navigation/native": "7.1.14",
16
+ "@react-navigation/routers": "7.4.1",
17
+ "@react-navigation/stack": "7.4.2",
18
+ "react-native-gesture-handler": "2.27.1",
19
+ "react-native-linear-gradient": "git+https://oauth2:TGi6oBj-LdzsKpLXQSoH@gitlab.mservice.com.vn/momo-platform/public/react-native-linear-gradient#v3.0.0",
20
+ "react-native-reanimated": "4.2.2",
21
+ "react-native-safe-area-context": "5.5.2",
22
+ "@shopify/flash-list": "2.1.0",
23
+ "lottie-react-native": "7.2.4"
24
+ },
25
+ "peerDependencies": {
26
+ "react-native": "*"
27
+ },
28
+ "devDependencies": {
29
+ "@types/color": "3.0.6"
30
+ },
31
+ "publishConfig": {
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "license": "MoMo"
35
+ }