@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 +11 -51
- package/Layout/utils.ts +72 -3
- package/package.json +34 -34
package/Layout/Screen.tsx
CHANGED
|
@@ -25,13 +25,7 @@ import {
|
|
|
25
25
|
View,
|
|
26
26
|
ViewProps,
|
|
27
27
|
} from 'react-native';
|
|
28
|
-
import
|
|
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
|
-
|
|
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
|
-
<
|
|
673
|
+
<View
|
|
716
674
|
style={[
|
|
717
675
|
styles.shadow,
|
|
718
|
-
{
|
|
719
|
-
|
|
676
|
+
{
|
|
677
|
+
paddingBottom: bottomInset + Spacing.S,
|
|
678
|
+
backgroundColor: theme.colors.background.surface,
|
|
679
|
+
},
|
|
720
680
|
]}
|
|
721
681
|
>
|
|
722
682
|
<Section>{Footer}</Section>
|
|
723
|
-
</
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
}
|