@momo-kits/foundation 0.162.3-test.2 → 0.163.1-beta.10

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,29 +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
- // AI-GENERATED START: parse the font_scale_config first-frame seed read straight from async storage (string | {response} | object)
35
- const parseSeedFontScaleConfig = (raw: any): FontScaleConfig | undefined => {
36
- let data = raw;
37
- if (typeof data === 'string') {
38
- try {
39
- data = JSON.parse(data);
40
- } catch {
41
- return undefined;
42
- }
43
- }
44
- data = data?.response ?? data;
45
- if (!data || (data.useOSFontScale === undefined && data.userScaleRate === undefined)) {
46
- return undefined;
47
- }
48
- return {
49
- useOSFontScale: data.useOSFontScale,
50
- userScaleRate: data.userScaleRate,
51
- };
52
- };
53
- // AI-GENERATED END: parse the font_scale_config first-frame seed read straight from async storage (string | {response} | object)
54
-
55
31
  const NavigationContainer: React.FC<NavigationContainerProps> = ({
56
32
  screen,
57
33
  theme = defaultTheme,
@@ -68,11 +44,13 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
68
44
  }) => {
69
45
  const context = useContext<any>(MiniAppContext);
70
46
  const [currentContext, setCurrentContext] = useState<any>({});
71
- // AI-GENERATED START: self-seed first-frame font scale by reading font_scale_config straight from async storage
72
- const [observerFontScaleConfig, setObserverFontScaleConfig] = useState<
73
- FontScaleConfig | undefined
74
- >(() => parseSeedFontScaleConfig(maxApi?.getItem?.(FONT_SCALE_OBSERVER_KEY)) ?? context?.fontScaleConfig);
75
- // AI-GENERATED END: self-seed first-frame font scale by reading font_scale_config straight from async storage
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]);
76
54
 
77
55
  const mergedContext = {
78
56
  ...context,
@@ -83,34 +61,18 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
83
61
  },
84
62
  };
85
63
 
86
- useEffect(() => {
87
- const sub = maxApi?.observer?.(
88
- FONT_SCALE_OBSERVER_KEY,
89
- (data: FontScaleConfig) => {
90
- setObserverFontScaleConfig(data ?? undefined);
91
- },
92
- );
93
- return () => {
94
- sub?.remove?.();
95
- };
96
- }, [maxApi]);
97
-
98
64
  return (
99
65
  <SafeAreaProvider>
100
66
  <MiniAppContext.Provider value={mergedContext}>
101
- <ScaleSizeProvider
102
- fontScaleConfig={observerFontScaleConfig}
103
- >
104
- <Navigation
105
- screen={screen}
106
- theme={theme}
107
- options={options}
108
- maxApi={maxApi}
109
- setCurrentContext={setCurrentContext}
110
- initialParams={initialParams}
111
- localize={localize}
112
- />
113
- </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
+ />
114
76
  </MiniAppContext.Provider>
115
77
  </SafeAreaProvider>
116
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
  );
