@momo-kits/foundation 0.162.2-test.10 → 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,18 +200,50 @@ 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
 
204
240
  let handleScroll;
205
241
  let Component: any = View;
206
242
 
207
- let keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
243
+ const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
244
+ let keyboardOffset = heightHeader - bottomInset;
208
245
  if (headerType === 'extended' || animatedHeader || inputSearchProps) {
209
- keyboardOffset = -Math.min(insets.bottom, 21);
246
+ keyboardOffset = -bottomInset;
210
247
  }
211
248
 
212
249
  /**
@@ -248,9 +285,8 @@ const Screen = forwardRef(
248
285
  interpolate={{
249
286
  inputRange: [0, 50],
250
287
  outputRange: [1, 0],
251
- extrapolate: 'clamp',
252
288
  }}
253
- animatedValue={animatedValue.current}
289
+ animatedValue={sharedValue}
254
290
  />
255
291
  ),
256
292
  };
@@ -265,7 +301,7 @@ const Screen = forwardRef(
265
301
  headerBackground: (props: any) => (
266
302
  <HeaderBackground
267
303
  {...props}
268
- animatedValue={animatedValue.current}
304
+ animatedValue={sharedValue}
269
305
  useShadowHeader={useShadowHeader}
270
306
  headerBackground={headerBackground}
271
307
  gradientColor={gradientColor}
@@ -284,9 +320,8 @@ const Screen = forwardRef(
284
320
  interpolate={{
285
321
  inputRange: [0, 50],
286
322
  outputRange: [1, 0],
287
- extrapolate: 'clamp',
288
323
  }}
289
- animatedValue={animatedValue.current}
324
+ animatedValue={sharedValue}
290
325
  />
291
326
  ),
292
327
  };
@@ -300,6 +335,7 @@ const Screen = forwardRef(
300
335
  headerBackground,
301
336
  inputSearchProps,
302
337
  navigation?.instance,
338
+ sharedValue,
303
339
  useShadowHeader,
304
340
  ],
305
341
  );
@@ -321,7 +357,7 @@ const Screen = forwardRef(
321
357
  headerBackground: (props: any) => (
322
358
  <HeaderBackground
323
359
  {...props}
324
- animatedValue={animatedValue.current}
360
+ animatedValue={sharedValue}
325
361
  useGradient={false}
326
362
  useShadowHeader={useShadowHeader}
327
363
  headerBackground={headerBackground}
@@ -342,7 +378,13 @@ const Screen = forwardRef(
342
378
 
343
379
  navigation?.instance?.setOptions(options);
344
380
  },
345
- [gradientColor, headerBackground, navigation?.instance, useShadowHeader],
381
+ [
382
+ gradientColor,
383
+ headerBackground,
384
+ navigation?.instance,
385
+ sharedValue,
386
+ useShadowHeader,
387
+ ],
346
388
  );
347
389
 
348
390
  /**
@@ -359,15 +401,14 @@ const Screen = forwardRef(
359
401
  interpolate={{
360
402
  inputRange: [0, 50],
361
403
  outputRange: [1, 0],
362
- extrapolate: 'clamp',
363
404
  }}
364
- animatedValue={animatedValue.current}
405
+ animatedValue={sharedValue}
365
406
  />
366
407
  ),
367
408
  };
368
409
 
369
410
  navigation?.instance?.setOptions(options);
370
- }, [navigation?.instance]);
411
+ }, [navigation?.instance, sharedValue]);
371
412
 
372
413
  /**
373
414
  * export search header
@@ -390,7 +431,7 @@ const Screen = forwardRef(
390
431
  headerLeft: (props: any) =>
391
432
  params?.hiddenBack ? null : <HeaderLeft {...props} />,
392
433
  headerTitle: () => (
393
- <SearchHeader {...params} animatedValue={animatedValue.current} />
434
+ <SearchHeader {...params} animatedValue={sharedValue} />
394
435
  ),
395
436
  };
396
437
 
@@ -457,9 +498,11 @@ const Screen = forwardRef(
457
498
  {
458
499
  useNativeDriver: true,
459
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;
460
504
  scrollViewProps?.onScroll?.(e);
461
505
  if (animatedHeader) {
462
- const offsetY = e.nativeEvent.contentOffset.y;
463
506
  let color = animatedHeader?.headerTintColor ?? Colors.black_17;
464
507
  if (offsetY > 50) {
465
508
  color = Colors.black_17;
@@ -494,6 +537,7 @@ const Screen = forwardRef(
494
537
  useNativeDriver: true,
495
538
  duration: 300,
496
539
  }).start();
540
+ sharedValue.value = withTiming(0, { duration: 300 });
497
541
  ref?.scrollTo?.({ y: 0, animated: true });
498
542
  }
499
543
  scrollViewProps?.onScrollEndDrag?.(e);
@@ -509,7 +553,10 @@ const Screen = forwardRef(
509
553
  style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
510
554
  >
511
555
  {animatedHeader?.component({
556
+ // legacy consumers read `animatedValue` (Animated.Value),
557
+ // modern consumers read `sharedValue` (SharedValue).
512
558
  animatedValue: animatedValue.current,
559
+ sharedValue: sharedValue,
513
560
  })}
514
561
  </View>
515
562
  );
@@ -576,7 +623,7 @@ const Screen = forwardRef(
576
623
  headerType={headerType}
577
624
  heightHeader={heightHeader}
578
625
  headerRightWidth={headerRightWidth}
579
- animatedValue={animatedValue.current}
626
+ animatedValue={sharedValue}
580
627
  inputSearchProps={inputSearchProps}
581
628
  navigation={navigation}
582
629
  inputSearchRef={inputSearchRef}
@@ -614,9 +661,9 @@ const Screen = forwardRef(
614
661
  <View>
615
662
  <FloatingButton
616
663
  {...floatingButtonProps}
617
- animatedValue={animatedValue.current}
664
+ animatedValue={sharedValue}
618
665
  bottom={
619
- Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
666
+ Footer || isTab ? 12 : bottomInset + Spacing.S
620
667
  }
621
668
  />
622
669
  </View>
@@ -627,7 +674,7 @@ const Screen = forwardRef(
627
674
  style={[
628
675
  styles.shadow,
629
676
  {
630
- paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
677
+ paddingBottom: bottomInset + Spacing.S,
631
678
  backgroundColor: theme.colors.background.surface,
632
679
  },
633
680
  ]}
@@ -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
@@ -14,17 +14,12 @@ const useScaleSize = (size: number, userScaleRate?: number) => {
14
14
  const { width } = useWindowDimensions();
15
15
  const deviceScale = width / DEFAULT_SCREEN_SIZE;
16
16
 
17
- // rate precedence: per-call param override > host config (ctxRate) > 1 (no scaling)
18
17
  const customRate = userScaleRate ?? ctxRate ?? 1;
19
18
 
20
- // useOSFontScale off -> the user has full control via customRate; skip device-width scaling
21
- // entirely and apply only the custom rate (capped at MAX_FONT_SCALE).
22
19
  if (!useOSFontScale) {
23
- // custom rate is uncapped (full user control); only the OS-follow path below caps at MAX_FONT_SCALE.
24
20
  return customRate > 1 ? customRate * size : size;
25
21
  }
26
22
 
27
- // useOSFontScale on -> device-width scale + OS font scale, take the larger.
28
23
  let fontSizeDeviceScale = size;
29
24
  let fontSizeOSScale = size;
30
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.162.2-test.10",
3
+ "version": "0.162.2-test.20",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},