@momo-kits/foundation 0.162.3-test.1 → 0.163.1-beta.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.
@@ -16,10 +16,9 @@ import { DeviceEventEmitter } from 'react-native';
16
16
  import StackScreen from './StackScreen';
17
17
  import ModalScreen from './ModalScreen';
18
18
  import Navigator from './Navigator';
19
- import ScaleSizeProvider from './ScaleSizeProvider';
20
19
  import { getModalOptions, getStackOptions } from './utils';
21
20
  import { NavigationContainerProps } from './types';
22
- import { ApplicationContext, FontScaleConfig, MiniAppContext } from '../Context';
21
+ import { ApplicationContext, MiniAppContext } from '../Context';
23
22
  import Localize from './Localize';
24
23
  import { Colors, defaultTheme } from '../Consts';
25
24
  import { HeaderLeft } from './Components/HeaderLeft';
@@ -29,8 +28,6 @@ import { HeaderBackground } from './Components/HeaderBackground';
29
28
 
30
29
  const Stack = createStackNavigator();
31
30
 
32
- const FONT_SCALE_OBSERVER_KEY = 'font_scale_config';
33
-
34
31
  const NavigationContainer: React.FC<NavigationContainerProps> = ({
35
32
  screen,
36
33
  theme = defaultTheme,
@@ -47,9 +44,13 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
47
44
  }) => {
48
45
  const context = useContext<any>(MiniAppContext);
49
46
  const [currentContext, setCurrentContext] = useState<any>({});
50
- const [observerFontScaleConfig, setObserverFontScaleConfig] = useState<
51
- FontScaleConfig | undefined
52
- >(context?.fontScaleConfig);
47
+
48
+ useEffect(() => {
49
+ const sub = maxApi?.observer?.('font_scale_config', (data: any) => {
50
+ setCurrentContext((prev: any) => ({ ...prev, fontScale: data }));
51
+ });
52
+ return () => sub?.remove?.();
53
+ }, [maxApi]);
53
54
 
54
55
  const mergedContext = {
55
56
  ...context,
@@ -60,34 +61,18 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
60
61
  },
61
62
  };
62
63
 
63
- useEffect(() => {
64
- const sub = maxApi?.observer?.(
65
- FONT_SCALE_OBSERVER_KEY,
66
- (data: FontScaleConfig) => {
67
- setObserverFontScaleConfig(data ?? undefined);
68
- },
69
- );
70
- return () => {
71
- sub?.remove?.();
72
- };
73
- }, [maxApi]);
74
-
75
64
  return (
76
65
  <SafeAreaProvider>
77
66
  <MiniAppContext.Provider value={mergedContext}>
78
- <ScaleSizeProvider
79
- fontScaleConfig={observerFontScaleConfig}
80
- >
81
- <Navigation
82
- screen={screen}
83
- theme={theme}
84
- options={options}
85
- maxApi={maxApi}
86
- setCurrentContext={setCurrentContext}
87
- initialParams={initialParams}
88
- localize={localize}
89
- />
90
- </ScaleSizeProvider>
67
+ <Navigation
68
+ screen={screen}
69
+ theme={theme}
70
+ options={options}
71
+ maxApi={maxApi}
72
+ setCurrentContext={setCurrentContext}
73
+ initialParams={initialParams}
74
+ localize={localize}
75
+ />
91
76
  </MiniAppContext.Provider>
92
77
  </SafeAreaProvider>
93
78
  );
@@ -1,15 +1,14 @@
1
- import React, { FC, useContext } from 'react';
1
+ import React, { FC } from 'react';
2
2
  import { ScaleSizeContext } from '../Context';
3
3
  import { ScaleSizeProviderProps } from './types';
4
4
 
5
+ /** @deprecated Max font scale is fixed at 1.5; this provider no longer affects scaling. */
5
6
  const ScaleSizeProvider: FC<ScaleSizeProviderProps> = ({
6
- fontScaleConfig,
7
+ scaleSizeMaxRate,
7
8
  children,
8
9
  }) => {
9
- // Inherit the config from an upper layer (e.g. app root) when the host doesn't set it via MiniAppContext.
10
- const parent = useContext(ScaleSizeContext);
11
10
  return (
12
- <ScaleSizeContext.Provider value={fontScaleConfig ?? parent}>
11
+ <ScaleSizeContext.Provider value={{ scaleSizeMaxRate }}>
13
12
  {children}
14
13
  </ScaleSizeContext.Provider>
15
14
  );
@@ -1,4 +1,4 @@
1
- import React, { useContext, useRef, useState } from 'react';
1
+ import React, { useContext, useEffect, useRef, useState } from 'react';
2
2
  import { SafeAreaProvider } from 'react-native-safe-area-context';
3
3
  import Navigator from './Navigator';
4
4
  import ScaleSizeProvider from './ScaleSizeProvider';
@@ -44,6 +44,13 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
44
44
  const navigator = useRef(new Navigator(navigationRef, isReady, true));
45
45
  const [currentContext, setCurrentContext] = useState({});
46
46
 
47
+ useEffect(() => {
48
+ const sub = maxApi?.observer?.('font_scale_config', (data: any) => {
49
+ setCurrentContext((prev: any) => ({ ...prev, fontScale: data }));
50
+ });
51
+ return () => sub?.remove?.();
52
+ }, [maxApi]);
53
+
47
54
  let headerBackground = context?.designConfig?.headerBar;
48
55
  let headerGradient = theme.colors?.gradient;
49
56
 
@@ -74,12 +81,9 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
74
81
  };
