@momo-kits/foundation 0.161.2-beta.9 → 0.161.2-reanimated.2
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/Application/BottomSheet.tsx +2 -3
- package/Application/BottomTab/index.tsx +1 -1
- package/Application/Components/HeaderAnimated.tsx +5 -8
- package/Application/Components/HeaderBackground.tsx +4 -4
- package/Application/Components/HeaderExtendHeader.tsx +14 -13
- package/Application/Components/HeaderTitle.tsx +9 -5
- package/Application/Components/SearchHeader.tsx +4 -7
- package/Application/Components/useAnimatedCompat.ts +63 -0
- package/Application/types.ts +4 -3
- package/Layout/FloatingButton.tsx +10 -5
- package/Layout/Screen.tsx +98 -65
- package/package.json +1 -1
|
@@ -37,8 +37,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
37
37
|
const action = useRef<undefined | string>(undefined);
|
|
38
38
|
const insets = useSafeAreaInsets();
|
|
39
39
|
const heightHeader = useHeaderHeight();
|
|
40
|
-
const
|
|
41
|
-
const keyboardOffset = heightHeader - bottomInset;
|
|
40
|
+
const keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
|
|
42
41
|
|
|
43
42
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
44
43
|
|
|
@@ -312,7 +311,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
312
311
|
{useBottomInset && (
|
|
313
312
|
<View
|
|
314
313
|
style={{
|
|
315
|
-
height:
|
|
314
|
+
height: Math.min(insets.bottom, 21),
|
|
316
315
|
backgroundColor: backgroundColor,
|
|
317
316
|
}}
|
|
318
317
|
/>
|
|
@@ -296,7 +296,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
296
296
|
activeTintColor={theme.colors.primary}
|
|
297
297
|
style={[
|
|
298
298
|
{
|
|
299
|
-
height: 64 +
|
|
299
|
+
height: 64 + Math.min(insets.bottom, 21),
|
|
300
300
|
},
|
|
301
301
|
]}
|
|
302
302
|
/>
|
|
@@ -8,6 +8,7 @@ import { type HeaderAnimatedProps } from '../types';
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { Image } from '../../Image';
|
|
10
10
|
import { Colors } from '../../Consts';
|
|
11
|
+
import { useNormalizedSharedValue } from './useAnimatedCompat';
|
|
11
12
|
|
|
12
13
|
const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
13
14
|
animatedValue,
|
|
@@ -15,23 +16,19 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
|
15
16
|
useScale = true,
|
|
16
17
|
children,
|
|
17
18
|
}) => {
|
|
19
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
18
20
|
const animatedStyle = useAnimatedStyle(() => {
|
|
19
21
|
const scale = useScale
|
|
20
|
-
? interpolate(
|
|
21
|
-
animatedValue.value,
|
|
22
|
-
[-300, 0, 300],
|
|
23
|
-
[4, 1, 1],
|
|
24
|
-
Extrapolation.CLAMP,
|
|
25
|
-
)
|
|
22
|
+
? interpolate(sharedValue.value, [-300, 0, 300], [4, 1, 1], Extrapolation.CLAMP)
|
|
26
23
|
: 1;
|
|
27
24
|
const opacity = interpolate(
|
|
28
|
-
|
|
25
|
+
sharedValue.value,
|
|
29
26
|
[0, 150, 300],
|
|
30
27
|
[1, 0.5, 0],
|
|
31
28
|
Extrapolation.CLAMP,
|
|
32
29
|
);
|
|
33
30
|
const translateY = interpolate(
|
|
34
|
-
|
|
31
|
+
sharedValue.value,
|
|
35
32
|
[-300, 0],
|
|
36
33
|
[-80, -2],
|
|
37
34
|
Extrapolation.CLAMP,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import LinearGradient from 'react-native-linear-gradient';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import React, { useContext } from 'react';
|
|
4
|
-
import Animated
|
|
4
|
+
import Animated from 'react-native-reanimated';
|
|
5
5
|
import { HeaderBackgroundProps } from '../types';
|
|
6
6
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
7
7
|
import { Colors, Styles } from '../../Consts';
|
|
8
8
|
import { Image } from '../../Image';
|
|
9
9
|
import BackgroundImageView from './BackgroundImageView';
|
|
10
|
+
import { useNormalizedSharedValue } from './useAnimatedCompat';
|
|
10
11
|
|
|
11
12
|
const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
12
13
|
animatedValue,
|
|
@@ -19,8 +20,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
|
19
20
|
const context = useContext<any>(MiniAppContext);
|
|
20
21
|
const gradientColor = customGradientColor ?? theme.colors.gradient;
|
|
21
22
|
const headerImage = headerBackground ?? theme.assets?.headerBackground;
|
|
22
|
-
const
|
|
23
|
-
const sv = animatedValue ?? fallback;
|
|
23
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
24
24
|
|
|
25
25
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
|
29
29
|
<BackgroundImageView
|
|
30
30
|
useShadowHeader={useShadowHeader}
|
|
31
31
|
heightHeader={'100%'}
|
|
32
|
-
animatedValue={
|
|
32
|
+
animatedValue={sharedValue}
|
|
33
33
|
headerBackground={headerImage}
|
|
34
34
|
/>
|
|
35
35
|
<View style={styles.gradientContainer}>
|
|
@@ -5,8 +5,6 @@ import Animated, {
|
|
|
5
5
|
Extrapolation,
|
|
6
6
|
interpolate,
|
|
7
7
|
useAnimatedStyle,
|
|
8
|
-
useSharedValue,
|
|
9
|
-
type SharedValue,
|
|
10
8
|
} from 'react-native-reanimated';
|
|
11
9
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
12
10
|
import { HeaderType } from '../../Layout/types';
|
|
@@ -16,6 +14,10 @@ import { Colors, Radius, Spacing } from '../../Consts';
|
|
|
16
14
|
import { Image } from '../../Image';
|
|
17
15
|
import { SearchHeaderProps } from '../types';
|
|
18
16
|
import BackgroundImageView from './BackgroundImageView';
|
|
17
|
+
import {
|
|
18
|
+
AnimatedCompatValue,
|
|
19
|
+
useNormalizedSharedValue,
|
|
20
|
+
} from './useAnimatedCompat';
|
|
19
21
|
|
|
20
22
|
const SCREEN_PADDING = 12;
|
|
21
23
|
const BACK_WIDTH = 28;
|
|
@@ -24,7 +26,7 @@ const { width: screenWidth } = Dimensions.get('window');
|
|
|
24
26
|
|
|
25
27
|
const HeaderExtendHeader: React.FC<{
|
|
26
28
|
headerType?: HeaderType;
|
|
27
|
-
animatedValue:
|
|
29
|
+
animatedValue: AnimatedCompatValue;
|
|
28
30
|
heightHeader: number;
|
|
29
31
|
headerRightWidth: number;
|
|
30
32
|
inputSearchProps?: SearchHeaderProps;
|
|
@@ -46,8 +48,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
46
48
|
}) => {
|
|
47
49
|
const { theme } = useContext(ApplicationContext);
|
|
48
50
|
const context = useContext<any>(MiniAppContext);
|
|
49
|
-
const
|
|
50
|
-
const sv = animatedValue ?? fallback;
|
|
51
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
51
52
|
const gradientColor = customGradientColor ?? theme.colors.gradient;
|
|
52
53
|
const headerBackground = customBackground ?? theme.assets?.headerBackground;
|
|
53
54
|
const leftPosition = inputSearchProps?.leftPosition || BACK_WIDTH + 20;
|
|
@@ -61,7 +62,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
61
62
|
|
|
62
63
|
const heightStyle = useAnimatedStyle(() => ({
|
|
63
64
|
height: interpolate(
|
|
64
|
-
|
|
65
|
+
sharedValue.value,
|
|
65
66
|
[0, 100],
|
|
66
67
|
[heightHeader + 52, heightHeader],
|
|
67
68
|
Extrapolation.CLAMP,
|
|
@@ -69,14 +70,14 @@ const HeaderExtendHeader: React.FC<{
|
|
|
69
70
|
}));
|
|
70
71
|
|
|
71
72
|
const gradientOpacityStyle = useAnimatedStyle(() => ({
|
|
72
|
-
opacity: interpolate(
|
|
73
|
+
opacity: interpolate(sharedValue.value, [0, 52], [1, 0], Extrapolation.CLAMP),
|
|
73
74
|
}));
|
|
74
75
|
|
|
75
76
|
const searchTranslateStyle = useAnimatedStyle(() => ({
|
|
76
77
|
transform: [
|
|
77
78
|
{
|
|
78
79
|
translateX: interpolate(
|
|
79
|
-
|
|
80
|
+
sharedValue.value,
|
|
80
81
|
[0, 100],
|
|
81
82
|
[SCREEN_PADDING, leftPosition],
|
|
82
83
|
Extrapolation.CLAMP,
|
|
@@ -84,7 +85,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
84
85
|
},
|
|
85
86
|
],
|
|
86
87
|
width: interpolate(
|
|
87
|
-
|
|
88
|
+
sharedValue.value,
|
|
88
89
|
[0, 100],
|
|
89
90
|
[
|
|
90
91
|
screenWidth - SCREEN_PADDING * 2,
|
|
@@ -95,7 +96,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
95
96
|
}));
|
|
96
97
|
|
|
97
98
|
const searchBackgroundStyle = useAnimatedStyle(() => {
|
|
98
|
-
const t = Math.min(Math.max(
|
|
99
|
+
const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
|
|
99
100
|
return { backgroundColor: t === 0
|
|
100
101
|
? theme.colors.background.surface
|
|
101
102
|
: theme.colors.background.default };
|
|
@@ -103,7 +104,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
103
104
|
|
|
104
105
|
const extendedHeightStyle = useAnimatedStyle(() => ({
|
|
105
106
|
height: interpolate(
|
|
106
|
-
|
|
107
|
+
sharedValue.value,
|
|
107
108
|
[0, 100],
|
|
108
109
|
[heightHeader + 52, heightHeader],
|
|
109
110
|
Extrapolation.CLAMP,
|
|
@@ -117,7 +118,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
117
118
|
<BackgroundImageView
|
|
118
119
|
useShadowHeader={useShadowHeader}
|
|
119
120
|
heightHeader={heightHeader}
|
|
120
|
-
animatedValue={
|
|
121
|
+
animatedValue={sharedValue}
|
|
121
122
|
headerBackground={headerBackground}
|
|
122
123
|
/>
|
|
123
124
|
<Animated.View
|
|
@@ -185,7 +186,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
185
186
|
<BackgroundImageView
|
|
186
187
|
useShadowHeader={useShadowHeader}
|
|
187
188
|
heightHeader={heightHeader}
|
|
188
|
-
animatedValue={
|
|
189
|
+
animatedValue={sharedValue}
|
|
189
190
|
headerBackground={headerBackground}
|
|
190
191
|
/>
|
|
191
192
|
{!!gradientColor && (
|
|
@@ -9,7 +9,6 @@ import Animated, {
|
|
|
9
9
|
Extrapolation,
|
|
10
10
|
interpolate,
|
|
11
11
|
useAnimatedStyle,
|
|
12
|
-
type SharedValue,
|
|
13
12
|
} from 'react-native-reanimated';
|
|
14
13
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
15
14
|
import { exportFontFamily, Text, useScaleSize } from '../../Text';
|
|
@@ -22,6 +21,10 @@ import {
|
|
|
22
21
|
import { Image } from '../../Image';
|
|
23
22
|
import { Icon } from '../../Icon';
|
|
24
23
|
import { Skeleton } from '../../Skeleton';
|
|
24
|
+
import {
|
|
25
|
+
AnimatedCompatValue,
|
|
26
|
+
useNormalizedSharedValue,
|
|
27
|
+
} from './useAnimatedCompat';
|
|
25
28
|
|
|
26
29
|
type HeaderTitleInterpolate = {
|
|
27
30
|
inputRange: number[];
|
|
@@ -29,7 +32,7 @@ type HeaderTitleInterpolate = {
|
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
type HeaderTitleExtraProps = {
|
|
32
|
-
animatedValue?:
|
|
35
|
+
animatedValue?: AnimatedCompatValue;
|
|
33
36
|
interpolate?: HeaderTitleInterpolate;
|
|
34
37
|
tintColor?: string;
|
|
35
38
|
children?: React.ReactNode;
|
|
@@ -47,15 +50,16 @@ const HeaderTitle: React.FC<HeaderTitleExtraProps & { [key: string]: any }> = pr
|
|
|
47
50
|
inputRange: [0, 200],
|
|
48
51
|
outputRange: [0, 1],
|
|
49
52
|
};
|
|
50
|
-
const
|
|
53
|
+
const hasAnimated = props.animatedValue != null;
|
|
54
|
+
const sharedValue = useNormalizedSharedValue(props.animatedValue);
|
|
51
55
|
|
|
52
56
|
const animatedStyle = useAnimatedStyle(() => {
|
|
53
|
-
if (!
|
|
57
|
+
if (!hasAnimated) {
|
|
54
58
|
return { opacity: 1 };
|
|
55
59
|
}
|
|
56
60
|
return {
|
|
57
61
|
opacity: interpolate(
|
|
58
|
-
|
|
62
|
+
sharedValue.value,
|
|
59
63
|
interpolateConfig.inputRange,
|
|
60
64
|
interpolateConfig.outputRange,
|
|
61
65
|
Extrapolation.CLAMP,
|
|
@@ -6,14 +6,12 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import Animated, {
|
|
10
|
-
useAnimatedStyle,
|
|
11
|
-
useSharedValue,
|
|
12
|
-
} from 'react-native-reanimated';
|
|
9
|
+
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
13
10
|
import React, { useContext } from 'react';
|
|
14
11
|
import { SearchHeaderProps } from '../types';
|
|
15
12
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
16
13
|
import { Text } from '../../Text';
|
|
14
|
+
import { useNormalizedSharedValue } from './useAnimatedCompat';
|
|
17
15
|
|
|
18
16
|
const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
19
17
|
(
|
|
@@ -31,13 +29,12 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
31
29
|
const BACK_WIDTH = 28;
|
|
32
30
|
const { width: screenWidth } = Dimensions.get('window');
|
|
33
31
|
|
|
34
|
-
const
|
|
35
|
-
const sv = animatedValue ?? fallback;
|
|
32
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
36
33
|
const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
|
|
37
34
|
const headerRightWidth = props?.headerRightWidth ?? 73;
|
|
38
35
|
|
|
39
36
|
const backgroundStyle = useAnimatedStyle(() => {
|
|
40
|
-
const t = Math.min(Math.max(
|
|
37
|
+
const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
|
|
41
38
|
return {
|
|
42
39
|
backgroundColor: t === 0
|
|
43
40
|
? theme.colors.background.surface
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { Animated } from 'react-native';
|
|
3
|
+
import { useSharedValue, type SharedValue } from 'react-native-reanimated';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Union of the two animation runtimes a consumer might hand to a foundation
|
|
7
|
+
* component. react-native `Animated.Value` (legacy) and reanimated
|
|
8
|
+
* `SharedValue` are different objects and cannot be used interchangeably, so
|
|
9
|
+
* foundation components normalize whatever they receive into a `SharedValue`
|
|
10
|
+
* via {@link useNormalizedSharedValue} before rendering with reanimated.
|
|
11
|
+
*/
|
|
12
|
+
export type AnimatedCompatValue = Animated.Value | SharedValue<number>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A react-native `Animated.Value` exposes `setValue`/`interpolate` methods; a
|
|
16
|
+
* reanimated `SharedValue` does not.
|
|
17
|
+
*/
|
|
18
|
+
export const isRNAnimatedValue = (v: unknown): v is Animated.Value =>
|
|
19
|
+
!!v &&
|
|
20
|
+
typeof (v as Animated.Value).setValue === 'function' &&
|
|
21
|
+
typeof (v as Animated.Value).interpolate === 'function';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Heuristic: a reanimated `SharedValue` carries a `value` accessor but none of
|
|
25
|
+
* the `Animated.Value` methods.
|
|
26
|
+
*/
|
|
27
|
+
export const isSharedValue = (v: unknown): v is SharedValue<number> =>
|
|
28
|
+
!!v && !isRNAnimatedValue(v) && 'value' in (v as object);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Normalize an incoming animated value (either runtime, or nothing) into a
|
|
32
|
+
* reanimated `SharedValue<number>` that foundation components can drive with
|
|
33
|
+
* `useAnimatedStyle`/`interpolate`.
|
|
34
|
+
*
|
|
35
|
+
* - SharedValue → returned as-is (zero overhead, shares the same UI-thread cell)
|
|
36
|
+
* - Animated.Value → bridged: a JS-thread `addListener` mirrors its value into
|
|
37
|
+
* an internal SharedValue. NOTE: when the source `Animated.Value` is driven
|
|
38
|
+
* by `Animated.event(..., { useNativeDriver: true })`, `addListener` does NOT
|
|
39
|
+
* fire per frame, so the bridge stays flat. For the Screen-driven case the
|
|
40
|
+
* bridge is fed from the scroll `listener` instead (see Screen.tsx); this
|
|
41
|
+
* hook's listener is the safety net for standalone / JS-driven usage.
|
|
42
|
+
* - undefined → a fresh internal SharedValue stuck at 0
|
|
43
|
+
*/
|
|
44
|
+
export function useNormalizedSharedValue(
|
|
45
|
+
input?: AnimatedCompatValue,
|
|
46
|
+
): SharedValue<number> {
|
|
47
|
+
const fallback = useSharedValue(0);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!isRNAnimatedValue(input)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const id = input.addListener(({ value }) => {
|
|
54
|
+
fallback.value = value;
|
|
55
|
+
});
|
|
56
|
+
return () => input.removeListener(id);
|
|
57
|
+
}, [input, fallback]);
|
|
58
|
+
|
|
59
|
+
if (isSharedValue(input)) {
|
|
60
|
+
return input;
|
|
61
|
+
}
|
|
62
|
+
return fallback;
|
|
63
|
+
}
|
package/Application/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { EventArg } from '@react-navigation/core';
|
|
|
2
2
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
|
3
3
|
import React, { ReactNode } from 'react';
|
|
4
4
|
import {
|
|
5
|
+
Animated,
|
|
5
6
|
TouchableOpacityProps,
|
|
6
7
|
ViewProps,
|
|
7
8
|
ViewStyle,
|
|
@@ -258,7 +259,7 @@ export interface HeaderBackProps extends NavigationButtonProps {
|
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
export type HeaderBackgroundProps = {
|
|
261
|
-
animatedValue?: SharedValue<number>;
|
|
262
|
+
animatedValue?: Animated.Value | SharedValue<number>;
|
|
262
263
|
useGradient?: boolean;
|
|
263
264
|
useShadowHeader?: boolean;
|
|
264
265
|
backgroundColor?: string;
|
|
@@ -298,7 +299,7 @@ export type TitleJourneyProps = {
|
|
|
298
299
|
};
|
|
299
300
|
|
|
300
301
|
export interface HeaderAnimatedProps extends ViewProps {
|
|
301
|
-
animatedValue: SharedValue<number>;
|
|
302
|
+
animatedValue: Animated.Value | SharedValue<number>;
|
|
302
303
|
image: string;
|
|
303
304
|
useScale?: boolean;
|
|
304
305
|
}
|
|
@@ -342,7 +343,7 @@ export type AnimatedHeader = {
|
|
|
342
343
|
|
|
343
344
|
export interface SearchHeaderProps extends InputSearchProps {
|
|
344
345
|
ref?: React.RefObject<InputRef>;
|
|
345
|
-
animatedValue?: SharedValue<number>;
|
|
346
|
+
animatedValue?: Animated.Value | SharedValue<number>;
|
|
346
347
|
headerRightWidth?: 0 | 74 | 110 | number;
|
|
347
348
|
leftPosition?: 12 | 48 | number;
|
|
348
349
|
renderButtons?: () => ReactNode;
|
|
@@ -11,11 +11,14 @@ import Animated, {
|
|
|
11
11
|
useSharedValue,
|
|
12
12
|
withTiming,
|
|
13
13
|
runOnJS,
|
|
14
|
-
type SharedValue,
|
|
15
14
|
} from 'react-native-reanimated';
|
|
16
15
|
import { ApplicationContext } from '../Context';
|
|
17
16
|
import { Icon } from '../Icon';
|
|
18
17
|
import { useScaleSize } from '../Text';
|
|
18
|
+
import {
|
|
19
|
+
AnimatedCompatValue,
|
|
20
|
+
useNormalizedSharedValue,
|
|
21
|
+
} from '../Application/Components/useAnimatedCompat';
|
|
19
22
|
|
|
20
23
|
export interface FloatingButtonProps {
|
|
21
24
|
label?: string;
|
|
@@ -24,7 +27,7 @@ export interface FloatingButtonProps {
|
|
|
24
27
|
icon?: string;
|
|
25
28
|
iconColor?: string;
|
|
26
29
|
size?: 'small' | 'large';
|
|
27
|
-
animatedValue?:
|
|
30
|
+
animatedValue?: AnimatedCompatValue;
|
|
28
31
|
bottom?: number;
|
|
29
32
|
renderComponent?: () => React.ReactNode;
|
|
30
33
|
}
|
|
@@ -41,6 +44,8 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
41
44
|
bottom = 12,
|
|
42
45
|
}: FloatingButtonProps) => {
|
|
43
46
|
const { theme } = useContext(ApplicationContext);
|
|
47
|
+
const enabled = animatedValue != null;
|
|
48
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
44
49
|
const scaledFontSize = useScaleSize(16);
|
|
45
50
|
const scaledLineHeight = useScaleSize(22);
|
|
46
51
|
const maxWidth = useSharedValue(0);
|
|
@@ -57,10 +62,10 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
57
62
|
}, [label, opacityAnimated]);
|
|
58
63
|
|
|
59
64
|
useAnimatedReaction(
|
|
60
|
-
() =>
|
|
65
|
+
() => sharedValue.value,
|
|
61
66
|
(value) => {
|
|
62
67
|
'worklet';
|
|
63
|
-
if (!label || !
|
|
68
|
+
if (!label || !enabled) return;
|
|
64
69
|
if (value !== lastOffset.value && value > 0) {
|
|
65
70
|
const direction = value > lastOffset.value ? 'down' : 'up';
|
|
66
71
|
lastOffset.value = value;
|
|
@@ -88,7 +93,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
},
|
|
91
|
-
[label,
|
|
96
|
+
[label, enabled, minWidth],
|
|
92
97
|
);
|
|
93
98
|
|
|
94
99
|
const containerStyle = useAnimatedStyle(() => ({
|
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,9 +25,7 @@ import {
|
|
|
24
25
|
View,
|
|
25
26
|
ViewProps,
|
|
26
27
|
} from 'react-native';
|
|
27
|
-
import
|
|
28
|
-
runOnJS,
|
|
29
|
-
useAnimatedScrollHandler,
|
|
28
|
+
import {
|
|
30
29
|
useSharedValue,
|
|
31
30
|
withTiming,
|
|
32
31
|
type SharedValue,
|
|
@@ -48,6 +47,10 @@ import { InputRef } from '../Input';
|
|
|
48
47
|
import { exportHeaderTitle, getOptions } from '../Application/utils';
|
|
49
48
|
import { validateChildren } from './utils';
|
|
50
49
|
import Section from './Section';
|
|
50
|
+
import {
|
|
51
|
+
isRNAnimatedValue,
|
|
52
|
+
isSharedValue,
|
|
53
|
+
} from '../Application/Components/useAnimatedCompat';
|
|
51
54
|
import {
|
|
52
55
|
HeaderBackground,
|
|
53
56
|
HeaderExtendHeader,
|
|
@@ -142,9 +145,10 @@ export interface ScreenProps extends ViewProps {
|
|
|
142
145
|
inputSearchRef?: Ref<InputRef>;
|
|
143
146
|
|
|
144
147
|
/**
|
|
145
|
-
* Optional. Animated value for header.
|
|
148
|
+
* Optional. Animated value for header. Accepts either a react-native
|
|
149
|
+
* `Animated.Value` (legacy) or a reanimated `SharedValue<number>`.
|
|
146
150
|
*/
|
|
147
|
-
animatedValue?: SharedValue<number>;
|
|
151
|
+
animatedValue?: Animated.Value | SharedValue<number>;
|
|
148
152
|
|
|
149
153
|
/**
|
|
150
154
|
* Optional. If `true`, use shadow header.
|
|
@@ -201,18 +205,36 @@ const Screen = forwardRef(
|
|
|
201
205
|
const screen: any = useContext(ScreenContext);
|
|
202
206
|
const insets = useSafeAreaInsets();
|
|
203
207
|
const heightHeader = useHeaderHeight();
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Dual animation source so both runtimes are supported without crashing:
|
|
211
|
+
* - `rnValue` is a react-native `Animated.Value` driven natively by the
|
|
212
|
+
* scroll event. It is what legacy consumers receive (`animatedValue`).
|
|
213
|
+
* - `sharedValue` is a reanimated `SharedValue` consumed by every internal
|
|
214
|
+
* foundation component (and offered to modern consumers). It is kept in
|
|
215
|
+
* sync from the scroll `listener` (see below), which fires on the JS
|
|
216
|
+
* thread even when the scroll uses the native driver.
|
|
217
|
+
* A consumer may also pass one of the two in; we reuse whichever matches.
|
|
218
|
+
*/
|
|
219
|
+
const rnValue = useRef<Animated.Value>(
|
|
220
|
+
isRNAnimatedValue(customAnimatedValue)
|
|
221
|
+
? customAnimatedValue
|
|
222
|
+
: new Animated.Value(0),
|
|
223
|
+
);
|
|
224
|
+
const internalShared = useSharedValue(0);
|
|
225
|
+
const sharedValue = isSharedValue(customAnimatedValue)
|
|
226
|
+
? customAnimatedValue
|
|
227
|
+
: internalShared;
|
|
228
|
+
|
|
206
229
|
const currentTint = useRef<string | undefined>(undefined);
|
|
207
230
|
const isTab = navigation?.instance?.getState?.()?.type === 'tab';
|
|
208
231
|
|
|
209
|
-
let handleScroll
|
|
232
|
+
let handleScroll;
|
|
210
233
|
let Component: any = View;
|
|
211
234
|
|
|
212
|
-
|
|
213
|
-
let keyboardOffset = heightHeader - bottomInset;
|
|
235
|
+
let keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
|
|
214
236
|
if (headerType === 'extended' || animatedHeader || inputSearchProps) {
|
|
215
|
-
keyboardOffset = -
|
|
237
|
+
keyboardOffset = -Math.min(insets.bottom, 21);
|
|
216
238
|
}
|
|
217
239
|
|
|
218
240
|
/**
|
|
@@ -255,7 +277,7 @@ const Screen = forwardRef(
|
|
|
255
277
|
inputRange: [0, 50],
|
|
256
278
|
outputRange: [1, 0],
|
|
257
279
|
}}
|
|
258
|
-
animatedValue={
|
|
280
|
+
animatedValue={sharedValue}
|
|
259
281
|
/>
|
|
260
282
|
),
|
|
261
283
|
};
|
|
@@ -270,7 +292,7 @@ const Screen = forwardRef(
|
|
|
270
292
|
headerBackground: (props: any) => (
|
|
271
293
|
<HeaderBackground
|
|
272
294
|
{...props}
|
|
273
|
-
animatedValue={
|
|
295
|
+
animatedValue={sharedValue}
|
|
274
296
|
useShadowHeader={useShadowHeader}
|
|
275
297
|
headerBackground={headerBackground}
|
|
276
298
|
gradientColor={gradientColor}
|
|
@@ -290,7 +312,7 @@ const Screen = forwardRef(
|
|
|
290
312
|
inputRange: [0, 50],
|
|
291
313
|
outputRange: [1, 0],
|
|
292
314
|
}}
|
|
293
|
-
animatedValue={
|
|
315
|
+
animatedValue={sharedValue}
|
|
294
316
|
/>
|
|
295
317
|
),
|
|
296
318
|
};
|
|
@@ -304,6 +326,7 @@ const Screen = forwardRef(
|
|
|
304
326
|
headerBackground,
|
|
305
327
|
inputSearchProps,
|
|
306
328
|
navigation?.instance,
|
|
329
|
+
sharedValue,
|
|
307
330
|
useShadowHeader,
|
|
308
331
|
],
|
|
309
332
|
);
|
|
@@ -325,7 +348,7 @@ const Screen = forwardRef(
|
|
|
325
348
|
headerBackground: (props: any) => (
|
|
326
349
|
<HeaderBackground
|
|
327
350
|
{...props}
|
|
328
|
-
animatedValue={
|
|
351
|
+
animatedValue={sharedValue}
|
|
329
352
|
useGradient={false}
|
|
330
353
|
useShadowHeader={useShadowHeader}
|
|
331
354
|
headerBackground={headerBackground}
|
|
@@ -346,7 +369,13 @@ const Screen = forwardRef(
|
|
|
346
369
|
|
|
347
370
|
navigation?.instance?.setOptions(options);
|
|
348
371
|
},
|
|
349
|
-
[
|
|
372
|
+
[
|
|
373
|
+
gradientColor,
|
|
374
|
+
headerBackground,
|
|
375
|
+
navigation?.instance,
|
|
376
|
+
sharedValue,
|
|
377
|
+
useShadowHeader,
|
|
378
|
+
],
|
|
350
379
|
);
|
|
351
380
|
|
|
352
381
|
/**
|
|
@@ -364,13 +393,13 @@ const Screen = forwardRef(
|
|
|
364
393
|
inputRange: [0, 50],
|
|
365
394
|
outputRange: [1, 0],
|
|
366
395
|
}}
|
|
367
|
-
animatedValue={
|
|
396
|
+
animatedValue={sharedValue}
|
|
368
397
|
/>
|
|
369
398
|
),
|
|
370
399
|
};
|
|
371
400
|
|
|
372
401
|
navigation?.instance?.setOptions(options);
|
|
373
|
-
}, [navigation?.instance]);
|
|
402
|
+
}, [navigation?.instance, sharedValue]);
|
|
374
403
|
|
|
375
404
|
/**
|
|
376
405
|
* export search header
|
|
@@ -393,7 +422,7 @@ const Screen = forwardRef(
|
|
|
393
422
|
headerLeft: (props: any) =>
|
|
394
423
|
params?.hiddenBack ? null : <HeaderLeft {...props} />,
|
|
395
424
|
headerTitle: () => (
|
|
396
|
-
<SearchHeader {...params} animatedValue={
|
|
425
|
+
<SearchHeader {...params} animatedValue={sharedValue} />
|
|
397
426
|
),
|
|
398
427
|
};
|
|
399
428
|
|
|
@@ -444,51 +473,47 @@ const Screen = forwardRef(
|
|
|
444
473
|
});
|
|
445
474
|
});
|
|
446
475
|
|
|
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
|
-
|
|
486
476
|
/**
|
|
487
477
|
* animated when use scroll && animated value
|
|
488
478
|
*/
|
|
489
479
|
if (scrollable) {
|
|
490
480
|
Component = Animated.ScrollView;
|
|
491
|
-
handleScroll =
|
|
481
|
+
handleScroll = Animated.event(
|
|
482
|
+
[
|
|
483
|
+
{
|
|
484
|
+
nativeEvent: {
|
|
485
|
+
contentOffset: { y: rnValue.current as Animated.Value },
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
{
|
|
490
|
+
useNativeDriver: true,
|
|
491
|
+
listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
492
|
+
const offsetY = e.nativeEvent.contentOffset.y;
|
|
493
|
+
// keep the reanimated source in sync for the internal components.
|
|
494
|
+
sharedValue.value = offsetY;
|
|
495
|
+
scrollViewProps?.onScroll?.(e);
|
|
496
|
+
if (animatedHeader) {
|
|
497
|
+
let color = animatedHeader?.headerTintColor ?? Colors.black_17;
|
|
498
|
+
if (offsetY > 50) {
|
|
499
|
+
color = Colors.black_17;
|
|
500
|
+
}
|
|
501
|
+
if (color !== currentTint.current) {
|
|
502
|
+
currentTint.current = color;
|
|
503
|
+
navigation?.setOptions({
|
|
504
|
+
headerTintColor: color,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
let barStyle: StatusBarStyle = 'dark-content';
|
|
508
|
+
if (currentTint.current === Colors.black_01) {
|
|
509
|
+
barStyle = 'light-content';
|
|
510
|
+
}
|
|
511
|
+
StatusBar.setBarStyle(barStyle, true);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
);
|
|
492
517
|
}
|
|
493
518
|
|
|
494
519
|
/**
|
|
@@ -498,7 +523,12 @@ const Screen = forwardRef(
|
|
|
498
523
|
const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
499
524
|
const offsetY = e.nativeEvent.contentOffset.y;
|
|
500
525
|
if (inputSearchProps && offsetY < 100 && offsetY > 0) {
|
|
501
|
-
|
|
526
|
+
Animated.timing(rnValue.current, {
|
|
527
|
+
toValue: 0,
|
|
528
|
+
useNativeDriver: true,
|
|
529
|
+
duration: 300,
|
|
530
|
+
}).start();
|
|
531
|
+
sharedValue.value = withTiming(0, { duration: 300 });
|
|
502
532
|
ref?.scrollTo?.({ y: 0, animated: true });
|
|
503
533
|
}
|
|
504
534
|
scrollViewProps?.onScrollEndDrag?.(e);
|
|
@@ -514,7 +544,10 @@ const Screen = forwardRef(
|
|
|
514
544
|
style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
|
|
515
545
|
>
|
|
516
546
|
{animatedHeader?.component({
|
|
517
|
-
animatedValue
|
|
547
|
+
// legacy consumers read `animatedValue` (Animated.Value),
|
|
548
|
+
// modern consumers read `sharedValue` (SharedValue).
|
|
549
|
+
animatedValue: rnValue.current,
|
|
550
|
+
sharedValue: sharedValue,
|
|
518
551
|
})}
|
|
519
552
|
</View>
|
|
520
553
|
);
|
|
@@ -581,7 +614,7 @@ const Screen = forwardRef(
|
|
|
581
614
|
headerType={headerType}
|
|
582
615
|
heightHeader={heightHeader}
|
|
583
616
|
headerRightWidth={headerRightWidth}
|
|
584
|
-
animatedValue={
|
|
617
|
+
animatedValue={sharedValue}
|
|
585
618
|
inputSearchProps={inputSearchProps}
|
|
586
619
|
navigation={navigation}
|
|
587
620
|
inputSearchRef={inputSearchRef}
|
|
@@ -619,9 +652,9 @@ const Screen = forwardRef(
|
|
|
619
652
|
<View>
|
|
620
653
|
<FloatingButton
|
|
621
654
|
{...floatingButtonProps}
|
|
622
|
-
animatedValue={
|
|
655
|
+
animatedValue={sharedValue}
|
|
623
656
|
bottom={
|
|
624
|
-
Footer || isTab ? 12 :
|
|
657
|
+
Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
|
|
625
658
|
}
|
|
626
659
|
/>
|
|
627
660
|
</View>
|
|
@@ -632,7 +665,7 @@ const Screen = forwardRef(
|
|
|
632
665
|
style={[
|
|
633
666
|
styles.shadow,
|
|
634
667
|
{
|
|
635
|
-
paddingBottom:
|
|
668
|
+
paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
|
|
636
669
|
backgroundColor: theme.colors.background.surface,
|
|
637
670
|
},
|
|
638
671
|
]}
|