@@ -315,6 +315,7 @@ const StackScreen: React.FC<any> = props => {
315
315
  <ScreenContext.Provider
316
316
  value={{
317
317
  screenName,
318
+ bottomTab: !!bottomTab,
318
319
  onElementLoad: (item: any) => {
319
320
  /**
320
321
  * widget handle
@@ -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
@@ -77,7 +77,8 @@ const Input = forwardRef(
77
77
  const scaledFontSize = useScaleSize(14);
78
78
  const scaleHeight = useScaleSize(size === 'small' ? 48 : 56);
79
79
  const [focused, setFocused] = useState(false);
80
- const haveValue = !!value || !!defaultValue;
80
+ const [internalText, setInternalText] = useState(defaultValue || '');
81
+ const haveValue = value !== undefined ? !!value : !!internalText;
81
82
  const inputRef = useRef<TextInput | null>(null);
82
83
  const componentName = 'Input';
83
84
 
@@ -95,6 +96,9 @@ const Input = forwardRef(
95
96
  };
96
97
 
97
98
  const _onChangeText = (text: string) => {
99
+ if (value === undefined) {
100
+ setInternalText(text);
101
+ }
98
102
  onChangeText?.(text);
99
103
  };
100
104
 
@@ -109,6 +113,13 @@ const Input = forwardRef(
109
113
  };
110
114
 
111
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
+ }
112
123
  inputRef?.current?.setNativeProps({ text });
113
124
  _onChangeText(text);
114
125
  };
@@ -69,7 +69,8 @@ const InputPhoneNumber = forwardRef(
69
69
  const context = useContext<any>(MiniAppContext);
70
70
  const scaleHeight = useScaleSize(size === 'small' ? 48 : 56);
71
71
  const [focused, setFocused] = useState(false);
72
- const haveValue = !!value || !!defaultValue;
72
+ const [internalText, setInternalText] = useState(defaultValue || '');
73
+ const haveValue = value !== undefined ? !!value : !!internalText;
73
74
  const inputRef = useRef<TextInput | null>(null);
74
75
  const componentName = 'InputPhoneNumber';
75
76
 
@@ -86,6 +87,9 @@ const InputPhoneNumber = forwardRef(
86
87
  };
87
88
 
88
89
  const _onChangeText = (text: string) => {
90
+ if (value === undefined) {
91
+ setInternalText(text);
92
+ }
89
93
  onChangeText?.(text);
90
94
  };
91
95
 
@@ -100,6 +104,13 @@ const InputPhoneNumber = forwardRef(
100
104
  };
101
105
 
102
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
+ }
103
114
  inputRef?.current?.setNativeProps({ text });
104
115
  _onChangeText(text);
105
116
  };
package/Layout/Screen.tsx CHANGED
@@ -25,10 +25,7 @@ import {
25
25
  View,
26
26
  ViewProps,
27
27
  } from 'react-native';
28
- import {
29
- useSharedValue,
30
- withTiming,
31
- } from 'react-native-reanimated';
28
+ import { useSharedValue, withTiming } from 'react-native-reanimated';
32
29
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
33
30
  import { ApplicationContext, ScreenContext } from '../Context';
34
31
  import Navigation from '../Application/Navigation';
@@ -196,7 +193,7 @@ const Screen = forwardRef(
196
193
  ) => {
197
194
  const screenRef = useRef<View | ScrollView>(null);
198
195
  const { width: widthDevice } = useWindowDimensions();
199
- const { theme } = useContext(ApplicationContext);
196
+ const { theme, navigator } = useContext(ApplicationContext);
200
197
  const screen: any = useContext(ScreenContext);
201
198
  const insets = useSafeAreaInsets();
202
199
  const heightHeader = useHeaderHeight();
@@ -235,7 +232,8 @@ const Screen = forwardRef(
235
232
  }, [customAnimatedValue, internalShared]);
236
233
 
237
234
  const currentTint = useRef<string | undefined>(undefined);
238
- const isTab = navigation?.instance?.getState?.()?.type === 'tab';
235
+
236
+ const isTab = !!screen?.bottomTab;
239
237
 
240
238
  let handleScroll;
241
239
  let Component: any = View;
@@ -650,7 +648,11 @@ const Screen = forwardRef(
650
648
  onScroll={handleScroll}
651
649
  onScrollEndDrag={handleScrollEnd}
652
650
  scrollEventThrottle={16}
653
- style={Styles.flex}
651
+ style={[Styles.flex]}
652
+ contentContainerStyle={[
653
+ scrollable && !Footer && !isTab && { paddingBottom: bottomInset },
654
+ scrollViewProps?.contentContainerStyle,
655
+ ]}
654
656
  >
655
657
  {renderAnimatedHeader()}
656
658
 
@@ -662,9 +664,7 @@ const Screen = forwardRef(
662
664
  <FloatingButton
663
665
  {...floatingButtonProps}
664
666
  animatedValue={sharedValue}
665
- bottom={
666
- Footer || isTab ? 12 : bottomInset + Spacing.S
667
- }
667
+ bottom={Footer || isTab ? 12 : bottomInset + Spacing.S}
668
668
  />
669
669
  </View>
670
670
  )}
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.2",
3
+ "version": "0.163.1-beta.10",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},