75
82
 
76
83
  return (
77
- <View style={{ height: height ?? '100%', minHeight:1 }}>
84
+ <View style={{ height: height ?? '100%', minHeight: 1 }}>
78
85
  <SafeAreaProvider>
79
86
  <MiniAppContext.Provider value={mergedContext}>
80
- <ScaleSizeProvider
81
- fontScaleConfig={mergedContext?.fontScaleConfig}
82
- >
83
87
  <ApplicationContext.Provider
84
88
  value={{
85
89
  navigator: navigator.current,
@@ -157,7 +161,6 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
157
161
  </ReactNavigationContainer>
158
162
  </NavigationIndependentTree>
159
163
  </ApplicationContext.Provider>
160
- </ScaleSizeProvider>
161
164
  </MiniAppContext.Provider>
162
165
  </SafeAreaProvider>
163
166
  </View>
@@ -10,7 +10,6 @@ import {
10
10
  import type { SharedValue } from 'react-native-reanimated';
11
11
  import { PopupNotifyProps } from '../Popup/types';
12
12
  import { InputRef, InputSearchProps } from '../Input';
13
- import { FontScaleConfig } from '../Context';
14
13
  import Navigation from './Navigation';
15
14
 
16
15
  export type NavigationProps = {
@@ -110,7 +109,7 @@ export type Theme = {
110
109
  };
111
110
 
112
111
  export type ScaleSizeProviderProps = {
113
- fontScaleConfig?: FontScaleConfig;
112
+ scaleSizeMaxRate?: number;
114
113
  children: ViewProps['children'];
115
114
  };
116
115
 
package/Context/index.ts CHANGED
@@ -9,12 +9,10 @@ const MiniAppContext = (Platform as any).MiniAppContext ?? Context;
9
9
  const ScreenContext = (Platform as any).ScreenContext ?? Context;
10
10
  const ComponentContext = (Platform as any).ComponentContext ?? Context;
11
11
  const SkeletonContext = createContext({ loading: false });
12
- // Single source of truth: holds both the OS flag and the user-adjusted rate.
13
- export type FontScaleConfig = {
14
- useOSFontScale?: boolean;
15
- userScaleRate?: number;
16
- };
17
- const ScaleSizeContext = createContext<FontScaleConfig>({});
12
+ /** @deprecated Max font scale is fixed at 1.5; this context is no longer read. */
13
+ const ScaleSizeContext = createContext<{ scaleSizeMaxRate?: number }>({
14
+ scaleSizeMaxRate: undefined,
15
+ });
18
16
  const TrackingScopeContext = createContext<{ scopeName?: string }>({
19
17
  scopeName: undefined,
20
18
  });
@@ -28,7 +28,12 @@ export interface IconButtonProps extends TouchableOpacityProps {
28
28
  /**
29
29
  * Optional. Defines the size of the IconButton.
30
30
  */
31
- size?: 'large' | 'small';
31
+ size?: 'large' | 'medium' | 'small';
32
+
33
+ /**
34
+ * Optional. Defines the shape of the IconButton.
35
+ */
36
+ shape?: 'circle' | 'square';
32
37
 
33
38
  /**
34
39
  * Optional. Params auto tracking component.
@@ -40,6 +45,7 @@ const IconButton: React.FC<IconButtonProps> = ({
40
45
  type = 'primary',
41
46
  icon,
42
47
  size,
48
+ shape = 'circle',
43
49
  params,
44
50
  ...rest
45
51
  }) => {
@@ -52,10 +58,21 @@ const IconButton: React.FC<IconButtonProps> = ({
52
58
  * get size icon button
53
59
  */
54
60
  const getSizeStyle = () => {
55
- if (size === 'small') {
56
- return styles.small;
61
+ switch (size) {
62
+ case 'small':
63
+ return styles.small;
64
+ case 'medium':
65
+ return styles.medium;
66
+ default:
67
+ return styles.large;
57
68
  }
58
- return styles.large;
69
+ };
70
+
71
+ /**
72
+ * get shape icon button
73
+ */
74
+ const getShapeStyle = () => {
75
+ return shape === 'square' ? styles.square : styles.circle;
59
76
  };
60
77
 
61
78
  /**
@@ -117,8 +134,13 @@ const IconButton: React.FC<IconButtonProps> = ({
117
134
  };
118
135
 
119
136
  const activeOpacity = type === 'disabled' ? 0.75 : 0.5;
120
- const buttonStyle = StyleSheet.flatten([getTypeStyle(), getSizeStyle()]);
121
- const iconSize = size === 'small' ? 16 : 24;
137
+ const buttonStyle = StyleSheet.flatten([
138
+ getTypeStyle(),
139
+ styles.base,
140
+ getSizeStyle(),
141
+ getShapeStyle(),
142
+ ]);
143
+ const iconSize = size === 'small' || size === 'medium' ? 16 : 24;
122
144
 
123
145
  return (
124
146
  <ComponentContext.Provider
@@ -2,19 +2,27 @@ import { StyleSheet } from 'react-native';
2
2
  import { Radius, Colors } from '../Consts';
3
3
 
4
4
  export default StyleSheet.create({
5
+ base: {
6
+ justifyContent: 'center',
7
+ alignItems: 'center',
8
+ },
5
9
  large: {
6
10
  width: 48,
7
11
  height: 48,
8
- borderRadius: Radius.XL,
9
- justifyContent: 'center',
10
- alignItems: 'center',
12
+ },
13
+ medium: {
14
+ width: 36,
15
+ height: 36,
11
16
  },
12
17
  small: {
13
- width: 40,
14
- height: 40,
18
+ width: 28,
19
+ height: 28,
20
+ },
21
+ circle: {
15
22
  borderRadius: Radius.XL,
16
- justifyContent: 'center',
17
- alignItems: 'center',
23
+ },
24
+ square: {
25
+ borderRadius: Radius.S,
18
26
  },
19
27
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
20
28
  });
package/Input/Input.tsx CHANGED
@@ -113,6 +113,13 @@ const Input = forwardRef(
113
113
  };
114
114
 
115
115
  const _setText = (text: string) => {
116
+ if (__DEV__ && value !== undefined) {
117
+ console.error(
118
+ `${componentName}: calling ref.setText() on a controlled input (\`value\` is provided) is an anti-pattern. ` +
119
+ 'It creates two sources of truth: setText will be overridden by React on the next render. ' +
120
+ 'Update the state behind `value` instead.',
121
+ );
122
+ }
116
123
  inputRef?.current?.setNativeProps({ text });
117
124
  _onChangeText(text);
118
125
  };
@@ -104,6 +104,13 @@ const InputPhoneNumber = forwardRef(
104
104
  };
105
105
 
106
106
  const _setText = (text: string) => {
107
+ if (__DEV__ && value !== undefined) {
108
+ console.error(
109
+ `${componentName}: calling ref.setText() on a controlled input (\`value\` is provided) is an anti-pattern. ` +
110
+ 'It creates two sources of truth: setText will be overridden by React on the next render. ' +
111
+ 'Update the state behind `value` instead.',
112
+ );
113
+ }
107
114
  inputRef?.current?.setNativeProps({ text });
108
115
  _onChangeText(text);
109
116
  };
package/Layout/Screen.tsx CHANGED
@@ -166,6 +166,13 @@ export interface ScreenProps extends ViewProps {
166
166
  trackingParams?: ScreenTrackingParams;
167
167
  }
168
168
 
169
+ /**
170
+ * Scroll distance (px) over which the animated search header collapses from its
171
+ * expanded to its docked state — the input range consumed by `HeaderExtendHeader`
172
+ * and the settle threshold in `handleScrollEnd`.
173
+ */
174
+ const SEARCH_HEADER_COLLAPSE_DISTANCE = 100;
175
+
169
176
  const Screen = forwardRef(
170
177
  (
171
178
  {
@@ -195,7 +202,7 @@ const Screen = forwardRef(
195
202
  ref: any,
196
203
  ) => {
197
204
  const screenRef = useRef<View | ScrollView>(null);
198
- const { width: widthDevice } = useWindowDimensions();
205
+ const { width: widthDevice, height: heightDevice } = useWindowDimensions();
199
206
  const { theme } = useContext(ApplicationContext);
200
207
  const screen: any = useContext(ScreenContext);
201
208
  const insets = useSafeAreaInsets();
@@ -650,7 +657,25 @@ const Screen = forwardRef(
650
657
  onScroll={handleScroll}
651
658
  onScrollEndDrag={handleScrollEnd}
652
659
  scrollEventThrottle={16}
653
- style={Styles.flex}
660
+ style={[
661
+ Styles.flex,
662
+ !scrollable && !Footer && !isTab && { paddingBottom: bottomInset },
663
+ ]}
664
+ contentContainerStyle={[
665
+ scrollable && !Footer && !isTab && { paddingBottom: bottomInset },
666
+ // The animated search header collapses as the body scrolls. When the
667
+ // body is short (e.g. no search history) the ScrollView is barely
668
+ // scrollable, so overscroll bounce feeds the scroll-driven animation
669
+ // and the input jitters on its way up to the header. A ScrollView
670
+ // frame can never exceed the window, so forcing the content to be at
671
+ // least one screen plus the collapse distance guarantees the animation
672
+ // always has room to complete smoothly, independent of body content.
673
+ scrollable &&
674
+ inputSearchProps && {
675
+ minHeight: heightDevice + SEARCH_HEADER_COLLAPSE_DISTANCE,
676
+ },
677
+ scrollViewProps?.contentContainerStyle,
678
+ ]}
654
679
  >
655
680
  {renderAnimatedHeader()}
656
681
 
package/Text/utils.ts CHANGED
@@ -1,24 +1,22 @@
1
1
  import { Dimensions, PixelRatio, useWindowDimensions } from 'react-native';
2
2
  import { useContext } from 'react';
3
- import { ScaleSizeContext } from '../Context';
3
+ import { MiniAppContext } from '../Context';
4
4
 
5
5
  const deviceWidth = Dimensions.get('window').width;
6
6
  const DEFAULT_SCREEN_SIZE = 375;
7
7
  const MAX_FONT_SCALE = 1.5;
8
8
  const MAX_DEVICE_SCALE = 5;
9
9
 
10
- const useScaleSize = (size: number, userScaleRate?: number) => {
11
- const { useOSFontScale = true, userScaleRate: ctxRate } =
12
- useContext(ScaleSizeContext);
10
+ const useScaleSize = (size: number, scaleRate?: number) => {
11
+ const context = useContext<any>(MiniAppContext);
12
+ if (context?.fontScale?.useOSScaleRate === false) {
13
+ return size * (context.fontScale.userScaleRate ?? 1);
14
+ }
13
15
  const fontScale = PixelRatio.getFontScale();
14
16
  const { width } = useWindowDimensions();
15
17
  const deviceScale = width / DEFAULT_SCREEN_SIZE;
16
18
 
17
- const customRate = userScaleRate ?? ctxRate ?? 1;
18
-
19
- if (!useOSFontScale) {
20
- return customRate > 1 ? customRate * size : size;
21
- }
19
+ const maxScaleRate = scaleRate ?? MAX_FONT_SCALE;
22
20
 
23
21
  let fontSizeDeviceScale = size;
24
22
  let fontSizeOSScale = size;
@@ -32,8 +30,8 @@ const useScaleSize = (size: number, userScaleRate?: number) => {
32
30
 
33
31
  if (fontScale > 1) {
34
32
  fontSizeOSScale = Math.min(
35
- fontScale * fontSizeOSScale,
36
- fontSizeOSScale * MAX_FONT_SCALE,
33
+ fontSizeOSScale * fontScale,
34
+ fontSizeOSScale * maxScaleRate,
37
35
  );
38
36
  }
39
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.162.3-test.1",
3
+ "version": "0.163.1-beta.1",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},
@@ -8,7 +8,7 @@
8
8
  "@momo-kits/foundation"
9
9
  ],
10
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",
11
+ "react-native-fast-image": "git+https://oauth2:k_r5y_gV6sX9-TqQxgcd@gitlab.mservice.com.vn/momo-platform/public/react-native-fast-image.git#v8.11.0",
12
12
  "@react-navigation/bottom-tabs": "7.4.2",
13
13
  "@react-navigation/core": "7.12.1",
14
14
  "@react-navigation/elements": "2.5.2",
@@ -16,7 +16,7 @@
16
16
  "@react-navigation/routers": "7.4.1",
17
17
  "@react-navigation/stack": "7.4.2",
18
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",
19
+ "react-native-linear-gradient": "git+https://oauth2:k_r5y_gV6sX9-TqQxgcd@gitlab.mservice.com.vn/momo-platform/public/react-native-linear-gradient#v3.0.0",
20
20
  "react-native-reanimated": "4.2.2",
21
21
  "react-native-safe-area-context": "5.5.2",
22
22
  "@shopify/flash-list": "2.1.0",