From 095b8440b24cc5d75d270dd6047994a53e5e0a24 Mon Sep 17 00:00:00 2001 From: Jarmy123 <36279528+Jarmy123@users.noreply.github.com> Date: Tue, 20 Dec 2022 15:20:04 +0100 Subject: [PATCH] feat: new generate animation config (#157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * created new config * Animation config composing * Fixing crash * Remove test style configs, export AnimationBuilder from lib * Add AnimationBuilder class * Add handling of stylesQueue from AnimationBuilder in useAnimationAPI * Update todos * Animation Builder is working * fixed crash without new config * add test for one animation * fixed animations bug and tests * Remaining animations, cleaning, tests * Animation builder tests * Docs update * fix: fixed all eslint/typescript errors and warnings * fix: deleted unneccessary fn export * chore: deleted comment Co-authored-by: mateki0 Co-authored-by: Daniel Grychtoł --- .../intro/animations/custom-transitions.md | 80 +++- example-expo/App.tsx | 5 +- example/src/screens/AnimationsExamples.tsx | 13 +- package.json | 7 + src/__tests__/animationBuilder.test.ts | 47 +++ src/core/hooks/useAnimationAPI.ts | 50 ++- src/core/utils/generateAnimationConfig.ts | 352 +++++++++++++++++- src/index.tsx | 36 +- src/types.ts | 3 +- src/types/animations.ts | 2 +- 10 files changed, 560 insertions(+), 35 deletions(-) create mode 100644 src/__tests__/animationBuilder.test.ts diff --git a/docs/docs/intro/animations/custom-transitions.md b/docs/docs/intro/animations/custom-transitions.md index ae809432..4885ad80 100644 --- a/docs/docs/intro/animations/custom-transitions.md +++ b/docs/docs/intro/animations/custom-transitions.md @@ -23,9 +23,10 @@ When the notification is about to show up, the value is animated from `0` to `1` Every time a notification is about to be shown, the library renders the UI part wrapped with an `` and applies **animated styles** to it so it knows how it should animate. -The source of these styles comes from the **animation config** that is generated wih the `generateAnimationConfig` function and is used internally by the library to generate the animations. You can also use it to create whatever transition you desire. -To recap, there are *4 properties* that can control the transtion. They are all handled by `generateAnimationConfig` and go as follows: +The source of these styles comes from the **animation config** that is generated with `AnimationBuilder` class or `generateAnimationConfig` function and is used internally by the library to generate the animations. You can also use it yourself to create whatever transition you desiere. + +Summarizing, there are _4 properties_ that can controll the transtion. They all are handled by `AnimationBuilder` or `generateAnimationConfig` and go as follows: - `animationConfigIn` - spring / timing configuration for transition in. **REQUIRED** - `animationConfigOut` - spring / timing configuration for transition out. **Not required**, fallbacks to `animationConfigIn` when not provided @@ -34,13 +35,85 @@ To recap, there are *4 properties* that can control the transtion. They are all The return type of this function (`generateAnimationConfig`) is `CustomAnimationConfig` which you can then use when changing the animation types in, e.g., `createNotification` or `notify` call. +### Generating transition config with `AnimationBuilder` + +The `AnimationBuilder` takes in a config object as a property with which you can define the animation. + +Below code snippets should give an idea how it works: + +**Example 1** + +```typescript +export const MoveDown = new AnimationBuilder({ + animationConfigIn: { + type: 'timing', + config: { + duration: 400, + }, + }, + transitionInStyles: (progress) => { + 'worklet' + const translateY = interpolate(progress.value, [0, 1], [-100, 0]) + return { + opacity: progress.value, + transform: [{ translateY }], + } + }, + transitionOutStyles: (progress) => { + 'worklet' + const translateY = interpolate(progress.value, [0, 1], [100, 0]) + return { + opacity: progress.value, + transform: [{ translateY }], + } + }, +}) +``` + +**Example 2** + +```typescript +export const RotateIn = generateAnimationConfig({ + animationConfigIn: { + type: 'timing', + config: { + duration: 700, + easing: Easing.out(Easing.exp), + }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const rotate = interpolate(progress.value, [0, 1], [-360, 0]) + + return { + transform: [{ rotate: `${rotate}deg` }, { scale: progress.value }], + opacity: progress.value, + } + }, +}) +``` + +**Example 3** + +```typescript +export const MoveDownRotateIn = MoveDown.add(RotateIn) +``` + ### Generating transition config with `generateAnimationConfig` +:::caution + +generateAnimation config is `deprecated`. Please use Animation builder which allows your animations to be more customizable. + +::: + The `generateAnimationConfig` takes in a config object as a property with which you can define the animation. The code snippets below should give an idea how it works: **Example 1** + ```typescript export const Example1 = generateAnimationConfig({ animationConfigIn: { @@ -65,12 +138,13 @@ export const Example1 = generateAnimationConfig({ ``` **Example 2** + ```typescript export const Example2 = generateAnimationConfig({ animationConfigIn: { type: 'timing', config: { - duration: 300 + duration: 300, }, }, animationConfigOut: { diff --git a/example-expo/App.tsx b/example-expo/App.tsx index 18cd0f33..7d9e6ff5 100644 --- a/example-expo/App.tsx +++ b/example-expo/App.tsx @@ -10,7 +10,7 @@ const { NotificationsProvider } = createNotifications({ export default function App() { return ( - + @@ -20,6 +20,9 @@ export default function App() { } const styles = StyleSheet.create({ + gestureHandlerWrapper: { + flex: 1, + }, container: { flex: 1, backgroundColor: '#fff', diff --git a/example/src/screens/AnimationsExamples.tsx b/example/src/screens/AnimationsExamples.tsx index 133bbc83..4065d706 100644 --- a/example/src/screens/AnimationsExamples.tsx +++ b/example/src/screens/AnimationsExamples.tsx @@ -2,7 +2,6 @@ import React from 'react' import { SafeAreaView } from 'react-native' import { createNotifications, - RotateInRotateOut, RotateZIn, SlideInLeftSlideOutRight, ZoomInDownZoomOutDown, @@ -99,17 +98,7 @@ export const AnimationsExamples = () => { } buttonText="Zoom In Bounce" /> - - notify('success', { - params: { - ...baseNotifyConfig, - }, - config: { animationConfig: RotateInRotateOut }, - }) - } - buttonText="Rotate In" - /> + notify('success', { diff --git a/package.json b/package.json index c0f7baca..7254e13c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ ], "scripts": { "test": "jest", + "test:watch": "jest --watch --detectOpenHandles", "typescript": "tsc --noEmit", "lint": "eslint \"**/*.{js,ts,tsx}\"", "prepare": "bob build", @@ -83,6 +84,12 @@ "modulePathIgnorePatterns": [ "/example/node_modules", "/lib/" + ], + "testPathIgnorePatterns": [ + "/node_modules/" + ], + "transformIgnorePatterns": [ + "node_modules/(?!(jest-)?@?react-native|react-native|@react-navigation|@react-native-community)" ] }, "commitlint": { diff --git a/src/__tests__/animationBuilder.test.ts b/src/__tests__/animationBuilder.test.ts new file mode 100644 index 00000000..5b6ef86c --- /dev/null +++ b/src/__tests__/animationBuilder.test.ts @@ -0,0 +1,47 @@ +import { mergeStylesFunctions } from 'src/core/hooks/useAnimationAPI' +import { + AnimationBuilder, + FadeIn, + MoveDownAnimation, + MoveUpAnimation, + RotateZIn, + SlideInLeftAnimation, + CrazyAnimationConfig, +} from 'src/core/utils/generateAnimationConfig' + +describe('props merger tests', function () { + it('should make an instance from class', () => { + const MoveDown = new AnimationBuilder(MoveDownAnimation) + + expect(MoveDown).toMatchObject(MoveDownAnimation) + }) + + it('should merge styles properly with add method', () => { + const MoveDown = new AnimationBuilder(MoveDownAnimation) + const MoveDownFadeIn = FadeIn.add(MoveDown).transitionInStylesQueue + const styles = mergeStylesFunctions(MoveDownFadeIn, { value: 1 }) + + expect(styles).toMatchObject({ + opacity: 1, + transform: [{ translateY: 0 }, { translateX: 0 }, { translateX: 0 }, { translateY: 0 }], + }) + }) + it('should merge animationConfigIn properly with add method outstyles', () => { + const SlideInLeft = new AnimationBuilder(SlideInLeftAnimation) + const SlideInLeftCrazy = SlideInLeft.add(CrazyAnimationConfig).animationConfigIn + + const configIn = CrazyAnimationConfig.animationConfigIn + + expect(SlideInLeftCrazy).toMatchObject(configIn) + }) + it('should merge styles properly with add method outstyles', () => { + const MoveUp = new AnimationBuilder(MoveUpAnimation) + const MoveUpRotateZIn = RotateZIn.add(MoveUp).transitionOutStylesQueue + const styles = mergeStylesFunctions(MoveUpRotateZIn, { value: 1 }) + + expect(styles).toMatchObject({ + opacity: 1, + transform: [{ translateY: 0 }, { translateX: 0 }, { translateY: 0 }], + }) + }) +}) diff --git a/src/core/hooks/useAnimationAPI.ts b/src/core/hooks/useAnimationAPI.ts index 6ead0291..0ddaabbb 100644 --- a/src/core/hooks/useAnimationAPI.ts +++ b/src/core/hooks/useAnimationAPI.ts @@ -1,17 +1,50 @@ import { useCallback } from 'react' +import type { ViewStyle, TextStyle, ImageStyle } from 'react-native' import { cancelAnimation, + SharedValue, useAnimatedStyle, useSharedValue, withSpring, withTiming, + AnimateStyle, } from 'react-native-reanimated' import { useDrag } from './useDrag' import type { NotificationState } from './useNotificationsStates' import { emitter } from '../services/NotificationEmitter' import { withAnimationCallbackJSThread } from '../utils/animation' -import { AnimationRange } from '../../types/animations' +import { AnimationRange, TransitionStylesConfigFunction } from '../../types/animations' import { useTimer } from './useTimer' +import type { AnimationBuilder } from '../utils/generateAnimationConfig' + +type Styles = AnimateStyle + +const mergeStylesObjects = (styles: Styles, newStyles: Styles) => { + 'worklet' + + const oldTransform = [...(styles?.transform || [])] + const newTransform = [...(newStyles?.transform || [])] + + return { + ...styles, + ...newStyles, + transform: [...oldTransform, ...newTransform], + } +} + +export const mergeStylesFunctions = ( + stylesFunctions: TransitionStylesConfigFunction[], + progress: SharedValue +) => { + 'worklet' + + return stylesFunctions.reduce( + (accumulatedStyles, styleFunction) => { + return mergeStylesObjects(accumulatedStyles, styleFunction(progress) as Styles) + }, + { opacity: 1, transform: [{ translateY: 0 }, { translateX: 0 }] } // it has to have the default opacity value + ) +} export const useAnimationAPI = ({ gestureConfig, @@ -98,13 +131,22 @@ export const useAnimationAPI = ({ const handleDragStateChange = dragStateHandler(dismiss, resetDrag) const animatedStyles = useAnimatedStyle(() => { + const animationBuilder: AnimationBuilder = animationConfig as AnimationBuilder const { transitionInStyles, transitionOutStyles } = animationConfig + if ( + ['out', 'idle_active'].includes(currentTransitionType.value) && + animationBuilder.transitionOutStylesQueue?.length > 0 + ) { + return mergeStylesFunctions(animationBuilder.transitionOutStylesQueue, progress) + } + if (animationBuilder?.transitionInStylesQueue?.length > 0) { + return mergeStylesFunctions(animationBuilder.transitionInStylesQueue, progress) + } if (['out', 'idle_active'].includes(currentTransitionType.value) && transitionOutStyles) { - return transitionOutStyles(progress) + return { opacity: 1, ...(transitionOutStyles(progress) as unknown as {}) } } - - return transitionInStyles(progress) + return { opacity: 1, ...(transitionInStyles(progress) as unknown as {}) } }) return { diff --git a/src/core/utils/generateAnimationConfig.ts b/src/core/utils/generateAnimationConfig.ts index 437555c8..848558f8 100644 --- a/src/core/utils/generateAnimationConfig.ts +++ b/src/core/utils/generateAnimationConfig.ts @@ -1,4 +1,354 @@ -import type { CustomAnimationConfig } from '../../types/animations' +import { Easing, interpolate, SharedValue } from 'react-native-reanimated' +import type { + AnimationTypeConfig, + CustomAnimationConfig, + TransitionStylesConfigFunction, +} from '../../types/animations' + +export class AnimationBuilder { + animationConfigIn: AnimationTypeConfig + animationConfigOut?: AnimationTypeConfig + + transitionInStylesQueue: TransitionStylesConfigFunction[] = [] // stores stacked styles functions after merging animation configs + transitionOutStylesQueue: TransitionStylesConfigFunction[] = [] // stores stacked styles functions after merging animation configs + + transitionInStyles: TransitionStylesConfigFunction // helper field for handling style funcs merging + transitionOutStyles?: TransitionStylesConfigFunction // helper field for handling style funcs merging + + constructor(config: CustomAnimationConfig) { + this.transitionInStylesQueue = [] + this.transitionOutStylesQueue = [] + this.animationConfigIn = config.animationConfigIn + this.animationConfigOut = config.animationConfigOut + + this.transitionInStyles = config.transitionInStyles + + if (config.transitionOutStyles) { + this.transitionOutStyles = config.transitionOutStyles + } + + this.transitionInStylesQueue.push(config.transitionInStyles) + config.transitionOutStyles && this.transitionOutStylesQueue?.push(config.transitionOutStyles) + } + + add(configToPipe: AnimationBuilder) { + this.animationConfigIn = configToPipe.animationConfigIn + + if (configToPipe.animationConfigOut) { + this.animationConfigOut = configToPipe.animationConfigOut + } + + this.transitionInStyles = configToPipe.transitionInStyles + + if (configToPipe.transitionOutStyles) { + this.transitionOutStyles = configToPipe.transitionOutStyles + } + + this.transitionInStylesQueue.push(configToPipe.transitionInStyles) + + configToPipe.transitionOutStyles && + this.transitionOutStylesQueue?.push(configToPipe.transitionOutStyles) + + return this + } +} +const ZoomInAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 1000, + }, + }, + transitionInStyles: (progress: SharedValue) => { + 'worklet' + const scale = interpolate(progress.value, [0, 1], [0.8, 1]) + return { + transform: [{ scale }], + } + }, +} + +export const MoveDownAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 400, + }, + }, + transitionInStyles: (progress) => { + 'worklet' + const translateY = interpolate(progress.value, [0, 1], [-100, 0]) + return { + opacity: progress.value, + transform: [{ translateY }], + } + }, + transitionOutStyles: (progress) => { + 'worklet' + const translateY = interpolate(progress.value, [0, 1], [100, 0]) + return { + opacity: progress.value, + transform: [{ translateY }], + } + }, +} + +export const MoveUpAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 400, + }, + }, + transitionInStyles: (progress) => { + 'worklet' + const translateY = interpolate(progress.value, [0, 1], [-100, 0]) + return { + opacity: progress.value, + transform: [{ translateY }], + } + }, + transitionOutStyles: (progress) => { + 'worklet' + const translateY = interpolate(progress.value, [0, 1], [-100, 0]) + return { + transform: [{ translateY }], + } + }, +} + +const RotateInAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 700, + easing: Easing.out(Easing.exp), + }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const rotate = interpolate(progress.value, [0, 1], [-360, 0]) + + return { + transform: [{ rotate: `${rotate}deg` }, { scale: progress.value }], + opacity: progress.value, + } + }, +} + +export const RotateZInAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 2000, + easing: Easing.out(Easing.exp), + }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const rotateZ = interpolate(progress.value, [0, 1], [-140, 0]) + const rotateX = interpolate(progress.value, [0, 1], [200, 20]) + const rotateY = interpolate(progress.value, [0, 1], [-40, 5]) + + return { + transform: [ + { rotateZ: `${rotateZ}deg` }, + { rotateX: `${rotateX}deg` }, + { rotateY: `${rotateY}deg` }, + { scale: progress.value }, + { perspective: 1500 }, + ], + opacity: progress.value, + } + }, +} + +export const SlideInLeftAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 400, + easing: Easing.inOut(Easing.sin), + }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const translateX = interpolate(progress.value, [0, 1], [-100, 0]) + + return { + transform: [{ translateX }], + } + }, +} + +const SlideInLeftSlideOutRightAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 400, + easing: Easing.inOut(Easing.sin), + }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const translateX = interpolate(progress.value, [0, 1], [-100, 0]) + + return { + opacity: progress.value, + transform: [{ translateX }], + } + }, + transitionOutStyles: (progress) => { + 'worklet' + + const translateX = interpolate(progress.value, [0, 1], [100, 0]) + + return { + opacity: progress.value, + transform: [{ translateX }], + } + }, +} + +const FadeInAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 500, + }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const translateX = 0 + + return { + opacity: progress.value, + transform: [{ translateX }], + } + }, + transitionOutStyles: (progress) => { + 'worklet' + + const translateX = 0 + + return { + opacity: progress.value, + transform: [{ translateX }], + } + }, +} + +export const CrazyAnimationConfigAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'spring', + config: { damping: 10, velocity: 20, stiffness: 80, mass: 1.2 }, + }, + animationConfigOut: { + type: 'timing', + config: { duration: 200 }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const interpolatedTrans = interpolate(progress.value, [0, 1], [0, 100]) + + return { + transform: [{ translateY: 500 }, { translateX: interpolatedTrans }], + opacity: progress.value, + } + }, + transitionOutStyles: (progress) => { + 'worklet' + + const interpolatedTrans = interpolate(progress.value, [0, 1], [0, 500]) + + return { + transform: [{ translateY: interpolatedTrans }], + opacity: progress.value, + } + }, +} + +const VeryCustomTransitionAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'spring', + config: { damping: 10, velocity: 20, stiffness: 80, mass: 1.2 }, + }, + animationConfigOut: { + type: 'timing', + config: { duration: 200 }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const translateY = interpolate(progress.value, [0, 1], [0, 100]) + + return { + transform: [{ translateY }], + opacity: progress.value, + } + }, + transitionOutStyles: (progress) => { + 'worklet' + + const translateY = interpolate(progress.value, [0, 1], [500, 100]) + + return { + transform: [{ translateY }], + opacity: progress.value, + } + }, +} + +const DiagonalSlideInLeftSlideOutRightAnimation: CustomAnimationConfig = { + animationConfigIn: { + type: 'timing', + config: { + duration: 700, + }, + }, + transitionInStyles: (progress) => { + 'worklet' + + const translateX = interpolate(progress.value, [0, 1], [-100, 0]) + const translateY = interpolate(progress.value, [0, 1], [-100, 0]) + return { + transform: [{ translateX }, { translateY }], + opacity: progress.value, + } + }, + transitionOutStyles: (progress) => { + 'worklet' + + const translateX = interpolate(progress.value, [0, 1], [100, 0]) + const translateY = interpolate(progress.value, [0, 1], [-100, 0]) + return { + transform: [{ translateX }, { translateY }], + opacity: progress.value, + } + }, +} + +export const MoveDown = new AnimationBuilder(MoveDownAnimation) +export const MoveUp = new AnimationBuilder(MoveUpAnimation) +export const ZoomIn = new AnimationBuilder(ZoomInAnimation) +export const SlideInLeft = new AnimationBuilder(SlideInLeftAnimation) +export const SlideInLeftSlideOutRight = new AnimationBuilder(SlideInLeftSlideOutRightAnimation) +export const RotateIn = new AnimationBuilder(RotateInAnimation) +export const RotateZIn = new AnimationBuilder(RotateZInAnimation) +export const FadeIn = new AnimationBuilder(FadeInAnimation) +export const CrazyAnimationConfig = new AnimationBuilder(CrazyAnimationConfigAnimation) +export const VeryCustomTransition = new AnimationBuilder(VeryCustomTransitionAnimation) +export const DiagonalSlideInLeftSlideOutRight = new AnimationBuilder( + DiagonalSlideInLeftSlideOutRightAnimation +) +export const ZoomInDownZoomOutDown = new AnimationBuilder(ZoomInAnimation).add(MoveDown) +export const ZoomInDownZoomOutUp = new AnimationBuilder(ZoomIn).add(MoveUp) export const generateAnimationConfig = (config: CustomAnimationConfig): CustomAnimationConfig => { return config diff --git a/src/index.tsx b/src/index.tsx index f496c3d7..01e53cad 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,18 +1,24 @@ import { createNotifications } from './core/createNotifications' import { modify, notify, remove, useNotifications } from './core/services/NotificationEmitterApi' -import { generateAnimationConfig } from './core/utils/generateAnimationConfig' +import { generateAnimationConfig, AnimationBuilder } from './core/utils/generateAnimationConfig' + +import { defaultVariants } from './defaultConfig/defaultConfig' +import { useNotificationController } from './hooks/useNotificationController' +import type { CustomVariants } from './types' import { - RotateInRotateOut, - RotateZIn, SlideInLeftSlideOutRight, - ZoomInDownZoomOutDown, + ZoomIn, + MoveUp, ZoomInDownZoomOutUp, - FadeInFadeOut, + ZoomInDownZoomOutDown, + MoveDown, + SlideInLeft, + RotateZIn, + FadeIn, + RotateIn, + VeryCustomTransition, DiagonalSlideInLeftSlideOutRight, -} from './defaultConfig/defaultAnimationConfig' -import { defaultVariants } from './defaultConfig/defaultConfig' -import { useNotificationController } from './hooks/useNotificationController' -import type { CustomVariants } from './types' +} from './core/utils/generateAnimationConfig' // default export { defaultVariants } @@ -20,12 +26,18 @@ export { defaultVariants } // animations export { generateAnimationConfig, + AnimationBuilder, + ZoomIn, + ZoomInDownZoomOutDown, + MoveDown, ZoomInDownZoomOutUp, + MoveUp, + SlideInLeft, SlideInLeftSlideOutRight, - ZoomInDownZoomOutDown, - RotateInRotateOut, RotateZIn, - FadeInFadeOut, + FadeIn, + RotateIn, + VeryCustomTransition, DiagonalSlideInLeftSlideOutRight, } diff --git a/src/types.ts b/src/types.ts index 50436d5a..abf67d0e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import type { DefaultLayoutConfig, DefaultVariants } from './defaultConfig/types import type { NotificationPosition } from './types/config' import type { GestureConfig } from './types/gestures' import type { ComponentProps, VFC } from 'react' +import type { AnimationBuilder } from './core/utils/generateAnimationConfig' export type RequiredProps>> = ComponentProps @@ -22,7 +23,7 @@ export type Variants = CustomVariants[keyof CustomVariants] extends never export type NotificationConfigBase = { duration: number notificationPosition: NotificationPosition - animationConfig: CustomAnimationConfig + animationConfig: AnimationBuilder | CustomAnimationConfig gestureConfig: GestureConfig isNotch?: boolean onClose?: () => void diff --git a/src/types/animations.ts b/src/types/animations.ts index 168a48c7..e282b5c2 100644 --- a/src/types/animations.ts +++ b/src/types/animations.ts @@ -6,7 +6,7 @@ import type { WithTimingConfig, } from 'react-native-reanimated' -type TransitionStylesConfigFunction = ( +export type TransitionStylesConfigFunction = ( progress: SharedValue ) => AnimatedStyleProp