@react-native-ama/animations 0.0.1
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/package.json +52 -0
- package/src/components/AnimatedContainer.test.tsx +100 -0
- package/src/components/AnimatedContainer.tsx +64 -0
- package/src/hooks/useAnimation.test.ts +162 -0
- package/src/hooks/useAnimation.ts +64 -0
- package/src/hooks/useAnimationDuration.test.ts +39 -0
- package/src/hooks/useAnimationDuration.ts +22 -0
- package/src/hooks/useReanimatedAnimationBuilder.test.ts +155 -0
- package/src/hooks/useReanimatedAnimationBuilder.ts +187 -0
- package/src/hooks/useReanimatedTiming.test.ts +96 -0
- package/src/hooks/useReanimatedTiming.ts +53 -0
- package/src/index.ts +8 -0
- package/src/utils/isMotionAnimation.ts +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@react-native-ama/animations",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"react-native": "src/index",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": [
|
|
10
|
+
{
|
|
11
|
+
"imports": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./dist/index.js"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "rm -rf dist && tsc -p ./tsconfig.build.json",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@react-native-ama/internal": "*",
|
|
27
|
+
"@react-native-ama/core": "*"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"react": "*",
|
|
31
|
+
"react-native": "*"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"react-native",
|
|
35
|
+
"a11y",
|
|
36
|
+
"accessibility"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/FormidableLabs/react-native-ama",
|
|
41
|
+
"directory": "packages/animations"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/FormidableLabs/react-native-ama/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/FormidableLabs/react-native-ama#readme",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"provenance": false
|
|
49
|
+
},
|
|
50
|
+
"author": "",
|
|
51
|
+
"license": "MIT"
|
|
52
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { AutofocusContainer } from '@react-native-ama/core';
|
|
2
|
+
import { render } from '@testing-library/react-native';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import * as UseReanimatedAnimationBuilder from '../hooks/useReanimatedAnimationBuilder';
|
|
6
|
+
import { AnimatedContainer } from './AnimatedContainer';
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
|
|
11
|
+
jest.spyOn(console, 'warn').mockImplementation();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const from = { left: 0 };
|
|
15
|
+
const to = { opacity: 1 };
|
|
16
|
+
|
|
17
|
+
describe('AnimatedContainer', () => {
|
|
18
|
+
it('passes the given animation data to the hook', () => {
|
|
19
|
+
const useReanimatedAnimationBuilder = jest
|
|
20
|
+
.spyOn(UseReanimatedAnimationBuilder, 'useReanimatedAnimationBuilder')
|
|
21
|
+
.mockReturnValue({ exiting: 'exiting', entering: 'entering' } as any);
|
|
22
|
+
|
|
23
|
+
const { getByTestId } = render(
|
|
24
|
+
<AnimatedContainer testID="test-id" from={from} to={to} />,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(useReanimatedAnimationBuilder).toHaveBeenCalledWith({
|
|
28
|
+
from,
|
|
29
|
+
to,
|
|
30
|
+
duration: 300,
|
|
31
|
+
exit: undefined,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(getByTestId('test-id').props).toEqual({
|
|
35
|
+
children: undefined,
|
|
36
|
+
entering: 'entering',
|
|
37
|
+
exiting: 'exiting',
|
|
38
|
+
style: undefined,
|
|
39
|
+
testID: 'test-id',
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('passes the given animation data to the hook 2', () => {
|
|
44
|
+
const useReanimatedAnimationBuilder = jest
|
|
45
|
+
.spyOn(UseReanimatedAnimationBuilder, 'useReanimatedAnimationBuilder')
|
|
46
|
+
.mockReturnValue({ exiting: 'exiting', entering: 'entering' } as any);
|
|
47
|
+
|
|
48
|
+
const { getByTestId } = render(
|
|
49
|
+
<AnimatedContainer testID="test-id" from={from} to={to} duration={600} />,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(useReanimatedAnimationBuilder).toHaveBeenCalledWith({
|
|
53
|
+
from,
|
|
54
|
+
to,
|
|
55
|
+
duration: 600,
|
|
56
|
+
exit: undefined,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(getByTestId('test-id').props).toEqual({
|
|
60
|
+
children: undefined,
|
|
61
|
+
entering: 'entering',
|
|
62
|
+
exiting: 'exiting',
|
|
63
|
+
style: undefined,
|
|
64
|
+
testID: 'test-id',
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('wraps the content with the AutofocusContainer when the autofocus property is true', () => {
|
|
69
|
+
const { UNSAFE_getAllByType } = render(
|
|
70
|
+
<AnimatedContainer
|
|
71
|
+
testID="test-id"
|
|
72
|
+
from={from}
|
|
73
|
+
to={to}
|
|
74
|
+
duration={300}
|
|
75
|
+
autofocus
|
|
76
|
+
/>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(UNSAFE_getAllByType(AutofocusContainer)).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
jest.mock('../hooks/useReanimatedAnimationBuilder');
|
|
84
|
+
jest.mock('../hooks/useFocus', () => {
|
|
85
|
+
return {
|
|
86
|
+
useFocus: () => {
|
|
87
|
+
return {
|
|
88
|
+
setFocus: jest.fn(),
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
jest.mock('react-native-reanimated', () => {
|
|
95
|
+
const { View } = require('react-native');
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
View: (props: any) => <View {...props} />,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { AutofocusContainer } from '@react-native-ama/core';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import type { PropsWithChildren } from 'react';
|
|
4
|
+
import type { ViewProps } from 'react-native';
|
|
5
|
+
import Animated, { AnimateProps } from 'react-native-reanimated';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
AnimatedEntryViewStyle,
|
|
9
|
+
AnimatedExitViewStyle,
|
|
10
|
+
ToAnimation,
|
|
11
|
+
useReanimatedAnimationBuilder,
|
|
12
|
+
} from '../hooks/useReanimatedAnimationBuilder';
|
|
13
|
+
|
|
14
|
+
type UseReanimated = Omit<AnimateProps<ViewProps>, 'entering' | 'exiting'> & {
|
|
15
|
+
autofocus?: boolean;
|
|
16
|
+
duration?: number;
|
|
17
|
+
from: AnimatedEntryViewStyle;
|
|
18
|
+
exitFrom?: AnimatedExitViewStyle;
|
|
19
|
+
exitTo?: ToAnimation;
|
|
20
|
+
to: ToAnimation;
|
|
21
|
+
style?: Pick<ViewProps, 'style'>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const AnimatedContainer = React.forwardRef<
|
|
25
|
+
Animated.View,
|
|
26
|
+
PropsWithChildren<UseReanimated>
|
|
27
|
+
>(
|
|
28
|
+
(
|
|
29
|
+
{
|
|
30
|
+
from,
|
|
31
|
+
to,
|
|
32
|
+
exitFrom,
|
|
33
|
+
exitTo,
|
|
34
|
+
duration = 300,
|
|
35
|
+
style,
|
|
36
|
+
autofocus,
|
|
37
|
+
children,
|
|
38
|
+
...rest
|
|
39
|
+
},
|
|
40
|
+
forwardRef,
|
|
41
|
+
) => {
|
|
42
|
+
const { entering, exiting } = useReanimatedAnimationBuilder({
|
|
43
|
+
from,
|
|
44
|
+
to,
|
|
45
|
+
exitFrom,
|
|
46
|
+
exitTo,
|
|
47
|
+
duration,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const Wrapper = autofocus ? AutofocusContainer : React.Fragment;
|
|
51
|
+
return (
|
|
52
|
+
<Wrapper>
|
|
53
|
+
<Animated.View
|
|
54
|
+
style={style}
|
|
55
|
+
entering={entering}
|
|
56
|
+
exiting={exiting}
|
|
57
|
+
ref={forwardRef}
|
|
58
|
+
{...rest}>
|
|
59
|
+
{children}
|
|
60
|
+
</Animated.View>
|
|
61
|
+
</Wrapper>
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as AMAProvider from '@react-native-ama/core';
|
|
2
|
+
import { renderHook } from '@testing-library/react-native';
|
|
3
|
+
import { Animated } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { useAnimation } from './useAnimation';
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
let timing = jest.spyOn(Animated, 'timing');
|
|
12
|
+
|
|
13
|
+
describe('useAnimation', () => {
|
|
14
|
+
describe('Given isReduceMotionEnabled is false', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
17
|
+
...amaContextValues,
|
|
18
|
+
isReduceMotionEnabled: false,
|
|
19
|
+
} as any);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('Then plays only the main timing', () => {
|
|
23
|
+
const { result } = renderHook(() =>
|
|
24
|
+
useAnimation({
|
|
25
|
+
duration: 300,
|
|
26
|
+
useNativeDriver: false,
|
|
27
|
+
from: {
|
|
28
|
+
left: 0,
|
|
29
|
+
opacity: 0,
|
|
30
|
+
},
|
|
31
|
+
to: {
|
|
32
|
+
left: 100,
|
|
33
|
+
opacity: 1,
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
result.current.play();
|
|
39
|
+
|
|
40
|
+
expect(timing).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(timing).toHaveBeenCalledWith(expect.any(Animated.Value), {
|
|
42
|
+
duration: 300,
|
|
43
|
+
toValue: 1,
|
|
44
|
+
useNativeDriver: false,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Given isReduceMotionEnabled is true', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
52
|
+
...amaContextValues,
|
|
53
|
+
isReduceMotionEnabled: true,
|
|
54
|
+
} as any);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('Then plays the reduceMotion and the main progress', () => {
|
|
58
|
+
const { result } = renderHook(() =>
|
|
59
|
+
useAnimation({
|
|
60
|
+
duration: 300,
|
|
61
|
+
useNativeDriver: false,
|
|
62
|
+
from: {
|
|
63
|
+
left: 0,
|
|
64
|
+
opacity: 0,
|
|
65
|
+
},
|
|
66
|
+
to: {
|
|
67
|
+
left: 100,
|
|
68
|
+
opacity: 1,
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
result.current.play();
|
|
74
|
+
|
|
75
|
+
expect(timing).toHaveBeenCalledTimes(2);
|
|
76
|
+
expect(timing).toHaveBeenNthCalledWith(1, expect.any(Animated.Value), {
|
|
77
|
+
duration: 0,
|
|
78
|
+
toValue: 1,
|
|
79
|
+
useNativeDriver: false,
|
|
80
|
+
});
|
|
81
|
+
expect(timing).toHaveBeenNthCalledWith(2, expect.any(Animated.Value), {
|
|
82
|
+
duration: 300,
|
|
83
|
+
toValue: 1,
|
|
84
|
+
useNativeDriver: false,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('Then plays the main progress with duration 0 when is a motion-only animation', () => {
|
|
89
|
+
const { result } = renderHook(() =>
|
|
90
|
+
useAnimation({
|
|
91
|
+
duration: 300,
|
|
92
|
+
useNativeDriver: false,
|
|
93
|
+
from: {
|
|
94
|
+
left: 0,
|
|
95
|
+
},
|
|
96
|
+
to: {
|
|
97
|
+
left: 100,
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
result.current.play();
|
|
103
|
+
|
|
104
|
+
expect(timing).toHaveBeenCalledTimes(2);
|
|
105
|
+
expect(timing).toHaveBeenNthCalledWith(1, expect.any(Animated.Value), {
|
|
106
|
+
duration: 0,
|
|
107
|
+
toValue: 1,
|
|
108
|
+
useNativeDriver: false,
|
|
109
|
+
});
|
|
110
|
+
expect(timing).toHaveBeenNthCalledWith(2, expect.any(Animated.Value), {
|
|
111
|
+
duration: 0,
|
|
112
|
+
toValue: 1,
|
|
113
|
+
useNativeDriver: false,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('Then plays the main progress with duration 0 when skipIfReduceMotionEnabled is set to true', () => {
|
|
118
|
+
const { result } = renderHook(() =>
|
|
119
|
+
useAnimation({
|
|
120
|
+
duration: 300,
|
|
121
|
+
useNativeDriver: false,
|
|
122
|
+
skipIfReduceMotionEnabled: true,
|
|
123
|
+
from: {
|
|
124
|
+
opacity: 0,
|
|
125
|
+
},
|
|
126
|
+
to: {
|
|
127
|
+
opacity: 100,
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
result.current.play();
|
|
133
|
+
|
|
134
|
+
expect(timing).toHaveBeenCalledTimes(2);
|
|
135
|
+
expect(timing).toHaveBeenNthCalledWith(1, expect.any(Animated.Value), {
|
|
136
|
+
duration: 0,
|
|
137
|
+
toValue: 1,
|
|
138
|
+
useNativeDriver: false,
|
|
139
|
+
});
|
|
140
|
+
expect(timing).toHaveBeenNthCalledWith(2, expect.any(Animated.Value), {
|
|
141
|
+
duration: 0,
|
|
142
|
+
toValue: 1,
|
|
143
|
+
useNativeDriver: false,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const amaContextValues = {
|
|
150
|
+
isReduceTransparencyEnabled: false,
|
|
151
|
+
isBoldTextEnabled: false,
|
|
152
|
+
isGrayscaleEnabled: false,
|
|
153
|
+
isInvertColorsEnabled: false,
|
|
154
|
+
isReduceMotionEnabled: false,
|
|
155
|
+
isScreenReaderEnabled: false,
|
|
156
|
+
reactNavigationScreenOptions: {
|
|
157
|
+
animationEnabled: true,
|
|
158
|
+
animation: 'default',
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
jest.mock('@react-native-ama/core');
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useAMAContext } from '@react-native-ama/core';
|
|
2
|
+
import { interpolateAnimationStates } from '@react-native-ama/internal';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import type { ViewStyle } from 'react-native';
|
|
5
|
+
import { Animated } from 'react-native';
|
|
6
|
+
|
|
7
|
+
type UseAnimation = {
|
|
8
|
+
from: ViewStyle;
|
|
9
|
+
to: ViewStyle;
|
|
10
|
+
duration: number;
|
|
11
|
+
useNativeDriver: boolean;
|
|
12
|
+
skipIfReduceMotionEnabled?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const useAnimation = ({
|
|
16
|
+
duration,
|
|
17
|
+
useNativeDriver,
|
|
18
|
+
from,
|
|
19
|
+
to,
|
|
20
|
+
skipIfReduceMotionEnabled = false,
|
|
21
|
+
}: UseAnimation) => {
|
|
22
|
+
const { isReduceMotionEnabled } = useAMAContext();
|
|
23
|
+
const progress = React.useRef(new Animated.Value(0)).current;
|
|
24
|
+
const reduceMotionProgress = React.useRef(new Animated.Value(0)).current;
|
|
25
|
+
const hasOnlyMotionAnimation = React.useRef(false);
|
|
26
|
+
|
|
27
|
+
const play = (toValue: number = 1) => {
|
|
28
|
+
if (isReduceMotionEnabled) {
|
|
29
|
+
Animated.timing(reduceMotionProgress, {
|
|
30
|
+
duration: 0,
|
|
31
|
+
toValue,
|
|
32
|
+
useNativeDriver,
|
|
33
|
+
}).start();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const animationDuration =
|
|
37
|
+
isReduceMotionEnabled &&
|
|
38
|
+
(hasOnlyMotionAnimation.current || skipIfReduceMotionEnabled)
|
|
39
|
+
? 0
|
|
40
|
+
: duration;
|
|
41
|
+
|
|
42
|
+
return Animated.timing(progress, {
|
|
43
|
+
duration: animationDuration,
|
|
44
|
+
toValue,
|
|
45
|
+
useNativeDriver,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const { __hasOnlyMotionAnimation, ...style } = interpolateAnimationStates(
|
|
50
|
+
from,
|
|
51
|
+
to,
|
|
52
|
+
isReduceMotionEnabled,
|
|
53
|
+
progress,
|
|
54
|
+
reduceMotionProgress,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
hasOnlyMotionAnimation.current = __hasOnlyMotionAnimation;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
animatedStyle: style,
|
|
61
|
+
progress,
|
|
62
|
+
play,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as AMAProvider from '@react-native-ama/core';
|
|
2
|
+
import { renderHook } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { useAnimationDuration } from './useAnimationDuration';
|
|
5
|
+
|
|
6
|
+
describe('useAnimationDuration', () => {
|
|
7
|
+
it('returns the given duration for motion animation when isReduceMotionEnabled is false', () => {
|
|
8
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
9
|
+
...amaContextValues,
|
|
10
|
+
isReduceMotionEnabled: false,
|
|
11
|
+
} as any);
|
|
12
|
+
|
|
13
|
+
const { result } = renderHook(() => useAnimationDuration());
|
|
14
|
+
|
|
15
|
+
expect(result.current.getAnimationDuration('left', 100)).toBe(100);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns 0 for motion animation when isReduceMotionEnabled is true', () => {
|
|
19
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
20
|
+
...amaContextValues,
|
|
21
|
+
isReduceMotionEnabled: true,
|
|
22
|
+
} as any);
|
|
23
|
+
|
|
24
|
+
const { result } = renderHook(() => useAnimationDuration());
|
|
25
|
+
|
|
26
|
+
expect(result.current.getAnimationDuration('left', 100)).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const amaContextValues = {
|
|
31
|
+
isReduceTransparencyEnabled: false,
|
|
32
|
+
isBoldTextEnabled: false,
|
|
33
|
+
isGrayscaleEnabled: false,
|
|
34
|
+
isInvertColorsEnabled: false,
|
|
35
|
+
isReduceMotionEnabled: false,
|
|
36
|
+
isScreenReaderEnabled: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
jest.mock('@react-native-ama/core');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useAMAContext } from '@react-native-ama/core';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { ViewStyle } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { isMotionAnimation } from '../utils/isMotionAnimation';
|
|
6
|
+
|
|
7
|
+
export const useAnimationDuration = () => {
|
|
8
|
+
const { isReduceMotionEnabled } = useAMAContext();
|
|
9
|
+
|
|
10
|
+
const getAnimationDuration = React.useCallback(
|
|
11
|
+
(propertyToAnimate: keyof ViewStyle, duration: number) => {
|
|
12
|
+
return isReduceMotionEnabled && isMotionAnimation(propertyToAnimate)
|
|
13
|
+
? 0
|
|
14
|
+
: duration;
|
|
15
|
+
},
|
|
16
|
+
[isReduceMotionEnabled],
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
getAnimationDuration,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as AMAProvider from '@react-native-ama/core';
|
|
2
|
+
import { renderHook } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { useReanimatedAnimationBuilder } from './useReanimatedAnimationBuilder';
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
let withTiming: jest.Mock;
|
|
11
|
+
|
|
12
|
+
describe('useReanimatedAnimationBuilder', () => {
|
|
13
|
+
describe('entering animation', () => {
|
|
14
|
+
it('handles the custom reanimated params', () => {
|
|
15
|
+
const { result } = renderHook(() =>
|
|
16
|
+
useReanimatedAnimationBuilder({
|
|
17
|
+
from: { transform: [{ translateY: 'targetHeight' }], opacity: 0 },
|
|
18
|
+
to: {
|
|
19
|
+
transform: [{ translateY: -42 }],
|
|
20
|
+
opacity: 1,
|
|
21
|
+
},
|
|
22
|
+
duration: 300,
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(
|
|
27
|
+
result.current.entering({
|
|
28
|
+
targetHeight: 42,
|
|
29
|
+
} as any),
|
|
30
|
+
).toEqual({
|
|
31
|
+
animations: {
|
|
32
|
+
opacity: 1,
|
|
33
|
+
transform: [{ translateY: -42 }],
|
|
34
|
+
},
|
|
35
|
+
initialValues: { opacity: 0, transform: [{ translateY: 42 }] },
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('generates the animation with duration 0 for motion ones', () => {
|
|
40
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
41
|
+
isReduceMotionEnabled: true,
|
|
42
|
+
} as any);
|
|
43
|
+
|
|
44
|
+
const { result } = renderHook(() =>
|
|
45
|
+
useReanimatedAnimationBuilder({
|
|
46
|
+
from: { transform: [{ translateY: 'targetHeight' }], opacity: 0 },
|
|
47
|
+
to: {
|
|
48
|
+
transform: [{ translateY: -42 }],
|
|
49
|
+
opacity: 1,
|
|
50
|
+
},
|
|
51
|
+
duration: 300,
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
result.current.entering({
|
|
56
|
+
targetHeight: 42,
|
|
57
|
+
} as any);
|
|
58
|
+
|
|
59
|
+
expect(withTiming).toHaveBeenCalledTimes(2);
|
|
60
|
+
expect(withTiming).toHaveBeenNthCalledWith(1, -42, {
|
|
61
|
+
duration: 0,
|
|
62
|
+
});
|
|
63
|
+
expect(withTiming).toHaveBeenNthCalledWith(2, 1, {
|
|
64
|
+
duration: 300,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('exiting animation', () => {
|
|
70
|
+
it('reverse the from/to animation', () => {
|
|
71
|
+
const { result } = renderHook(() =>
|
|
72
|
+
useReanimatedAnimationBuilder({
|
|
73
|
+
from: { transform: [{ translateY: 'targetHeight' }], opacity: 0 },
|
|
74
|
+
to: {
|
|
75
|
+
transform: [{ translateY: -42 }],
|
|
76
|
+
opacity: 1,
|
|
77
|
+
},
|
|
78
|
+
duration: 300,
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(
|
|
83
|
+
result.current.exiting({
|
|
84
|
+
targetHeight: 42,
|
|
85
|
+
} as any),
|
|
86
|
+
).toEqual({
|
|
87
|
+
animations: {
|
|
88
|
+
opacity: 0,
|
|
89
|
+
transform: [{ translateY: 42 }],
|
|
90
|
+
},
|
|
91
|
+
initialValues: { opacity: 1, transform: [{ translateY: -42 }] },
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('allows specifying a different start animation', () => {
|
|
96
|
+
const { result } = renderHook(() =>
|
|
97
|
+
useReanimatedAnimationBuilder({
|
|
98
|
+
from: { transform: [{ translateY: 'targetHeight' }], opacity: 0 },
|
|
99
|
+
exitFrom: {
|
|
100
|
+
transform: [{ translateY: 0 }],
|
|
101
|
+
opacity: 0.5,
|
|
102
|
+
},
|
|
103
|
+
to: { transform: [{ translateY: -42 }], opacity: 1 },
|
|
104
|
+
duration: 300,
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(
|
|
109
|
+
result.current.exiting({
|
|
110
|
+
targetHeight: 42,
|
|
111
|
+
} as any),
|
|
112
|
+
).toEqual({
|
|
113
|
+
animations: {
|
|
114
|
+
opacity: 0.5,
|
|
115
|
+
transform: [{ translateY: 0 }],
|
|
116
|
+
},
|
|
117
|
+
initialValues: { opacity: 1, transform: [{ translateY: -42 }] },
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('generates the animation with duration 0 for motion ones', () => {
|
|
122
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
123
|
+
isReduceMotionEnabled: true,
|
|
124
|
+
} as any);
|
|
125
|
+
|
|
126
|
+
const { result } = renderHook(() =>
|
|
127
|
+
useReanimatedAnimationBuilder({
|
|
128
|
+
from: { transform: [{ translateY: 'targetHeight' }], opacity: 0 },
|
|
129
|
+
to: { transform: [{ translateY: -42 }], opacity: 1 },
|
|
130
|
+
duration: 300,
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
result.current.exiting({ targetHeight: 42 } as any);
|
|
135
|
+
|
|
136
|
+
expect(withTiming).toHaveBeenCalledTimes(2);
|
|
137
|
+
expect(withTiming).toHaveBeenNthCalledWith(1, 42, {
|
|
138
|
+
duration: 0,
|
|
139
|
+
});
|
|
140
|
+
expect(withTiming).toHaveBeenNthCalledWith(2, 0, {
|
|
141
|
+
duration: 300,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
function mockReanimated() {
|
|
148
|
+
withTiming = jest.fn(value => value);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
withTiming,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
jest.mock('react-native-reanimated', () => mockReanimated());
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useAMAContext } from '@react-native-ama/core';
|
|
2
|
+
import { MOTION_ANIMATIONS } from '@react-native-ama/internal';
|
|
3
|
+
import type { ViewProps, ViewStyle } from 'react-native';
|
|
4
|
+
import {
|
|
5
|
+
EntryAnimationsValues,
|
|
6
|
+
EntryExitAnimationFunction,
|
|
7
|
+
ExitAnimationsValues,
|
|
8
|
+
StyleProps,
|
|
9
|
+
withTiming,
|
|
10
|
+
} from 'react-native-reanimated';
|
|
11
|
+
|
|
12
|
+
export type ToAnimation = ViewProps['style'];
|
|
13
|
+
|
|
14
|
+
type UseReanimatedAnimationBuilder = {
|
|
15
|
+
from: AnimatedEntryViewStyle;
|
|
16
|
+
to: ToAnimation;
|
|
17
|
+
exitFrom?: AnimatedExitViewStyle;
|
|
18
|
+
exitTo?: ToAnimation;
|
|
19
|
+
duration: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const useReanimatedAnimationBuilder = ({
|
|
23
|
+
from,
|
|
24
|
+
to,
|
|
25
|
+
exitFrom,
|
|
26
|
+
exitTo,
|
|
27
|
+
duration,
|
|
28
|
+
}: UseReanimatedAnimationBuilder) => {
|
|
29
|
+
const { isReduceMotionEnabled } = useAMAContext();
|
|
30
|
+
|
|
31
|
+
const animationBuilder = <InitialValues, FinalValues>(
|
|
32
|
+
initial: InitialValues,
|
|
33
|
+
final: FinalValues,
|
|
34
|
+
): EntryExitAnimationFunction => {
|
|
35
|
+
return (values: EntryAnimationsValues | ExitAnimationsValues) => {
|
|
36
|
+
'worklet';
|
|
37
|
+
|
|
38
|
+
const animations = (function generateAnimations(startValues, endValues) {
|
|
39
|
+
return Object.keys(startValues as object).reduce(
|
|
40
|
+
(outputAnimation, key) => {
|
|
41
|
+
const k: keyof InitialValues = key as keyof InitialValues;
|
|
42
|
+
const toValue = endValues[key as keyof FinalValues];
|
|
43
|
+
const value = startValues[k];
|
|
44
|
+
const mappedToValue =
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
typeof toValue === 'string' ? values[toValue] || 0 : toValue;
|
|
47
|
+
|
|
48
|
+
const realDuration =
|
|
49
|
+
isReduceMotionEnabled &&
|
|
50
|
+
MOTION_ANIMATIONS.includes(key as keyof ViewStyle)
|
|
51
|
+
? 0
|
|
52
|
+
: duration;
|
|
53
|
+
const newValue = Array.isArray(value)
|
|
54
|
+
? value.map((item, index) =>
|
|
55
|
+
generateAnimations(
|
|
56
|
+
item as InitialValues,
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
endValues?.[k]?.[index],
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
: withTiming(mappedToValue, {
|
|
62
|
+
duration: realDuration,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
outputAnimation[k] = newValue;
|
|
66
|
+
|
|
67
|
+
return outputAnimation;
|
|
68
|
+
},
|
|
69
|
+
{} as InitialValues,
|
|
70
|
+
);
|
|
71
|
+
})(initial, final);
|
|
72
|
+
|
|
73
|
+
const initialValues = (function generateInitialValues(
|
|
74
|
+
startValues,
|
|
75
|
+
): StyleProps {
|
|
76
|
+
return Object.entries(startValues as object).reduce(
|
|
77
|
+
(newList, [key, value]) => {
|
|
78
|
+
const mappedValue =
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
typeof value === 'string' ? values[value] : value;
|
|
81
|
+
const newValue = Array.isArray(value)
|
|
82
|
+
? value.map(item => generateInitialValues(item as InitialValues))
|
|
83
|
+
: mappedValue;
|
|
84
|
+
|
|
85
|
+
newList[key as keyof StyleProps] = newValue;
|
|
86
|
+
|
|
87
|
+
return newList;
|
|
88
|
+
},
|
|
89
|
+
{} as StyleProps,
|
|
90
|
+
);
|
|
91
|
+
})(initial);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
initialValues,
|
|
95
|
+
animations,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
entering: animationBuilder<AnimatedEntryViewStyle, ToAnimation>(from, to),
|
|
102
|
+
exiting: animationBuilder<
|
|
103
|
+
ToAnimation,
|
|
104
|
+
AnimatedExitViewStyle | AnimatedEntryViewStyle
|
|
105
|
+
>(exitTo || to, exitFrom || from),
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type AnimatedEntryViewStyle = ReanimatedStyle<ReanimatedEntryValues>;
|
|
110
|
+
export type AnimatedExitViewStyle = ReanimatedStyle<ReanimatedExitValues>;
|
|
111
|
+
|
|
112
|
+
type ReanimatedStyle<K> = {
|
|
113
|
+
[key in keyof Omit<ViewStyle, 'transform'>]: ViewStyle[key] | K;
|
|
114
|
+
} & {
|
|
115
|
+
transform?:
|
|
116
|
+
| (
|
|
117
|
+
| PerpectiveTransform<K>
|
|
118
|
+
| RotateTransform<K>
|
|
119
|
+
| RotateXTransform<K>
|
|
120
|
+
| RotateYTransform<K>
|
|
121
|
+
| RotateZTransform<K>
|
|
122
|
+
| ScaleTransform<K>
|
|
123
|
+
| ScaleXTransform<K>
|
|
124
|
+
| ScaleYTransform<K>
|
|
125
|
+
| TranslateXTransform<K>
|
|
126
|
+
| TranslateYTransform<K>
|
|
127
|
+
| SkewXTransform<K>
|
|
128
|
+
| SkewYTransform<K>
|
|
129
|
+
| MatrixTransform<K>
|
|
130
|
+
)[]
|
|
131
|
+
| undefined;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
type PerpectiveTransform<K> = {
|
|
135
|
+
c: string | K;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type RotateTransform<K> = {
|
|
139
|
+
rotate: string | K;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
type RotateXTransform<K> = {
|
|
143
|
+
rotateX: string | K;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type RotateYTransform<K> = {
|
|
147
|
+
rotateY: string | K;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
type RotateZTransform<K> = {
|
|
151
|
+
rotateZ: string | K;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type ScaleTransform<K> = {
|
|
155
|
+
scale: number | K;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
type ScaleXTransform<K> = {
|
|
159
|
+
scaleX: number | K;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
type ScaleYTransform<K> = {
|
|
163
|
+
scaleY: number | K;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
type TranslateXTransform<K> = {
|
|
167
|
+
translateX: number | K;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
type TranslateYTransform<K> = {
|
|
171
|
+
translateY: number | K;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
type SkewXTransform<K> = {
|
|
175
|
+
skewX: string | K;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
type SkewYTransform<K> = {
|
|
179
|
+
skewY: string | K;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
type MatrixTransform<K> = {
|
|
183
|
+
matrix: number[] | K;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
type ReanimatedEntryValues = keyof EntryAnimationsValues;
|
|
187
|
+
type ReanimatedExitValues = keyof ExitAnimationsValues;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as AMAProvider from '@react-native-ama/core';
|
|
2
|
+
import { MOTION_ANIMATIONS } from '@react-native-ama/internal';
|
|
3
|
+
import { renderHook } from '@testing-library/react-native';
|
|
4
|
+
|
|
5
|
+
import { useReanimatedTiming } from './useReanimatedTiming';
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('useReanimatedTiming', () => {
|
|
12
|
+
describe('withTiming', () => {
|
|
13
|
+
it('calls reanimated withTiming function with the give parameters when isReduceMotionEnabled is false', () => {
|
|
14
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
15
|
+
...amaContextValues,
|
|
16
|
+
isReduceMotionEnabled: false,
|
|
17
|
+
} as any);
|
|
18
|
+
|
|
19
|
+
const { result } = renderHook(() => useReanimatedTiming());
|
|
20
|
+
|
|
21
|
+
result.current.withTiming('left', 42, { duration: 100 });
|
|
22
|
+
expect(withTiming).toHaveBeenCalledWith(42, { duration: 100 }, undefined);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it.each(MOTION_ANIMATIONS)(
|
|
26
|
+
'calls reanimated withTiming function with duration = 0 for motion animation when isReduceMotionEnabled is true',
|
|
27
|
+
key => {
|
|
28
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
29
|
+
...amaContextValues,
|
|
30
|
+
isReduceMotionEnabled: true,
|
|
31
|
+
} as any);
|
|
32
|
+
|
|
33
|
+
const { result } = renderHook(() => useReanimatedTiming());
|
|
34
|
+
|
|
35
|
+
result.current.withTiming(key as any, 42, { duration: 100 });
|
|
36
|
+
expect(withTiming).toHaveBeenCalledWith(42, { duration: 0 }, undefined);
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('withSpring', () => {
|
|
42
|
+
it('calls reanimated withSpring function with the give parameters when isReduceMotionEnabled is false', () => {
|
|
43
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
44
|
+
...amaContextValues,
|
|
45
|
+
isReduceMotionEnabled: false,
|
|
46
|
+
} as any);
|
|
47
|
+
|
|
48
|
+
const { result } = renderHook(() => useReanimatedTiming());
|
|
49
|
+
|
|
50
|
+
result.current.withSpring('left', 42, { damping: 42 });
|
|
51
|
+
expect(withSpring).toHaveBeenCalledWith(42, { damping: 42 }, undefined);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it.each(MOTION_ANIMATIONS)(
|
|
55
|
+
'calls reanimated withTiming function with duration = 0 for motion animation when isReduceMotionEnabled is true',
|
|
56
|
+
key => {
|
|
57
|
+
jest.spyOn(AMAProvider, 'useAMAContext').mockReturnValue({
|
|
58
|
+
...amaContextValues,
|
|
59
|
+
isReduceMotionEnabled: true,
|
|
60
|
+
} as any);
|
|
61
|
+
|
|
62
|
+
const { result } = renderHook(() => useReanimatedTiming());
|
|
63
|
+
|
|
64
|
+
result.current.withSpring(key as any, 42);
|
|
65
|
+
|
|
66
|
+
expect(withSpring).not.toHaveBeenCalled();
|
|
67
|
+
expect(withTiming).toHaveBeenCalledWith(42, { duration: 0 });
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const amaContextValues = {
|
|
74
|
+
isReduceTransparencyEnabled: false,
|
|
75
|
+
isBoldTextEnabled: false,
|
|
76
|
+
isGrayscaleEnabled: false,
|
|
77
|
+
isInvertColorsEnabled: false,
|
|
78
|
+
isReduceMotionEnabled: false,
|
|
79
|
+
isScreenReaderEnabled: false,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let withSpring: jest.Mock;
|
|
83
|
+
let withTiming: jest.Mock;
|
|
84
|
+
|
|
85
|
+
function mockReanimated() {
|
|
86
|
+
withSpring = jest.fn();
|
|
87
|
+
withTiming = jest.fn();
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
withSpring,
|
|
91
|
+
withTiming,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
jest.mock('@react-native-ama/core');
|
|
96
|
+
jest.mock('react-native-reanimated', () => mockReanimated());
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useAMAContext } from '@react-native-ama/core';
|
|
2
|
+
import type { MotionAnimationKey } from '@react-native-ama/internal';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
AnimationCallback,
|
|
6
|
+
withSpring as ReanimateWithSpring,
|
|
7
|
+
withTiming as ReanimateWithTiming,
|
|
8
|
+
WithSpringConfig,
|
|
9
|
+
WithTimingConfig,
|
|
10
|
+
} from 'react-native-reanimated';
|
|
11
|
+
|
|
12
|
+
import { isMotionAnimation } from '../utils/isMotionAnimation';
|
|
13
|
+
|
|
14
|
+
export const useReanimatedTiming = () => {
|
|
15
|
+
const { isReduceMotionEnabled } = useAMAContext();
|
|
16
|
+
|
|
17
|
+
const withTiming = React.useCallback(
|
|
18
|
+
(
|
|
19
|
+
propertyKey: MotionAnimationKey,
|
|
20
|
+
toValue: number,
|
|
21
|
+
config: WithTimingConfig = {},
|
|
22
|
+
callback?: AnimationCallback,
|
|
23
|
+
) => {
|
|
24
|
+
if (isReduceMotionEnabled && isMotionAnimation(propertyKey)) {
|
|
25
|
+
config.duration = 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return ReanimateWithTiming(toValue, config, callback);
|
|
29
|
+
},
|
|
30
|
+
[isReduceMotionEnabled],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const withSpring = React.useCallback(
|
|
34
|
+
(
|
|
35
|
+
propertyKey: MotionAnimationKey,
|
|
36
|
+
toValue: number,
|
|
37
|
+
config?: WithSpringConfig,
|
|
38
|
+
callback?: AnimationCallback,
|
|
39
|
+
) => {
|
|
40
|
+
if (isReduceMotionEnabled && isMotionAnimation(propertyKey)) {
|
|
41
|
+
return ReanimateWithTiming(toValue, { duration: 0 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return ReanimateWithSpring(toValue, config, callback);
|
|
45
|
+
},
|
|
46
|
+
[isReduceMotionEnabled],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
withTiming,
|
|
51
|
+
withSpring,
|
|
52
|
+
};
|
|
53
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { AnimatedContainer } from './components/AnimatedContainer';
|
|
3
|
+
|
|
4
|
+
// Hooks
|
|
5
|
+
export { useAnimation } from './hooks/useAnimation';
|
|
6
|
+
export { useAnimationDuration } from './hooks/useAnimationDuration';
|
|
7
|
+
export { useReanimatedAnimationBuilder } from './hooks/useReanimatedAnimationBuilder';
|
|
8
|
+
export { useReanimatedTiming } from './hooks/useReanimatedTiming';
|