@momo-kits/foundation 0.161.2-test.1 → 0.162.2-test.5

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
@@ -12,6 +12,7 @@ import React, {
12
12
  useRef,
13
13
  } from 'react';
14
14
  import {
15
+ Animated,
15
16
  KeyboardAvoidingView,
16
17
  NativeScrollEvent,
17
18
  NativeSyntheticEvent,
@@ -24,13 +25,6 @@ import {
24
25
  View,
25
26
  ViewProps,
26
27
  } from 'react-native';
27
- import Animated, {
28
- runOnJS,
29
- useAnimatedScrollHandler,
30
- useSharedValue,
31
- withTiming,
32
- type SharedValue,
33
- } from 'react-native-reanimated';
34
28
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
35
29
  import { ApplicationContext, ScreenContext } from '../Context';
36
30
  import Navigation from '../Application/Navigation';
@@ -144,7 +138,7 @@ export interface ScreenProps extends ViewProps {
144
138
  /**
145
139
  * Optional. Animated value for header.
146
140
  */
147
- animatedValue?: SharedValue<number>;
141
+ animatedValue?: Animated.Value;
148
142
 
149
143
  /**
150
144
  * Optional. If `true`, use shadow header.
@@ -201,23 +195,18 @@ const Screen = forwardRef(
201
195
  const screen: any = useContext(ScreenContext);
202
196
  const insets = useSafeAreaInsets();
203
197
  const heightHeader = useHeaderHeight();
204
- const internalAnimatedValue = useSharedValue(0);
205
- const animatedValue = customAnimatedValue ?? internalAnimatedValue;
198
+ const animatedValue = useRef<Animated.Value>(
199
+ customAnimatedValue || new Animated.Value(0),
200
+ );
206
201
  const currentTint = useRef<string | undefined>(undefined);
207
202
  const isTab = navigation?.instance?.getState?.()?.type === 'tab';
208
203
 
209
- let handleScroll: any;
204
+ let handleScroll;
210
205
  let Component: any = View;
211
206
 
212
- // AI-GENERATED START
213
- // On iOS the home indicator is ~34pt but visual safe area needs only ~21pt cap.
214
- // On Android 15+ edge-to-edge the nav bar can be 48dp — no cap needed.
215
- const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
216
- // AI-GENERATED END
217
-
218
- let keyboardOffset = heightHeader - bottomInset;
207
+ let keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
219
208
  if (headerType === 'extended' || animatedHeader || inputSearchProps) {
220
- keyboardOffset = -bottomInset;
209
+ keyboardOffset = -Math.min(insets.bottom, 21);
221
210
  }
222
211
 
223
212
  /**
@@ -259,8 +248,9 @@ const Screen = forwardRef(
259
248
  interpolate={{
260
249
  inputRange: [0, 50],
261
250
  outputRange: [1, 0],
251
+ extrapolate: 'clamp',
262
252
  }}
263
- animatedValue={animatedValue}
253
+ animatedValue={animatedValue.current}
264
254
  />
265
255
  ),
266
256
  };
@@ -275,7 +265,7 @@ const Screen = forwardRef(
275
265
  headerBackground: (props: any) => (
276
266
  <HeaderBackground
277
267
  {...props}
278
- animatedValue={animatedValue}
268
+ animatedValue={animatedValue.current}
279
269
  useShadowHeader={useShadowHeader}
280
270
  headerBackground={headerBackground}
281
271
  gradientColor={gradientColor}
@@ -294,8 +284,9 @@ const Screen = forwardRef(
294
284
  interpolate={{
295
285
  inputRange: [0, 50],
296
286
  outputRange: [1, 0],
287
+ extrapolate: 'clamp',
297
288
  }}
298
- animatedValue={animatedValue}
289
+ animatedValue={animatedValue.current}
299
290
  />
300
291
  ),
301
292
  };
@@ -330,7 +321,7 @@ const Screen = forwardRef(
330
321
  headerBackground: (props: any) => (
331
322
  <HeaderBackground
332
323
  {...props}
333
- animatedValue={animatedValue}
324
+ animatedValue={animatedValue.current}
334
325
  useGradient={false}
335
326
  useShadowHeader={useShadowHeader}
336
327
  headerBackground={headerBackground}
@@ -368,8 +359,9 @@ const Screen = forwardRef(
368
359
  interpolate={{
369
360
  inputRange: [0, 50],
370
361
  outputRange: [1, 0],
362
+ extrapolate: 'clamp',
371
363
  }}
372
- animatedValue={animatedValue}
364
+ animatedValue={animatedValue.current}
373
365
  />
374
366
  ),
375
367
  };
@@ -398,7 +390,7 @@ const Screen = forwardRef(
398
390
  headerLeft: (props: any) =>
399
391
  params?.hiddenBack ? null : <HeaderLeft {...props} />,
400
392
  headerTitle: () => (
401
- <SearchHeader {...params} animatedValue={animatedValue} />
393
+ <SearchHeader {...params} animatedValue={animatedValue.current} />
402
394
  ),
403
395
  };
404
396
 
@@ -449,51 +441,45 @@ const Screen = forwardRef(
449
441
  });
450
442
  });
451
443
 
452
- const onTintColorChange = useCallback(
453
- (offsetY: number) => {
454
- if (!animatedHeader) return;
455
- let color = animatedHeader?.headerTintColor ?? Colors.black_17;
456
- if (offsetY > 50) {
457
- color = Colors.black_17;
458
- }
459
- if (color !== currentTint.current) {
460
- currentTint.current = color;
461
- navigation?.setOptions({
462
- headerTintColor: color,
463
- });
464
- let barStyle: StatusBarStyle = 'dark-content';
465
- if (currentTint.current === Colors.black_01) {
466
- barStyle = 'light-content';
467
- }
468
- StatusBar.setBarStyle(barStyle, true);
469
- }
470
- },
471
- [animatedHeader, navigation],
472
- );
473
-
474
- const emitOnScroll = useCallback(
475
- (offsetY: number) => {
476
- scrollViewProps?.onScroll?.({
477
- nativeEvent: { contentOffset: { x: 0, y: offsetY } },
478
- } as NativeSyntheticEvent<NativeScrollEvent>);
479
- },
480
- [scrollViewProps],
481
- );
482
-
483
- const scrollHandler = useAnimatedScrollHandler({
484
- onScroll: (event) => {
485
- animatedValue.value = event.contentOffset.y;
486
- runOnJS(emitOnScroll)(event.contentOffset.y);
487
- runOnJS(onTintColorChange)(event.contentOffset.y);
488
- },
489
- });
490
-
491
444
  /**
492
445
  * animated when use scroll && animated value
493
446
  */
494
447
  if (scrollable) {
495
448
  Component = Animated.ScrollView;
496
- handleScroll = scrollHandler;
449
+ handleScroll = Animated.event(
450
+ [
451
+ {
452
+ nativeEvent: {
453
+ contentOffset: { y: animatedValue.current as Animated.Value },
454
+ },
455
+ },
456
+ ],
457
+ {
458
+ useNativeDriver: true,
459
+ listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
460
+ scrollViewProps?.onScroll?.(e);
461
+ if (animatedHeader) {
462
+ const offsetY = e.nativeEvent.contentOffset.y;
463
+ let color = animatedHeader?.headerTintColor ?? Colors.black_17;
464
+ if (offsetY > 50) {
465
+ color = Colors.black_17;
466
+ }
467
+ if (color !== currentTint.current) {
468
+ currentTint.current = color;
469
+ navigation?.setOptions({
470
+ headerTintColor: color,
471
+ });
472
+
473
+ let barStyle: StatusBarStyle = 'dark-content';
474
+ if (currentTint.current === Colors.black_01) {
475
+ barStyle = 'light-content';
476
+ }
477
+ StatusBar.setBarStyle(barStyle, true);
478
+ }
479
+ }
480
+ },
481
+ },
482
+ );
497
483
  }
498
484
 
499
485
  /**
@@ -503,7 +489,11 @@ const Screen = forwardRef(
503
489
  const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
504
490
  const offsetY = e.nativeEvent.contentOffset.y;
505
491
  if (inputSearchProps && offsetY < 100 && offsetY > 0) {
506
- animatedValue.value = withTiming(0, { duration: 300 });
492
+ Animated.timing(animatedValue.current, {
493
+ toValue: 0,
494
+ useNativeDriver: true,
495
+ duration: 300,
496
+ }).start();
507
497
  ref?.scrollTo?.({ y: 0, animated: true });
508
498
  }
509
499
  scrollViewProps?.onScrollEndDrag?.(e);
@@ -519,7 +509,7 @@ const Screen = forwardRef(
519
509
  style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
520
510
  >
521
511
  {animatedHeader?.component({
522
- animatedValue: animatedValue,
512
+ animatedValue: animatedValue.current,
523
513
  })}
524
514
  </View>
525
515
  );
@@ -586,7 +576,7 @@ const Screen = forwardRef(
586
576
  headerType={headerType}
587
577
  heightHeader={heightHeader}
588
578
  headerRightWidth={headerRightWidth}
589
- animatedValue={animatedValue}
579
+ animatedValue={animatedValue.current}
590
580
  inputSearchProps={inputSearchProps}
591
581
  navigation={navigation}
592
582
  inputSearchRef={inputSearchRef}
@@ -624,9 +614,9 @@ const Screen = forwardRef(
624
614
  <View>
625
615
  <FloatingButton
626
616
  {...floatingButtonProps}
627
- animatedValue={animatedValue}
617
+ animatedValue={animatedValue.current}
628
618
  bottom={
629
- Footer || isTab ? 12 : bottomInset + Spacing.S
619
+ Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
630
620
  }
631
621
  />
632
622
  </View>
@@ -637,7 +627,7 @@ const Screen = forwardRef(
637
627
  style={[
638
628
  styles.shadow,
639
629
  {
640
- paddingBottom: bottomInset + Spacing.S,
630
+ paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
641
631
  backgroundColor: theme.colors.background.surface,
642
632
  },
643
633
  ]}
@@ -1,10 +1,5 @@
1
- import React, { FC, useContext, useEffect } from 'react';
2
- import { View } from 'react-native';
3
- import Animated, {
4
- useAnimatedStyle,
5
- useSharedValue,
6
- withTiming,
7
- } from 'react-native-reanimated';
1
+ import React, { FC, useContext, useEffect, useRef } from 'react';
2
+ import { Animated, View } from 'react-native';
8
3
  import styles from './styles';
9
4
  import { ProgressBarProps } from './types';
10
5
  import { ApplicationContext } from '../Context';
@@ -12,15 +7,20 @@ import { Radius } from '../Consts';
12
7
 
13
8
  const ProgressBar: FC<ProgressBarProps> = ({ percent = 0, style }) => {
14
9
  const { theme } = useContext(ApplicationContext);
15
- const animation = useSharedValue(0);
10
+ const animation = useRef(new Animated.Value(0)).current;
16
11
 
17
12
  useEffect(() => {
18
- animation.value = withTiming(percent, { duration: 200 });
13
+ Animated.timing(animation, {
14
+ toValue: percent,
15
+ duration: 200,
16
+ useNativeDriver: false,
17
+ }).start();
19
18
  }, [percent, animation]);
20
19
 
21
- const animatedStyle = useAnimatedStyle(() => ({
22
- width: `${Math.min(Math.max(animation.value, 0), 100)}%`,
23
- }));
20
+ const width = animation.interpolate({
21
+ inputRange: [0, 100],
22
+ outputRange: ['0%', '100%'],
23
+ });
24
24
 
25
25
  return (
26
26
  <View
@@ -31,14 +31,12 @@ const ProgressBar: FC<ProgressBarProps> = ({ percent = 0, style }) => {
31
31
  ]}
32
32
  >
33
33
  <Animated.View
34
- style={[
35
- {
36
- height: 4,
37
- borderRadius: Radius.XXS,
38
- backgroundColor: theme.colors.primary,
39
- },
40
- animatedStyle,
41
- ]}
34
+ style={{
35
+ height: 4,
36
+ borderRadius: Radius.XXS,
37
+ width,
38
+ backgroundColor: theme.colors.primary,
39
+ }}
42
40
  />
43
41
  </View>
44
42
  );
@@ -1,5 +1,5 @@
1
1
  import React, { FC, useContext } from 'react';
2
- import { View } from 'react-native';
2
+ import { Animated } from 'react-native';
3
3
  import styles from './styles';
4
4
  import { DotProps } from './types';
5
5
  import { ApplicationContext } from '../Context';
@@ -13,7 +13,7 @@ const Dot: FC<DotProps> = ({ active, style }) => {
13
13
  { backgroundColor: theme.colors.background.pressed },
14
14
  ];
15
15
 
16
- return <View style={[style, dotStyle]} />;
16
+ return <Animated.View style={[style, dotStyle]} />;
17
17
  };
18
18
 
19
19
  export default Dot;
@@ -1,12 +1,5 @@
1
- import React, { FC, useContext, useState } from 'react';
2
- import { View } from 'react-native';
3
- import Animated, {
4
- Extrapolation,
5
- interpolate,
6
- useAnimatedScrollHandler,
7
- useAnimatedStyle,
8
- useSharedValue,
9
- } from 'react-native-reanimated';
1
+ import React, { FC, useContext, useRef, useState } from 'react';
2
+ import { Animated, View } from 'react-native';
10
3
  import { ScrollIndicatorProps } from './types';
11
4
  import styles from './styles';
12
5
  import { ApplicationContext, MiniAppContext } from '../Context';
@@ -20,37 +13,40 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
20
13
  }) => {
21
14
  const { theme } = useContext(ApplicationContext);
22
15
  const context = useContext<any>(MiniAppContext);
23
- const left = useSharedValue(0);
16
+ const left = useRef(new Animated.Value(0)).current;
24
17
  const [scrollViewWidth, setScrollViewWidth] = useState(0);
25
18
  const [scrollContentWidth, setScrollContentWidth] = useState(0);
26
19
 
27
20
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
28
21
 
29
- const onScroll = useAnimatedScrollHandler({
30
- onScroll: (event) => {
31
- left.value = event.contentOffset.x;
32
- },
33
- });
34
-
35
- const indicatorStyle = useAnimatedStyle(() => {
36
- if (!scrollViewWidth || !scrollContentWidth) {
37
- return {};
22
+ const translateX = () => {
23
+ if (scrollViewWidth && scrollContentWidth) {
24
+ const value = left.interpolate({
25
+ inputRange: [0, scrollContentWidth - scrollViewWidth],
26
+ outputRange: [0, INDICATOR_CONTAINER_WIDTH - INDICATOR_WIDTH],
27
+ extrapolate: 'clamp',
28
+ });
29
+ return { transform: [{ translateX: value }] };
38
30
  }
39
- const value = interpolate(
40
- left.value,
41
- [0, scrollContentWidth - scrollViewWidth],
42
- [0, INDICATOR_CONTAINER_WIDTH - INDICATOR_WIDTH],
43
- Extrapolation.CLAMP,
44
- );
45
- return { transform: [{ translateX: value }] };
46
- });
31
+ return {};
32
+ };
47
33
 
48
34
  const renderScrollView = () => {
49
35
  return (
50
36
  <Animated.ScrollView
51
- ref={scrollViewRef as any}
52
- onScroll={onScroll}
53
- scrollEventThrottle={16}
37
+ ref={scrollViewRef}
38
+ onScroll={Animated.event(
39
+ [
40
+ {
41
+ nativeEvent: {
42
+ contentOffset: {
43
+ x: left,
44
+ },
45
+ },
46
+ },
47
+ ],
48
+ { useNativeDriver: true },
49
+ )}
54
50
  alwaysBounceHorizontal={false}
55
51
  showsHorizontalScrollIndicator={false}
56
52
  horizontal
@@ -80,7 +76,7 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
80
76
  {
81
77
  backgroundColor: theme.colors.primary,
82
78
  },
83
- indicatorStyle,
79
+ translateX(),
84
80
  ]}
85
81
  />
86
82
  </View>
@@ -1,20 +1,13 @@
1
- import React, { useContext, useEffect, useState } from 'react';
1
+ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
+ Animated,
4
+ Easing,
3
5
  LayoutAnimation,
4
6
  Platform,
5
7
  StyleSheet,
6
8
  UIManager,
7
9
  View,
8
10
  } from 'react-native';
9
- import Animated, {
10
- cancelAnimation,
11
- Easing,
12
- interpolate,
13
- useAnimatedStyle,
14
- useSharedValue,
15
- withRepeat,
16
- withTiming,
17
- } from 'react-native-reanimated';
18
11
  import LinearGradient from 'react-native-linear-gradient';
19
12
  import { SkeletonTypes } from './types';
20
13
  import { Colors, Styles } from '../Consts';
@@ -27,31 +20,32 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
27
20
  const PRIMARY_COLOR = Colors.black_05;
28
21
  const HIGHLIGHT_COLOR1 = Colors.black_05;
29
22
  const HIGHLIGHT_COLOR2 = Colors.black_03;
30
- const progress = useSharedValue(0);
23
+ const beginShimmerPosition = useRef(new Animated.Value(0)).current;
31
24
 
32
25
  const shimmerColors = [HIGHLIGHT_COLOR1, HIGHLIGHT_COLOR2, HIGHLIGHT_COLOR1];
33
-
34
- useEffect(() => {
35
- progress.value = 0;
36
- progress.value = withRepeat(
37
- withTiming(1, {
26
+ const linearTranslate = beginShimmerPosition.interpolate({
27
+ inputRange: [0, 1],
28
+ outputRange: [-width, width],
29
+ });
30
+ const animatedValue = useMemo(() => {
31
+ return Animated.loop(
32
+ Animated.timing(beginShimmerPosition, {
33
+ toValue: 1,
38
34
  duration: 1000,
39
35
  easing: Easing.linear,
36
+ useNativeDriver: Platform.OS !== 'web',
40
37
  }),
41
- -1,
42
- false,
43
38
  );
39
+ }, [beginShimmerPosition]);
40
+
41
+ useEffect(() => {
42
+ animatedValue.start();
44
43
  screen?.onLoading?.(true);
45
44
  return () => {
46
- cancelAnimation(progress);
45
+ animatedValue.stop();
47
46
  screen?.onLoading?.(false);
48
47
  };
49
- }, [progress, screen]);
50
-
51
- const shimmerStyle = useAnimatedStyle(() => {
52
- const translateX = interpolate(progress.value, [0, 1], [-width, width]);
53
- return { transform: [{ translateX }] };
54
- });
48
+ }, [animatedValue, screen]);
55
49
 
56
50
  const onLayout = (newWidth: number) => {
57
51
  if (newWidth !== width) {
@@ -66,13 +60,11 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
66
60
  style={[Styles.flex, { backgroundColor: PRIMARY_COLOR }]}
67
61
  >
68
62
  <Animated.View
69
- style={[
70
- {
71
- width: '60%',
72
- height: '100%',
73
- },
74
- shimmerStyle,
75
- ]}
63
+ style={{
64
+ transform: [{ translateX: linearTranslate }],
65
+ width: '60%',
66
+ height: '100%',
67
+ }}
76
68
  >
77
69
  <LinearGradient
78
70
  style={[StyleSheet.absoluteFill]}
package/Text/utils.ts CHANGED
@@ -7,13 +7,18 @@ 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, scaleRate?: number) => {
11
- const { scaleSizeMaxRate = MAX_FONT_SCALE } = useContext(ScaleSizeContext);
10
+ const useScaleSize = (size: number, userScaleRate?: number) => {
11
+ const { useOSFontScale = false, userScaleRate: ctxRate } =
12
+ useContext(ScaleSizeContext);
12
13
  const fontScale = PixelRatio.getFontScale();
13
14
  const { width } = useWindowDimensions();
14
15
  const deviceScale = width / DEFAULT_SCREEN_SIZE;
15
16
 
16
- const maxScaleRate = scaleRate ?? scaleSizeMaxRate;
17
+ // rate user tự chỉnh: param override > config > 1 (không scale)
18
+ const customRate = userScaleRate ?? ctxRate ?? 1;
19
+ // useOSFontScale bật -> lấy scale OS đè lên, ignore custom rate;
20
+ // tắt -> dùng custom rate. Cap cứng MAX_FONT_SCALE (150%) cả hai.
21
+ const appliedRate = useOSFontScale ? fontScale : customRate;
17
22
 
18
23
  let fontSizeDeviceScale = size;
19
24
  let fontSizeOSScale = size;
@@ -25,10 +30,10 @@ const useScaleSize = (size: number, scaleRate?: number) => {
25
30
  );
26
31
  }
27
32
 
28
- if (fontScale > 1) {
33
+ if (appliedRate > 1) {
29
34
  fontSizeOSScale = Math.min(
30
- fontSizeOSScale * fontScale,
31
- fontSizeOSScale * maxScaleRate,
35
+ appliedRate * fontSizeOSScale,
36
+ fontSizeOSScale * MAX_FONT_SCALE,
32
37
  );
33
38
  }
34
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.161.2-test.1",
3
+ "version": "0.162.2-test.5",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},