@momo-kits/foundation 0.167.1-duotest.2 → 0.167.1

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.
Files changed (43) hide show
  1. package/Application/BottomSheet.tsx +14 -115
  2. package/Application/BottomTab/BottomTabBar.tsx +3 -6
  3. package/Application/BottomTab/index.tsx +135 -162
  4. package/Application/Components/HeaderExtendHeader.tsx +28 -75
  5. package/Application/Components/HeaderLeft.tsx +0 -38
  6. package/Application/Components/HeaderRight.tsx +9 -142
  7. package/Application/Components/HeaderTitle.tsx +7 -43
  8. package/Application/Components/SearchHeader.tsx +15 -22
  9. package/Application/ModalScreen.tsx +2 -16
  10. package/Application/NavigationContainer.tsx +38 -22
  11. package/Application/Navigator.ts +3 -1
  12. package/Application/StackScreen.tsx +9 -63
  13. package/Application/types.ts +1 -27
  14. package/Application/utils.tsx +6 -242
  15. package/Consts/theme.ts +4 -3
  16. package/Input/Input.tsx +4 -3
  17. package/Input/InputMoney.tsx +8 -5
  18. package/Input/InputPhoneNumber.tsx +4 -3
  19. package/Input/InputSearch.tsx +4 -3
  20. package/Input/InputTextArea.tsx +4 -3
  21. package/Layout/Card.tsx +26 -8
  22. package/Layout/FloatingButton.tsx +3 -3
  23. package/Layout/GridSystem.tsx +137 -0
  24. package/Layout/Item.tsx +10 -1
  25. package/Layout/Screen.tsx +52 -199
  26. package/Layout/Section.tsx +18 -5
  27. package/Layout/index.ts +4 -4
  28. package/Layout/styles.ts +0 -5
  29. package/Layout/utils.ts +5 -465
  30. package/Popup/PopupNotify.tsx +22 -33
  31. package/Popup/PopupPromotion.tsx +8 -20
  32. package/Text/utils.ts +2 -1
  33. package/package.json +2 -3
  34. package/Application/BottomTab/DuoBarCompression.tsx +0 -135
  35. package/Application/BottomTab/DuoTabBar.tsx +0 -462
  36. package/Application/Components/DuoGlassBackground.tsx +0 -179
  37. package/Application/Components/DuoQuickReturnHeader.tsx +0 -42
  38. package/Application/Components/OptionalGlassView.ts +0 -11
  39. package/Application/Components/OptionalScrollObserver.ts +0 -12
  40. package/Application/Components/SideBarButton.tsx +0 -139
  41. package/Application/device.ts +0 -29
  42. package/Layout/ReservedRegions.tsx +0 -47
  43. package/Popup/utils.ts +0 -32
@@ -1,24 +1,14 @@
1
- import React, {
2
- useCallback,
3
- useContext,
4
- useEffect,
5
- useRef,
6
- useState,
7
- } from 'react';
1
+ import React, { useCallback, useContext, useEffect, useRef } from 'react';
8
2
  import {
9
3
  BackHandler,
10
4
  Dimensions,
11
- Keyboard,
12
5
  KeyboardAvoidingView,
13
- KeyboardEvent,
14
- LayoutAnimation,
15
6
  Modal,
16
7
  PanResponder,
17
8
  Platform,
18
9
  Pressable,
19
10
  StyleSheet,
20
11
  TouchableOpacity,
21
- useWindowDimensions,
22
12
  View,
23
13
  } from 'react-native';
24
14
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -32,38 +22,22 @@ import Animated, {
32
22
  Easing,
33
23
  Extrapolation,
34
24
  interpolate,
25
+ runOnJS,
35
26
  useAnimatedStyle,
36
27
  useSharedValue,
37
28
  withSpring,
38
29
  withTiming,
39
30
  } from 'react-native-reanimated';
40
- import { scheduleOnRN } from 'react-native-worklets';
41
31
  import layoutStyles from '../Layout/styles';
