@momo-kits/foundation 0.162.2-test.1 → 0.162.2-test.20

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
@@ -25,6 +25,10 @@ import {
25
25
  View,
26
26
  ViewProps,
27
27
  } from 'react-native';
28
+ import {
29
+ useSharedValue,
30
+ withTiming,
31
+ } from 'react-native-reanimated';
28
32
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
29
33
  import { ApplicationContext, ScreenContext } from '../Context';
30
34
  import Navigation from '../Application/Navigation';
@@ -136,7 +140,8 @@ export interface ScreenProps extends ViewProps {
136
140
  inputSearchRef?: Ref<InputRef>;
137
141
 
138
142
  /**
139
- * Optional. Animated value for header.
143
+ * Optional. React Native animated value for header.
144
+ * Internally, Screen mirrors it into a reanimated shared value.
140
145
  */
141
146
  animatedValue?: Animated.Value;
142
147
 
@@ -195,9 +200,40 @@ const Screen = forwardRef(
195
200
  const screen: any = useContext(ScreenContext);
196
201
  const insets = useSafeAreaInsets();
197
202
  const heightHeader = useHeaderHeight();
203
+
204
+ /**
205
+ * Dual animation source:
206
+ * - `animatedValue` is a react-native `Animated.Value` driven natively by the
207
+ * scroll event. It is what legacy consumers receive (`animatedValue`).
208
+ * - `sharedValue` is a reanimated `SharedValue` consumed by every internal
209
+ * foundation component (and offered to modern consumers). It is kept in
210
+ * sync from the scroll `listener` (see below), which fires on the JS
211
+ * thread even when the scroll uses the native driver.
212
+ * If a legacy value is driven outside of Screen, its listener also
213
+ * mirrors JS-driven updates into the shared value.
214
+ * Consumers pass only `Animated.Value`; the reanimated shared value stays
215
+ * an internal implementation detail.
216
+ */
198
217
  const animatedValue = useRef<Animated.Value>(
199
- customAnimatedValue || new Animated.Value(0),
218
+ customAnimatedValue ?? new Animated.Value(0),
200
219
  );
220
+ const internalShared = useSharedValue(0);
221
+ const sharedValue = internalShared;
222
+
223
+ useEffect(() => {
224
+ if (!customAnimatedValue) {
225
+ return;
226
+ }
227
+
228
+ const listenerId = customAnimatedValue.addListener(({ value }) => {
229
+ internalShared.value = value;
230
+ });
231
+
232
+ return () => {
233
+ customAnimatedValue.removeListener(listenerId);
234
+ };
235
+ }, [customAnimatedValue, internalShared]);
236
+
201
237
  const currentTint = useRef<string | undefined>(undefined);
202
238
  const isTab = navigation?.instance?.getState?.()?.type === 'tab';
203
239
 
@@ -249,9 +285,8 @@ const Screen = forwardRef(
249
285
  interpolate={{
250
286
  inputRange: [0, 50],
251
287
  outputRange: [1, 0],
252
- extrapolate: 'clamp',
253
288
  }}
254
- animatedValue={animatedValue.current}
289
+ animatedValue={sharedValue}
255
290
  />
256
291
  ),
257
292
  };
@@ -266,7 +301,7 @@ const Screen = forwardRef(
266
301
  headerBackground: (props: any) => (
267
302
  <HeaderBackground
268
303
  {...props}
269
- animatedValue={animatedValue.current}
304
+ animatedValue={sharedValue}
270
305
  useShadowHeader={useShadowHeader}
271
306
  headerBackground={headerBackground}
272
307
  gradientColor={gradientColor}
@@ -285,9 +320,8 @@ const Screen = forwardRef(
285
320
  interpolate={{
286
321
  inputRange: [0, 50],
287
322
  outputRange: [1, 0],
288
- extrapolate: 'clamp',
289
323
  }}
290
- animatedValue={animatedValue.current}
324
+ animatedValue={sharedValue}
291
325
  />
292
326
  ),
293
327
  };
@@ -301,6 +335,7 @@ const Screen = forwardRef(
301
335
  headerBackground,
302
336
  inputSearchProps,
303
337
  navigation?.instance,
338
+ sharedValue,
304
339
  useShadowHeader,
305
340
  ],
306
341
  );
@@ -322,7 +357,7 @@ const Screen = forwardRef(
322
357
  headerBackground: (props: any) => (
323
358
  <HeaderBackground
324
359
  {...props}
325
- animatedValue={animatedValue.current}
360
+ animatedValue={sharedValue}
326
361
  useGradient={false}
327
362
  useShadowHeader={useShadowHeader}
328
363
  headerBackground={headerBackground}
@@ -343,7 +378,13 @@ const Screen = forwardRef(
343
378
 
344
379
  navigation?.instance?.setOptions(options);
345
380
  },
346
- [gradientColor, headerBackground, navigation?.instance, useShadowHeader],
381
+ [
382
+ gradientColor,
383
+ headerBackground,
384
+ navigation?.instance,
385
+ sharedValue,
386
+ useShadowHeader,
387
+ ],
347
388
  );
348
389
 
349
390
  /**
@@ -360,15 +401,14 @@ const Screen = forwardRef(
360
401
  interpolate={{
361
402
  inputRange: [0, 50],
362
403
  outputRange: [1, 0],
363
- extrapolate: 'clamp',
364
404
  }}
365
- animatedValue={animatedValue.current}
405
+ animatedValue={sharedValue}
366
406
  />
367
407
  ),
368
408
  };
369
409
 
370
410
  navigation?.instance?.setOptions(options);
371
- }, [navigation?.instance]);
411
+ }, [navigation?.instance, sharedValue]);
372
412
 
373
413
  /**
374
414
  * export search header
@@ -391,7 +431,7 @@ const Screen = forwardRef(
391
431
  headerLeft: (props: any) =>
392
432
  params?.hiddenBack ? null : <HeaderLeft {...props} />,
393
433
  headerTitle: () => (
394
- <SearchHeader {...params} animatedValue={animatedValue.current} />
434
+ <SearchHeader {...params} animatedValue={sharedValue} />
395
435
  ),
396
436
  };
397
437
 
@@ -458,9 +498,11 @@ const Screen = forwardRef(
458
498
  {
459
499
  useNativeDriver: true,
460
500
  listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
501
+ const offsetY = e.nativeEvent.contentOffset.y;
502
+ // keep the reanimated source in sync for the internal components.
503
+ sharedValue.value = offsetY;
461
504
  scrollViewProps?.onScroll?.(e);
462
505
  if (animatedHeader) {
463
- const offsetY = e.nativeEvent.contentOffset.y;
464
506
  let color = animatedHeader?.headerTintColor ?? Colors.black_17;
465
507
  if (offsetY > 50) {
466
508
  color = Colors.black_17;
@@ -495,6 +537,7 @@ const Screen = forwardRef(
495
537
  useNativeDriver: true,
496
538
  duration: 300,
497
539
  }).start();
540
+ sharedValue.value = withTiming(0, { duration: 300 });
498
541
  ref?.scrollTo?.({ y: 0, animated: true });
499
542
  }
500
543
  scrollViewProps?.onScrollEndDrag?.(e);
@@ -510,7 +553,10 @@ const Screen = forwardRef(
510
553
  style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
511
554
  >
512
555
  {animatedHeader?.component({
556
+ // legacy consumers read `animatedValue` (Animated.Value),
557
+ // modern consumers read `sharedValue` (SharedValue).
513
558
  animatedValue: animatedValue.current,
559
+ sharedValue: sharedValue,
514
560
  })}
515
561
  </View>
516
562
  );
@@ -577,7 +623,7 @@ const Screen = forwardRef(
577
623
  headerType={headerType}
578
624
  heightHeader={heightHeader}
579
625
  headerRightWidth={headerRightWidth}
580
- animatedValue={animatedValue.current}
626
+ animatedValue={sharedValue}
581
627
  inputSearchProps={inputSearchProps}
582
628
  navigation={navigation}
583
629
  inputSearchRef={inputSearchRef}
@@ -615,7 +661,7 @@ const Screen = forwardRef(
615
661
  <View>
616
662
  <FloatingButton
617
663
  {...floatingButtonProps}
618
- animatedValue={animatedValue.current}
664
+ animatedValue={sharedValue}
619
665
  bottom={
620
666
  Footer || isTab ? 12 : bottomInset + Spacing.S
621
667
  }
@@ -1,5 +1,10 @@
1
- import React, { FC, useContext, useEffect, useRef } from 'react';
2
- import { Animated, View } from 'react-native';
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';
3
8
  import styles from './styles';
4
9
  import { ProgressBarProps } from './types';
5
10
  import { ApplicationContext } from '../Context';
@@ -7,20 +12,15 @@ import { Radius } from '../Consts';
7
12
 
8
13
  const ProgressBar: FC<ProgressBarProps> = ({ percent = 0, style }) => {
9
14
  const { theme } = useContext(ApplicationContext);
10
- const animation = useRef(new Animated.Value(0)).current;
15
+ const animation = useSharedValue(0);
11
16
 
12
17
  useEffect(() => {
13
- Animated.timing(animation, {
14
- toValue: percent,
15
- duration: 200,
16
- useNativeDriver: false,
17
- }).start();
18
+ animation.value = withTiming(percent, { duration: 200 });
18
19
  }, [percent, animation]);
19
20
 
20
- const width = animation.interpolate({
21
- inputRange: [0, 100],
22
- outputRange: ['0%', '100%'],
23
- });
21
+ const animatedStyle = useAnimatedStyle(() => ({
22
+ width: `${Math.min(Math.max(animation.value, 0), 100)}%`,
23
+ }));
24
24
 
25
25
  return (
26
26
  <View
@@ -31,12 +31,14 @@ const ProgressBar: FC<ProgressBarProps> = ({ percent = 0, style }) => {
31
31
  ]}
32
32
  >
33
33
  <Animated.View
34
- style={{
35
- height: 4,
36
- borderRadius: Radius.XXS,
37
- width,
38
- backgroundColor: theme.colors.primary,
39
- }}
34
+ style={[
35
+ {
36
+ height: 4,
37
+ borderRadius: Radius.XXS,
38
+ backgroundColor: theme.colors.primary,
39
+ },
40
+ animatedStyle,
41
+ ]}
40
42
  />
41
43
  </View>
42
44
  );
@@ -1,5 +1,5 @@
1
1
  import React, { FC, useContext } from 'react';
2
- import { Animated } from 'react-native';
2
+ import { View } 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 <Animated.View style={[style, dotStyle]} />;
16
+ return <View style={[style, dotStyle]} />;
17
17
  };
18
18
 
19
19
  export default Dot;
@@ -1,5 +1,12 @@
1
- import React, { FC, useContext, useRef, useState } from 'react';
2
- import { Animated, View } from 'react-native';
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';
3
10
  import { ScrollIndicatorProps } from './types';
4
11
  import styles from './styles';
5
12
  import { ApplicationContext, MiniAppContext } from '../Context';
@@ -13,40 +20,37 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
13
20
  }) => {
14
21
  const { theme } = useContext(ApplicationContext);
15
22
  const context = useContext<any>(MiniAppContext);
16
- const left = useRef(new Animated.Value(0)).current;
23
+ const left = useSharedValue(0);
17
24
  const [scrollViewWidth, setScrollViewWidth] = useState(0);
18
25
  const [scrollContentWidth, setScrollContentWidth] = useState(0);
19
26
 
20
27
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
21
28
 
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 }] };
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 {};
30
38
  }
31
- return {};
32
- };
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
+ });
33
47
 
34
48
  const renderScrollView = () => {
35
49
  return (
36
50
  <Animated.ScrollView
37
- ref={scrollViewRef}
38
- onScroll={Animated.event(
39
- [
40
- {
41
- nativeEvent: {
42
- contentOffset: {
43
- x: left,
44
- },
45
- },
46
- },
47
- ],
48
- { useNativeDriver: true },
49
- )}
51
+ ref={scrollViewRef as any}
52
+ onScroll={onScroll}
53
+ scrollEventThrottle={16}
50
54
  alwaysBounceHorizontal={false}
51
55
  showsHorizontalScrollIndicator={false}
52
56
  horizontal
@@ -76,7 +80,7 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
76
80
  {
77
81
  backgroundColor: theme.colors.primary,
78
82
  },
79
- translateX(),
83
+ indicatorStyle,
80
84
  ]}
81
85
  />
82
86
  </View>
@@ -1,13 +1,20 @@
1
- import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useContext, useEffect, useState } from 'react';
2
2
  import {
3
- Animated,
4
- Easing,
5
3
  LayoutAnimation,
6
4
  Platform,
7
5
  StyleSheet,
8
6
  UIManager,
9
7
  View,
10
8
  } 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';
11
18
  import LinearGradient from 'react-native-linear-gradient';
12
19
  import { SkeletonTypes } from './types';
13
20
  import { Colors, Styles } from '../Consts';
@@ -20,32 +27,31 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
20
27
  const PRIMARY_COLOR = Colors.black_05;
21
28
  const HIGHLIGHT_COLOR1 = Colors.black_05;
22
29
  const HIGHLIGHT_COLOR2 = Colors.black_03;
23
- const beginShimmerPosition = useRef(new Animated.Value(0)).current;
30
+ const progress = useSharedValue(0);
24
31
 
25
32
  const shimmerColors = [HIGHLIGHT_COLOR1, HIGHLIGHT_COLOR2, HIGHLIGHT_COLOR1];
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,
33
+
34
+ useEffect(() => {
35
+ progress.value = 0;
36
+ progress.value = withRepeat(
37
+ withTiming(1, {
34
38
  duration: 1000,
35
39
  easing: Easing.linear,
36
- useNativeDriver: Platform.OS !== 'web',
37
40
  }),
41
+ -1,
42
+ false,
38
43
  );
39
- }, [beginShimmerPosition]);
40
-
41
- useEffect(() => {
42
- animatedValue.start();
43
44
  screen?.onLoading?.(true);
44
45
  return () => {
45
- animatedValue.stop();
46
+ cancelAnimation(progress);
46
47
  screen?.onLoading?.(false);
47
48
  };
48
- }, [animatedValue, screen]);
49
+ }, [progress, screen]);
50
+
51
+ const shimmerStyle = useAnimatedStyle(() => {
52
+ const translateX = interpolate(progress.value, [0, 1], [-width, width]);
53
+ return { transform: [{ translateX }] };
54
+ });
49
55
 
50
56
  const onLayout = (newWidth: number) => {
51
57
  if (newWidth !== width) {
@@ -60,11 +66,13 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
60
66
  style={[Styles.flex, { backgroundColor: PRIMARY_COLOR }]}
61
67
  >
62
68
  <Animated.View
63
- style={{
64
- transform: [{ translateX: linearTranslate }],
65
- width: '60%',
66
- height: '100%',
67
- }}
69
+ style={[
70
+ {
71
+ width: '60%',
72
+ height: '100%',
73
+ },
74
+ shimmerStyle,
75
+ ]}
68
76
  >
69
77
  <LinearGradient
70
78
  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 = true, 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
+ const customRate = userScaleRate ?? ctxRate ?? 1;
18
+
19
+ if (!useOSFontScale) {
20
+ return customRate > 1 ? customRate * size : size;
21
+ }
17
22
 
18
23
  let fontSizeDeviceScale = size;
19
24
  let fontSizeOSScale = size;
@@ -27,8 +32,8 @@ const useScaleSize = (size: number, scaleRate?: number) => {
27
32
 
28
33
  if (fontScale > 1) {
29
34
  fontSizeOSScale = Math.min(
30
- fontSizeOSScale * fontScale,
31
- fontSizeOSScale * maxScaleRate,
35
+ fontScale * 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.162.2-test.1",
3
+ "version": "0.162.2-test.20",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},