@momo-kits/foundation 0.161.2-beta.8 → 0.161.2-beta.9

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.
@@ -8,13 +8,22 @@ import React, {
8
8
  useState,
9
9
  } from 'react';
10
10
  import {
11
- Animated,
12
11
  PixelRatio,
13
12
  TextInput,
14
13
  TextInputFocusEvent,
15
14
  TouchableOpacity,
16
15
  View,
17
16
  } from 'react-native';
17
+ import Animated, {
18
+ cancelAnimation,
19
+ Easing,
20
+ useAnimatedStyle,
21
+ useSharedValue,
22
+ withDelay,
23
+ withRepeat,
24
+ withSequence,
25
+ withTiming,
26
+ } from 'react-native-reanimated';
18
27
  import { useComponentId } from '../Application';
19
28
  import { Spacing, Styles } from '../Consts';
20
29
  import { useScaleSize, Text } from '../Text';
@@ -32,37 +41,43 @@ import {
32
41
  const OTPCaret: FC<CaretProps> = ({ index, length }) => {
33
42
  const DURATION = 300;
34
43
  const { theme } = useContext(ApplicationContext);
35
- const opacity = useRef(new Animated.Value(0)).current;
44
+ const opacity = useSharedValue(0);
36
45
 
37
46
  useEffect(() => {
38
- Animated.loop(
39
- Animated.sequence([
40
- Animated.timing(opacity, {
41
- toValue: 1,
42
- duration: DURATION,
43
- useNativeDriver: true,
44
- }),
45
- Animated.delay(DURATION * 2),
46
- Animated.timing(opacity, {
47
- toValue: 0,
48
- duration: DURATION,
49
- useNativeDriver: true,
50
- }),
51
- ]),
52
- ).start();
47
+ opacity.value = withRepeat(
48
+ withSequence(
49
+ withTiming(1, { duration: DURATION, easing: Easing.linear }),
50
+ withDelay(
51
+ DURATION * 2,
52
+ withTiming(0, { duration: DURATION, easing: Easing.linear }),
53
+ ),
54
+ ),
55
+ -1,
56
+ false,
57
+ );
58
+ return () => {
59
+ cancelAnimation(opacity);
60
+ };
53
61
  }, [opacity]);
62
+
63
+ const animatedStyle = useAnimatedStyle(() => ({
64
+ opacity: opacity.value,
65
+ }));
66
+
54
67
  const spacingStyle = !isNaN(Number(length)) &&
55
68
  index !== Number(length) - 1 && { marginRight: Spacing.L };
56
69
 
57
70
  return (
58
71
  <View style={[Styles.rowCenter, spacingStyle]}>
59
72
  <Animated.View
60
- style={{
61
- height: useScaleSize(12),
62
- width: 1,
63
- backgroundColor: theme.colors.primary,
64
- opacity,
65
- }}
73
+ style={[
74
+ {
75
+ height: useScaleSize(12),
76
+ width: 1,
77
+ backgroundColor: theme.colors.primary,
78
+ },
79
+ animatedStyle,
80
+ ]}
66
81
  />
67
82
  <Text color={theme.colors.text.hint} typography={'body_default_regular'}>
68
83
  -
@@ -1,17 +1,18 @@
1
+ import React, { useContext, useEffect, useState } from 'react';
1
2
  import {
2
- default as React,
3
- useContext,
4
- useEffect,
5
- useRef,
6
- useState,
7
- } from 'react';
8
- import {
9
- Animated,
10
3
  LayoutChangeEvent,
11
4
  StyleSheet,
12
5
  TouchableOpacity,
13
6
  View,
14
7
  } from 'react-native';
8
+ import Animated, {
9
+ useAnimatedReaction,
10
+ useAnimatedStyle,
11
+ useSharedValue,
12
+ withTiming,
13
+ runOnJS,
14
+ type SharedValue,
15
+ } from 'react-native-reanimated';
15
16
  import { ApplicationContext } from '../Context';
16
17
  import { Icon } from '../Icon';
17
18
  import { useScaleSize } from '../Text';
@@ -23,7 +24,7 @@ export interface FloatingButtonProps {
23
24
  icon?: string;
24
25
  iconColor?: string;
25
26
  size?: 'small' | 'large';
26
- animatedValue?: Animated.Value;
27
+ animatedValue?: SharedValue<number>;
27
28
  bottom?: number;
28
29
  renderComponent?: () => React.ReactNode;
29
30
  }
@@ -42,68 +43,67 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
42
43
  const { theme } = useContext(ApplicationContext);
43
44
  const scaledFontSize = useScaleSize(16);
44
45
  const scaledLineHeight = useScaleSize(22);
45
- const maxWidth = useRef(0);
46
+ const maxWidth = useSharedValue(0);
46
47
  const minWidth = size === 'small' ? 36 : 48;
47
- const [opacityAnimated] = useState(new Animated.Value(0)); // Initial opacity set to 0
48
- const [widthAnimated, setWidthAnimated] = useState<Animated.Value>();
49
- const lastOffset = useRef(0);
50
- const lastDirection = useRef<string>(null);
51
- const [showText, setShowText] = React.useState(true);
48
+ const opacityAnimated = useSharedValue(0);
49
+ const widthAnimated = useSharedValue<number | null>(null);
50
+ const lastOffset = useSharedValue(0);
51
+ const lastDirection = useSharedValue<string | null>(null);
52
+ const [showText, setShowText] = useState(true);
52
53
 
53
54
  useEffect(() => {
54
55
  if (!label) return;
56
+ opacityAnimated.value = withTiming(1, { duration: 100 });
57
+ }, [label, opacityAnimated]);
55
58
 
56
- Animated.timing(opacityAnimated, {
57
- toValue: 1,
58
- duration: 100,
59
- useNativeDriver: true,
60
- }).start();
61
-
62
- const listener = animatedValue?.addListener(({ value }) => {
63
- if (value !== lastOffset.current && value > 0) {
64
- const direction = value > lastOffset.current ? 'down' : 'up';
65
- lastOffset.current = value;
66
- if (lastDirection.current !== direction) {
67
- lastDirection.current = direction;
59
+ useAnimatedReaction(
60
+ () => animatedValue?.value ?? 0,
61
+ (value) => {
62
+ 'worklet';
63
+ if (!label || !animatedValue) return;
64
+ if (value !== lastOffset.value && value > 0) {
65
+ const direction = value > lastOffset.value ? 'down' : 'up';
66
+ lastOffset.value = value;
67
+ if (lastDirection.value !== direction) {
68
+ lastDirection.value = direction;
68
69
  if (direction === 'down') {
69
- Animated.timing(opacityAnimated, {
70
- toValue: 0,
71
- duration: 100,
72
- useNativeDriver: true,
73
- }).start();
74
- Animated.timing(widthAnimated!, {
75
- toValue: minWidth,
76
- duration: 100,
77
- useNativeDriver: false,
78
- }).start(() => setShowText(false));
70
+ opacityAnimated.value = withTiming(0, { duration: 100 });
71
+ widthAnimated.value = withTiming(
72
+ minWidth,
73
+ { duration: 100 },
74
+ (finished) => {
75
+ if (finished) runOnJS(setShowText)(false);
76
+ },
77
+ );
79
78
  } else {
80
- Animated.timing(opacityAnimated, {
81
- toValue: 1,
82
- duration: 100,
83
- useNativeDriver: true,
84
- }).start();
85
- Animated.timing(widthAnimated!, {
86
- toValue: maxWidth.current,
87
- duration: 100,
88
- useNativeDriver: false,
89
- }).start(() => setShowText(true));
79
+ opacityAnimated.value = withTiming(1, { duration: 100 });
80
+ widthAnimated.value = withTiming(
81
+ maxWidth.value,
82
+ { duration: 100 },
83
+ (finished) => {
84
+ if (finished) runOnJS(setShowText)(true);
85
+ },
86
+ );
90
87
  }
91
88
  }
92
89
  }
93
- });
90
+ },
91
+ [label, animatedValue, minWidth],
92
+ );
94
93
 
95
- return () => {
96
- if (listener) {
97
- animatedValue?.removeListener(listener);
98
- }
99
- };
100
- }, [animatedValue, label, minWidth, opacityAnimated, widthAnimated]);
94
+ const containerStyle = useAnimatedStyle(() => ({
95
+ width: widthAnimated.value ?? undefined,
96
+ }));
97
+
98
+ const labelStyle = useAnimatedStyle(() => ({
99
+ opacity: opacityAnimated.value,
100
+ }));
101
101
 
102
102
  const handleLayout = (event: LayoutChangeEvent) => {
103
103
  const layout = event.nativeEvent.layout;
104
- if (widthAnimated) return;
105
- maxWidth.current = layout.width;
106
- setWidthAnimated(new Animated.Value(layout.width));
104
+ if (widthAnimated.value != null) return;
105
+ maxWidth.value = layout.width;
106
+ widthAnimated.value = layout.width;
107
107
  };
108
108
 
109
109
  if (renderComponent) {
@@ -132,11 +132,11 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
132
132
  {
133
133
  right: position === 'right' ? 12 : undefined,
134
134
  alignSelf: position === 'center' ? 'center' : 'flex-end',
135
- width: widthAnimated,
136
135
  height: size === 'small' ? 36 : 48,
137
136
  backgroundColor: theme.colors.primary,
138
137
  bottom,
139
138
  },
139
+ containerStyle,
140
140
  ]}
141
141
  >
142
142
  <TouchableOpacity
@@ -156,9 +156,9 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
156
156
  {
157
157
  fontSize: scaledFontSize,
158
158
  lineHeight: scaledLineHeight,
159
- opacity: opacityAnimated,
160
159
  color: 'white',
161
160
  },
161
+ labelStyle,
162
162
  ]}