42
- import { getDuoTopInset, isIPhoneDuo } from './device';
43
-
44
- // iPhone Duo inner display: center the sheet instead of spanning the width
45
- const DUO_REGULAR_MIN_WIDTH = 600;
46
32
 
47
33
  const BottomSheet: React.FC<BottomSheetParams> = props => {
48
34
  const { theme, navigator } = useContext(ApplicationContext);
49
35
  const context = useContext<any>(MiniAppContext);
50
- const windowSize = useWindowDimensions();
51
- const isDuo = isIPhoneDuo();
52
- const isDuoCentered = isDuo && windowSize.width >= DUO_REGULAR_MIN_WIDTH;
53
- const isPortrait = windowSize.height > windowSize.width;
54
- // 'screen' is the outer display on iPhone Duo, even when unfolded
55
- const heightDevice = isDuo
56
- ? windowSize.height
57
- : Dimensions.get('screen').height;
36
+ const heightDevice = Dimensions.get('screen').height;
58
37
  const action = useRef<undefined | string>(undefined);
59
38
  const insets = useSafeAreaInsets();
60
39
  const heightHeader = useHeaderHeight();
61
- const topGap = Math.max(
62
- getDuoTopInset(insets, windowSize.width, windowSize.height),
63
- Spacing.XL,
64
- );
65
- const bottomInset =
66
- Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
40
+ const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
67
41
  const keyboardOffset = heightHeader - bottomInset;
68
42
 
69
43
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
@@ -84,43 +58,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
84
58
  }: BottomSheetParams = props.route.params;
85
59
 
86
60
  const translateY = useSharedValue(heightDevice);
87
- const [keyboardLift, setKeyboardLift] = useState(0);
88
-
89
- // KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
90
- // so lift it here and shrink maxHeight by the same amount instead
91
- useEffect(() => {
92
- if (!isDuo || !useKeyboardAvoidingView) {
93
- return;
94
- }
95
- const onKeyboardChange = (e: KeyboardEvent) => {
96
- const overlap = windowSize.height - e.endCoordinates.screenY;
97
- const lift = Math.max(
98
- overlap - bottomInset - (keyboardVerticalOffset ?? 0),
99
- 0,
100
- );
101
- if (e.duration) {
102
- LayoutAnimation.configureNext({
103
- duration: e.duration,
104
- update: {
105
- duration: e.duration,
106
- type: LayoutAnimation.Types[e.easing] || 'keyboard',
107
- },
108
- });
109
- }
110
- setKeyboardLift(lift);
111
- };
112
- const subscriptions = [
113
- Keyboard.addListener('keyboardWillChangeFrame', onKeyboardChange),
114
- Keyboard.addListener('keyboardWillHide', onKeyboardChange),
115
- ];
116
- return () => subscriptions.forEach(sub => sub.remove());
117
- }, [
118
- bottomInset,
119
- isDuo,
120
- keyboardVerticalOffset,
121
- useKeyboardAvoidingView,
122
- windowSize.height,
123
- ]);
124
61
 
125
62
  const openAnimation = useCallback(() => {
126
63
  translateY.value = withTiming(0, {
@@ -137,7 +74,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
137
74
  duration: 200,
138
75
  easing: Easing.bezier(0.3, 0.0, 0.8, 0.15),
139
76
  },
140
- () => scheduleOnRN(callback),
77
+ () => runOnJS(callback)(),
141
78
  );
142
79
  },
143
80
  [heightDevice, translateY],
@@ -200,9 +137,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
200
137
  }
201
138
  closeAnimation(() => {
202
139
  navigator?.pop();
203
- scheduleOnRN(() => {
140
+ runOnJS(() => {
204
141
  callback?.();
205
- });
142
+ })();
206
143
  });
207
144
  },
208
145
  [barrierDismissible, closeAnimation, navigator],
@@ -311,23 +248,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
311
248
  };
312
249
  });
313
250
 
314
- // landscape side buttons span the full height, so inset the content; in
315
- // portrait the side status bar only covers the top corner
316
- const sideInsetStyle = isDuo &&
317
- !isDuoCentered &&
318
- !isPortrait && {
319
- paddingLeft: insets.left,
320
- paddingRight: insets.right,
321
- };
322
-
323
- const screen = (
324
- <Screen
325
- {...props}
326
- {...props.route.params}
327
- onRequestClose={onRequestClose}
328
- />
329
- );
330
-
331
251
  return (
332
252
  <Container
333
253
  transparent
@@ -344,7 +264,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
344
264
  android: undefined,
345
265
  })}
346
266
  keyboardVerticalOffset={keyboardVerticalOffset ?? keyboardOffset}
347
- enabled={useKeyboardAvoidingView && !isDuo}
267
+ enabled={useKeyboardAvoidingView}
348
268
  contentContainerStyle={styles.container}
349
269
  style={styles.container}
350
270
  >
@@ -367,34 +287,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
367
287
  borderTopRightRadius: Radius.M,
368
288
  overflow: 'hidden',
369
289
  },
370
- isDuo && {
371
- maxHeight: windowSize.height - topGap - keyboardLift,
372
- marginBottom: keyboardLift,
373
- },
374
- isDuoCentered && {
375
- // keeps the portrait width in landscape, like system sheets
376
- width: Math.min(
377
- Math.min(windowSize.width, windowSize.height) - Spacing.L * 2,
378
- windowSize.width -
379
- Math.max(insets.left, insets.right, Spacing.L) * 2,
380
- ),
381
- alignSelf: 'center',
382
- },
383
290
  showBaseLineDebug && styles.debugBaseLine,
384
291
  ]}
385
292
  >
386
- {isDuo ? (
387
- // content shrinks so a tall sheet keeps its header and footer
388
- <View style={[styles.shrink, sideInsetStyle]}>
389
- {renderHeader()}
390
- <View style={styles.shrink}>{screen}</View>
391
- </View>
392
- ) : (
393
- <>
394
- {renderHeader()}
395
- {screen}
396
- </>
397
- )}
293
+ {renderHeader()}
294
+ <Screen
295
+ {...props}
296
+ {...props.route.params}
297
+ onRequestClose={onRequestClose}
298
+ />
398
299
  {footerComponent && (
399
300
  <View
400
301
  style={[
@@ -403,7 +304,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
403
304
  paddingBottom: Spacing.S,
404
305
  backgroundColor: theme.colors.background.surface,
405
306
  },
406
- sideInsetStyle,
407
307
  ]}
408
308
  >
409
309
  {footerComponent}
@@ -453,7 +353,6 @@ const styles = StyleSheet.create({
453
353
  justifyContent: 'center',
454
354
  marginHorizontal: Spacing.M,
455
355
  },
456
- shrink: { flexShrink: 1 },
457
356
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
458
357
  });
459
358
 