163
163
  numberOfLines={1}
164
164
  >
package/Layout/Screen.tsx CHANGED
@@ -12,7 +12,6 @@ import React, {
12
12
  useRef,
13
13
  } from 'react';
14
14
  import {
15
- Animated,
16
15
  KeyboardAvoidingView,
17
16
  NativeScrollEvent,
18
17
  NativeSyntheticEvent,
@@ -25,6 +24,13 @@ import {
25
24
  View,
26
25
  ViewProps,
27
26
  } from 'react-native';
27
+ import Animated, {
28
+ runOnJS,
29
+ useAnimatedScrollHandler,
30
+ useSharedValue,
31
+ withTiming,
32
+ type SharedValue,
33
+ } from 'react-native-reanimated';
28
34
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
29
35
  import { ApplicationContext, ScreenContext } from '../Context';
30
36
  import Navigation from '../Application/Navigation';
@@ -138,7 +144,7 @@ export interface ScreenProps extends ViewProps {
138
144
  /**
139
145
  * Optional. Animated value for header.
140
146
  */
141
- animatedValue?: Animated.Value;
147
+ animatedValue?: SharedValue<number>;
142
148
 
143
149
  /**
144
150
  * Optional. If `true`, use shadow header.
@@ -195,18 +201,18 @@ const Screen = forwardRef(
195
201
  const screen: any = useContext(ScreenContext);
196
202
  const insets = useSafeAreaInsets();
197
203
  const heightHeader = useHeaderHeight();
198
- const animatedValue = useRef<Animated.Value>(
199
- customAnimatedValue || new Animated.Value(0),
200
- );
204
+ const internalAnimatedValue = useSharedValue(0);
205
+ const animatedValue = customAnimatedValue ?? internalAnimatedValue;
201
206
  const currentTint = useRef<string | undefined>(undefined);
202
207
  const isTab = navigation?.instance?.getState?.()?.type === 'tab';
203
208
 
204
- let handleScroll;
209
+ let handleScroll: any;
205
210
  let Component: any = View;
206
211
 
207
- let keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
212
+ const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
213
+ let keyboardOffset = heightHeader - bottomInset;
208
214
  if (headerType === 'extended' || animatedHeader || inputSearchProps) {
209
- keyboardOffset = -Math.min(insets.bottom, 21);
215
+ keyboardOffset = -bottomInset;
210
216
  }
211
217
 
212
218
  /**
@@ -248,9 +254,8 @@ const Screen = forwardRef(
248
254
  interpolate={{
249
255
  inputRange: [0, 50],
250
256
  outputRange: [1, 0],
251
- extrapolate: 'clamp',
252
257
  }}
253
- animatedValue={animatedValue.current}
258
+ animatedValue={animatedValue}
254
259
  />
255
260
  ),
256
261
  };
@@ -265,7 +270,7 @@ const Screen = forwardRef(
265
270
  headerBackground: (props: any) => (
266
271
  <HeaderBackground
267
272
  {...props}
268
- animatedValue={animatedValue.current}
273
+ animatedValue={animatedValue}
269
274
  useShadowHeader={useShadowHeader}
270
275
  headerBackground={headerBackground}
271
276
  gradientColor={gradientColor}
@@ -284,9 +289,8 @@ const Screen = forwardRef(
284
289
  interpolate={{
285
290
  inputRange: [0, 50],
286
291
  outputRange: [1, 0],
287
- extrapolate: 'clamp',
288
292
  }}
289
- animatedValue={animatedValue.current}
293
+ animatedValue={animatedValue}
290
294
  />
291
295
  ),
292
296
  };
@@ -321,7 +325,7 @@ const Screen = forwardRef(
321
325
  headerBackground: (props: any) => (
322
326
  <HeaderBackground
323
327
  {...props}
324
- animatedValue={animatedValue.current}
328
+ animatedValue={animatedValue}
325
329
  useGradient={false}
326
330
  useShadowHeader={useShadowHeader}
327
331
  headerBackground={headerBackground}
@@ -359,9 +363,8 @@ const Screen = forwardRef(
359
363
  interpolate={{
360
364
  inputRange: [0, 50],
361
365
  outputRange: [1, 0],
362
- extrapolate: 'clamp',
363
366
  }}
364
- animatedValue={animatedValue.current}
367
+ animatedValue={animatedValue}
365
368
  />
366
369
  ),
367
370
  };
@@ -390,7 +393,7 @@ const Screen = forwardRef(
390
393
  headerLeft: (props: any) =>
391
394
  params?.hiddenBack ? null : <HeaderLeft {...props} />,
392
395
  headerTitle: () => (
393
- <SearchHeader {...params} animatedValue={animatedValue.current} />
396
+ <SearchHeader {...params} animatedValue={animatedValue} />
394
397
  ),
395
398
  };
396
399
 
@@ -441,45 +444,51 @@ const Screen = forwardRef(
441
444
  });
442
445
  });
443
446
 
447
+ const onTintColorChange = useCallback(
448
+ (offsetY: number) => {
449
+ if (!animatedHeader) return;
450
+ let color = animatedHeader?.headerTintColor ?? Colors.black_17;
451
+ if (offsetY > 50) {
452
+ color = Colors.black_17;
453
+ }
454
+ if (color !== currentTint.current) {
455
+ currentTint.current = color;
456
+ navigation?.setOptions({
457
+ headerTintColor: color,
458
+ });
459
+ let barStyle: StatusBarStyle = 'dark-content';
460
+ if (currentTint.current === Colors.black_01) {
461
+ barStyle = 'light-content';
462
+ }
463
+ StatusBar.setBarStyle(barStyle, true);
464
+ }
465
+ },
466
+ [animatedHeader, navigation],
467
+ );
468
+
469
+ const emitOnScroll = useCallback(
470
+ (offsetY: number) => {
471
+ scrollViewProps?.onScroll?.({
472
+ nativeEvent: { contentOffset: { x: 0, y: offsetY } },
473
+ } as NativeSyntheticEvent<NativeScrollEvent>);
474
+ },
475
+ [scrollViewProps],
476
+ );
477
+
478
+ const scrollHandler = useAnimatedScrollHandler({
479
+ onScroll: (event) => {
480
+ animatedValue.value = event.contentOffset.y;
481
+ runOnJS(emitOnScroll)(event.contentOffset.y);
482
+ runOnJS(onTintColorChange)(event.contentOffset.y);
483
+ },
484
+ });
485
+
444
486
  /**
445
487
  * animated when use scroll && animated value
446
488
  */
447
489
  if (scrollable) {
448
490
  Component = Animated.ScrollView;
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
- );
491
+ handleScroll = scrollHandler;
483
492
  }
484
493
 
485
494
  /**
@@ -489,11 +498,7 @@ const Screen = forwardRef(
489
498
  const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
490
499
  const offsetY = e.nativeEvent.contentOffset.y;
491
500
  if (inputSearchProps && offsetY < 100 && offsetY > 0) {
492
- Animated.timing(animatedValue.current, {
493
- toValue: 0,
494
- useNativeDriver: true,
495
- duration: 300,
496
- }).start();
501
+ animatedValue.value = withTiming(0, { duration: 300 });
497
502
  ref?.scrollTo?.({ y: 0, animated: true });
498
503
  }
499
504
  scrollViewProps?.onScrollEndDrag?.(e);
@@ -509,7 +514,7 @@ const Screen = forwardRef(
509
514
  style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
510
515
  >
511
516
  {animatedHeader?.component({
512
- animatedValue: animatedValue.current,
517
+ animatedValue: animatedValue,
513
518
  })}
514
519
  </View>
515
520
  );
@@ -576,7 +581,7 @@ const Screen = forwardRef(
576
581
  headerType={headerType}
577
582
  heightHeader={heightHeader}
578
583
  headerRightWidth={headerRightWidth}
579
- animatedValue={animatedValue.current}
584
+ animatedValue={animatedValue}
580
585
  inputSearchProps={inputSearchProps}
581
586
  navigation={navigation}
582
587
  inputSearchRef={inputSearchRef}
@@ -614,9 +619,9 @@ const Screen = forwardRef(
614
619
  <View>
615
620
  <FloatingButton
616
621
  {...floatingButtonProps}
617
- animatedValue={animatedValue.current}
622
+ animatedValue={animatedValue}
618
623
  bottom={
619
- Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
624
+ Footer || isTab ? 12 : bottomInset + Spacing.S
620
625
  }
621
626
  />
622
627
  </View>
@@ -627,7 +632,7 @@ const Screen = forwardRef(
627
632
  style={[
628
633
  styles.shadow,
629
634
  {
630
- paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
635
+ paddingBottom: bottomInset + Spacing.S,
631
636
  backgroundColor: theme.colors.background.surface,
632
637
  },
633
638
  ]}
@@ -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>