@@ -58,13 +58,11 @@ export default function BottomTabBar({
58
58
  const { theme } = useContext(ApplicationContext);
59
59
  const { routes } = state;
60
60
  const widthDevice = useWindowDimensions()?.width;
61
- const insets = useSafeAreaInsets();
62
61
  const useFloating = floatingButton && state.routes.length % 2 === 0;
63
- const itemWidth =
64
- (widthDevice - insets.left - insets.right) /
65
- (state.routes.length + (useFloating ? 1 : 0));
62
+ const itemWidth = widthDevice / (state.routes.length + (useFloating ? 1 : 0));
66
63
  // const buildLink = useLinkBuilder();
67
64
  const indicatorAnimated = useSharedValue(0);
65
+ const insets = useSafeAreaInsets();
68
66
 
69
67
  const indicatorStyle = useAnimatedStyle(() => ({
70
68
  width: itemWidth,
@@ -248,8 +246,7 @@ export default function BottomTabBar({
248
246
  },
249
247
  {
250
248
  paddingBottom: paddingBottom,
251
- paddingLeft: insets.left,
252
- paddingRight: insets.right,
249
+ paddingHorizontal: Math.max(insets.left, insets.right),
253
250
  },
254
251
  style,
255
252
  ]}
@@ -27,15 +27,7 @@ import StackScreen from '../StackScreen';
27
27
  import { BottomTabProps } from '../types';
28
28
  import { getOptions, getStackOptions, useAppState } from '../utils';
29
29
  import BottomTabBar from './BottomTabBar';
30
- import { DuoTabBar } from './DuoTabBar';
31
- import { DuoBarCompressionProvider } from './DuoBarCompression';
32
- import {
33
- ApplicationContext,
34
- MiniAppContext,
35
- ScreenContext,
36
- } from '../../Context';
37
- import { useScreenLayout } from '../../Layout';
38
- import { getHeaderRowOptions } from '../../Layout/utils';
30
+ import { ApplicationContext, MiniAppContext } from '../../Context';
39
31
 
40
32
  const Tab = createBottomTabNavigator();
41
33
  const Stack = createStackNavigator();
@@ -44,7 +36,7 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
44
36
  const { navigator } = useContext(ApplicationContext);
45
37
  const context = useContext<any>(MiniAppContext);
46
38
  const { nested, options, screen, initialParams } = route?.params;
47
- const headerStyleOptions = getHeaderRowOptions(useScreenLayout());
39
+ const insets = useSafeAreaInsets();
48
40
 
49
41
  const opacityValue = 0.3;
50
42
  const scaleValue = 0.97;
@@ -147,7 +139,11 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
147
139
  return (
148
140
  <Animated.View style={[styles.container, tabAnimatedStyle]}>
149
141
  <Stack.Navigator
150
- screenOptions={headerStyleOptions}
142
+ screenOptions={{
143
+ headerStyle: {
144
+ height: 52 + insets.top,
145
+ },
146
+ }}
151
147
  >
152
148
  <Stack.Screen
153
149
  name="Stack"
@@ -178,7 +174,11 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
178
174
  <NavigationIndependentTree>
179
175
  <NavigationContainer>
180
176
  <Stack.Navigator
181
- screenOptions={headerStyleOptions}
177
+ screenOptions={{
178
+ headerStyle: {
179
+ height: 52 + insets.top,
180
+ },
181
+ }}
182
182
  >
183
183
  <Stack.Screen
184
184
  name="Stack"
@@ -205,17 +205,8 @@ const BottomTab: React.FC<BottomTabProps> = ({
205
205
  navigation,
206
206
  initialRouteName,
207
207
  floatingButton,
208
- compressionBehavior,
209
208
  }) => {
210
209
  const { theme, navigator } = useContext(ApplicationContext);
211
- const screen: any = useContext(ScreenContext);
212
- const { isIPhoneDuo } = useScreenLayout();
213
- // each tab lays itself out, the hosting stack screen must not add a header or insets
214
- screen?.claimHeader?.();
215
- useEffect(() => {
216
- screen?.syncHeader?.();
217
- // eslint-disable-next-line react-hooks/exhaustive-deps
218
- }, []);
219
210
  const dimensions = useWindowDimensions();
220
211
  const insets = useSafeAreaInsets();
221
212
  const initialIndex = tabs.findIndex(i => i.name === initialRouteName);
@@ -289,156 +280,138 @@ const BottomTab: React.FC<BottomTabProps> = ({
289
280
  };
290
281
 
291
282
  return (
292
- <DuoBarCompressionProvider
293
- behavior={compressionBehavior}
294
- tabCount={tabs.length}
295
- hasQr={!!floatingButton}
283
+ <Tab.Navigator
284
+ screenOptions={{
285
+ headerShown: false,
286
+ headerTransparent: true,
287
+ headerBackground: undefined,
288
+ tabBarLabelPosition: 'below-icon',
289
+ tabBarBackground: () => (
290
+ <View
291
+ style={[
292
+ styles.container,
293
+ {
294
+ backgroundColor: theme.colors.background.surface,
295
+ },
296
+ ]}
297
+ >
298
+ <Animated.View style={[styles.indicatorContainer, indicatorStyle]}>
299
+ <View
300
+ style={[
301
+ styles.indicator,
302
+ { backgroundColor: theme.colors.primary },
303
+ ]}
304
+ />
305
+ </Animated.View>
306
+ </View>
307
+ ),
308
+ }}
309
+ tabBar={props => (
310
+ <BottomTabBar
311
+ {...props}
312
+ floatingButton={floatingButton}
313
+ inactiveTintColor={theme.colors.text.hint}
314
+ activeTintColor={theme.colors.primary}
315
+ style={[
316
+ {
317
+ height:
318
+ 64 +
319
+ (Platform.OS === 'ios'
320
+ ? Math.min(insets.bottom, 21)
321
+ : insets.bottom),
322
+ },
323
+ ]}
324
+ />
325
+ )}
326
+ initialRouteName={initialRouteName}
296
327
  >
297
- <Tab.Navigator
298
- screenOptions={{
299
- headerShown: false,
300
- headerTransparent: true,
301
- headerBackground: undefined,
302
- tabBarLabelPosition: 'below-icon',
303
- tabBarBackground: () => (
304
- <View
305
- style={[
306
- styles.container,
307
- {
308
- backgroundColor: theme.colors.background.surface,
309
- },
310
- ]}
311
- >
312
- <Animated.View
313
- style={[styles.indicatorContainer, indicatorStyle]}
314
- >
315
- <View
316
- style={[
317
- styles.indicator,
318
- { backgroundColor: theme.colors.primary },
319
- ]}
320
- />
321
- </Animated.View>
322
- </View>
323
- ),
324
- }}
325
- tabBar={props =>
326
- isIPhoneDuo ? (
327
- <DuoTabBar
328
- {...props}
329
- floatingButton={floatingButton}
330
- inactiveTintColor={theme.colors.text.default}
331
- activeTintColor={theme.colors.primary}
332
- />
333
- ) : (
334
- <BottomTabBar
335
- {...props}
336
- floatingButton={floatingButton}
337
- inactiveTintColor={theme.colors.text.hint}
338
- activeTintColor={theme.colors.primary}
339
- style={[
340
- {
341
- height:
342
- 64 +
343
- (Platform.OS === 'ios'
344
- ? Math.min(insets.bottom, 21)
345
- : insets.bottom),
346
- },
347
- ]}
348
- />
349
- )
328
+ {tabs.map((item, index) => {
329
+ const isNum = !isNaN(parseInt(`${item?.badgeLabel}`, 10));
330
+ const isDot = typeof item?.badgeLabel === 'string' && !item?.badgeLabel;
331
+ const isNumZero = isNum && Number(item.badgeLabel) === 0;
332
+
333
+ let badgeLabel = item?.badgeLabel;
334
+ if (isNumZero) {
335
+ badgeLabel = undefined;
350
336
  }
351
- initialRouteName={initialRouteName}
352
- >
353
- {tabs.map((item, index) => {
354
- const isNum = !isNaN(parseInt(`${item?.badgeLabel}`, 10));
355
- const isDot =
356
- typeof item?.badgeLabel === 'string' && !item?.badgeLabel;
357
- const isNumZero = isNum && Number(item.badgeLabel) === 0;
358
337
 
359
- let badgeLabel = item?.badgeLabel;
360
- if (isNumZero) {
361
- badgeLabel = undefined;
362
- }
338
+ let badgeStyle: {
339
+ marginRight: number;
340
+ top: number;
341
+ backgroundColor: string;
342
+ width?: number;
343
+ } = {
344
+ marginRight: -8,
345
+ top: -4,
346
+ backgroundColor: Colors.orange_03,
347
+ };
363
348
 
364
- let badgeStyle: {
365
- marginRight: number;
366
- top: number;
367
- backgroundColor: string;
368
- width?: number;
369
- } = {
370
- marginRight: -8,
349
+ if (isNum) {
350
+ badgeStyle = {
351
+ marginRight: -4,
371
352
  top: -4,
353
+ backgroundColor: Colors.red_03,
354
+ };
355
+ }
356
+ if (isDot) {
357
+ badgeStyle = {
358
+ marginRight: -2,
359
+ top: -2,
372
360
  backgroundColor: Colors.orange_03,
361
+ width: 12,
373
362
  };
363
+ }
374
364
 
375
- if (isNum) {
376
- badgeStyle = {
377
- marginRight: -4,
378
- top: -4,
379
- backgroundColor: Colors.red_03,
380
- };
381
- }
382
- if (isDot) {
383
- badgeStyle = {
384
- marginRight: -2,
385
- top: -2,
386
- backgroundColor: Colors.orange_03,
387
- width: 12,
388
- };
389
- }
390
-
391
- return (
392
- <Tab.Screen
393
- key={`BottomTab-${item.name}`}
394
- name={item.name}
395
- component={TabScreen}
396
- initialParams={{ ...item, nested, index }}
397
- listeners={handler}
398
- options={{
399
- tabBarLabel: ({ focused, color }) => {
400
- return (
401
- <Text
402
- numberOfLines={1}
403
- typography={
404
- focused ? 'label_xs_medium' : 'description_xs_regular'
405
- }
406
- color={color}
407
- >
408
- {item.label}
409
- </Text>
410
- );
365
+ return (
366
+ <Tab.Screen
367
+ key={`BottomTab-${item.name}`}
368
+ name={item.name}
369
+ component={TabScreen}
370
+ initialParams={{ ...item, nested, index }}
371
+ listeners={handler}
372
+ options={{
373
+ tabBarLabel: ({ focused, color }) => {
374
+ return (
375
+ <Text
376
+ numberOfLines={1}
377
+ typography={
378
+ focused ? 'label_xs_medium' : 'description_xs_regular'
379
+ }
380
+ color={color}
381
+ >
382
+ {item.label}
383
+ </Text>
384
+ );
385
+ },
386
+ title: item.label,
387
+ tabBarStyle: [
388
+ {
389
+ height: 64,
411
390
  },
412
- title: item.label,
413
- tabBarStyle: [
414
- {
415
- height: 64,
416
- },
417
- styles.tabStyle,
418
- ],
419
- tabBarBadge: badgeLabel,
420
- tabBarBadgeStyle: [
421
- {
422
- borderColor: 'white',
423
- borderWidth: 2,
424
- fontSize: 10,
425
- lineHeight: 14,
426
- fontWeight: 'bold',
427
- alignSelf: 'center',
428
- fontFamily: exportFontFamily('bold'),
429
- },
430
- badgeStyle,
431
- ],
432
- tabBarIcon: ({ color }) => (
433
- <Icon source={item.icon} color={color} size={28} />
434
- ),
435
- ...item,
436
- }}
437
- />
438
- );
439
- })}
440
- </Tab.Navigator>
441
- </DuoBarCompressionProvider>
391
+ styles.tabStyle,
392
+ ],
393
+ tabBarBadge: badgeLabel,
394
+ tabBarBadgeStyle: [
395
+ {
396
+ borderColor: 'white',
397
+ borderWidth: 2,
398
+ fontSize: 10,
399
+ lineHeight: 14,
400
+ fontWeight: 'bold',
401
+ alignSelf: 'center',
402
+ fontFamily: exportFontFamily('bold'),
403
+ },
404
+ badgeStyle,
405
+ ],
406
+ tabBarIcon: ({ color }) => (
407
+ <Icon source={item.icon} color={color} size={28} />
408
+ ),
409
+ ...item,
410
+ }}
411
+ />
412
+ );
413
+ })}
414
+ </Tab.Navigator>
442
415
  );
443
416
  };
444
417