From de127e61e6274567a14bfe0fead8a59a50f06e86 Mon Sep 17 00:00:00 2001 From: Karel Suchomel Date: Wed, 6 Nov 2024 20:18:00 +0100 Subject: [PATCH 1/2] feat: add all animation controls under animationConfig prop --- src/components/Toast.tsx | 49 ++++++++++++++++++++++++++++------------ src/core/types.ts | 8 +++++-- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index 2111605..d287361 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -10,11 +10,15 @@ import { ViewStyle, } from 'react-native'; import Animated, { + Easing, + ReduceMotion, runOnJS, useAnimatedStyle, useSharedValue, withSpring, + type WithSpringConfig, withTiming, + type WithTimingConfig, } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { @@ -114,13 +118,36 @@ export const Toast: FC = ({ const setPosition = useCallback(() => { //control the position of the toast when rendering //based on offset, visibility, keyboard, and toast height + let timingConfig: WithTimingConfig = { + duration: 300, + }; + let springConfig: WithSpringConfig = { + stiffness: 80, + }; + if (toast.animationConfig) { + const { + duration = 300, + easing = Easing.inOut(Easing.quad), + reduceMotion = ReduceMotion.System, + ...spring + } = toast.animationConfig; + timingConfig = { + duration, + easing, + reduceMotion, + }; + springConfig = spring; + } + if (toast.position === ToastPosition.TOP) { - offsetY.value = withTiming(toast.visible ? offset : startingY, { - duration: toast?.animationConfig?.duration ?? 300, - }); - position.value = withTiming(toast.visible ? offset : startingY, { - duration: toast?.animationConfig?.duration ?? 300, - }); + offsetY.value = withTiming( + toast.visible ? offset : startingY, + timingConfig + ); + position.value = withTiming( + toast.visible ? offset : startingY, + timingConfig + ); } else { let kbHeight = keyboardVisible ? keyboardHeight : 0; const val = toast.visible @@ -133,15 +160,9 @@ export const Toast: FC = ({ 24 : startingY; - offsetY.value = withSpring(val, { - stiffness: toast?.animationConfig?.stiffness ?? 80, - ...(toast?.animationConfig ?? {}), - }); + offsetY.value = withSpring(val, springConfig); - position.value = withSpring(val, { - stiffness: toast?.animationConfig?.stiffness ?? 80, - ...(toast?.animationConfig ?? {}), - }); + position.value = withSpring(val, springConfig); } }, [ offset, diff --git a/src/core/types.ts b/src/core/types.ts index d2d0ada..bc6d0d6 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,5 +1,8 @@ import type { TextStyle, ViewStyle } from 'react-native'; -import type { SpringConfig } from 'react-native-reanimated/lib/typescript/reanimated2/animation/springUtils'; +import type { + WithTimingConfig, + WithSpringConfig, +} from 'react-native-reanimated'; export type ToastType = 'success' | 'error' | 'loading' | 'blank'; export enum ToastPosition { @@ -54,7 +57,8 @@ export interface Toast { isSwipeable?: boolean; animationConfig?: { flingPositionReturnDuration?: number; - } & SpringConfig; + } & WithSpringConfig & + WithTimingConfig; } export type ToastOptions = Partial< From 9afd9c78c8b9cd9f5b50fd2d830ac011de867135 Mon Sep 17 00:00:00 2001 From: Nick DeBaise Date: Tue, 12 Nov 2024 15:10:25 -0500 Subject: [PATCH 2/2] fix: update to allow for more granular customizability --- README.md | 62 +++++++++++++++++++++++++++++++++++++++ src/components/Toast.tsx | 44 +++++++++++++-------------- src/components/Toasts.tsx | 16 ++++++++-- src/core/toast.ts | 2 ++ src/core/types.ts | 18 +++++++----- src/index.tsx | 2 +- 6 files changed, 110 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 6dc8e31..b087ed9 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,11 @@ toast('Hello World', { text: TextStyle, indicator: ViewStyle }, + animationType: 'timing' | 'spring', + animationConfig: { + flingPositionReturnDuration: number, + ...(springConfig | timingConfig) + }, }); ``` @@ -402,6 +407,63 @@ Every type has its own duration. You can overwrite them `duration` with the toas
+### Animation Options +You can now control the animation type and configuration for toasts. + +#### Props + +- **animationType** (`'spring' | 'timing'`, optional): Choose the animation type for toast appearance. By default, toasts positioned at the bottom use spring, and those at the top use timing. +- **animationConfig** (object, optional): Customize the animation configuration for spring or timing. + +#### Example Usage + +```javascript +import { toast } from 'react-native-toast'; + +// Show a toast with custom animation settings +toast.show('This is a toast message', { + animationType: 'spring', + animationConfig: { + duration: 500, + stiffness: 100, + }, + position: 'top', +}); +```` + +### Global Animation Configuration/Type + +You can define a `globalAnimationType` and a `globalAnimationConfig` that sets the default animation configuration for all toasts. If an individual toast specifies its own `animationConfig`, it will override this global setting. + +#### Props + +- **globalAnimationConfig** (object, optional): Provides a default configuration for toast animations using either spring or timing options. + +#### Example Usage + +```javascript +import { Toasts } from 'react-native-toast'; + +// In your component + + +// Or when showing a toast +toast.show('This is a toast message', { + position: 'bottom', + animationType: 'spring', + animationConfig: { + duration: 400, + damping: 10, + }, +}); +``` + ### Dismiss toast programmatically diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index d287361..d3fe565 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -116,14 +116,9 @@ export const Toast: FC = ({ }, []); const setPosition = useCallback(() => { - //control the position of the toast when rendering - //based on offset, visibility, keyboard, and toast height - let timingConfig: WithTimingConfig = { - duration: 300, - }; - let springConfig: WithSpringConfig = { - stiffness: 80, - }; + let timingConfig: WithTimingConfig = { duration: 300 }; + let springConfig: WithSpringConfig = { stiffness: 80 }; + if (toast.animationConfig) { const { duration = 300, @@ -131,22 +126,22 @@ export const Toast: FC = ({ reduceMotion = ReduceMotion.System, ...spring } = toast.animationConfig; - timingConfig = { - duration, - easing, - reduceMotion, - }; + timingConfig = { duration, easing, reduceMotion }; springConfig = spring; } + const useSpringAnimation = toast.animationType === 'spring'; + + const animation = useSpringAnimation ? withSpring : withTiming; + if (toast.position === ToastPosition.TOP) { - offsetY.value = withTiming( + offsetY.value = animation( toast.visible ? offset : startingY, - timingConfig + useSpringAnimation ? springConfig : timingConfig ); - position.value = withTiming( + position.value = animation( toast.visible ? offset : startingY, - timingConfig + useSpringAnimation ? springConfig : timingConfig ); } else { let kbHeight = keyboardVisible ? keyboardHeight : 0; @@ -160,9 +155,14 @@ export const Toast: FC = ({ 24 : startingY; - offsetY.value = withSpring(val, springConfig); - - position.value = withSpring(val, springConfig); + offsetY.value = animation( + val, + useSpringAnimation ? springConfig : timingConfig + ); + position.value = animation( + val, + useSpringAnimation ? springConfig : timingConfig + ); } }, [ offset, @@ -177,6 +177,7 @@ export const Toast: FC = ({ offsetY, extraInsets, toast.animationConfig, + toast.animationType, ]); const composedGesture = useMemo(() => { @@ -215,19 +216,16 @@ export const Toast: FC = ({ ]); useEffect(() => { - //set the toast height if it updates while rendered setToastHeight(toast?.height ? toast.height : DEFAULT_TOAST_HEIGHT); }, [toast.height]); useEffect(() => { - //set the toast width if it updates while rendered setToastWidth( toast?.width ? toast.width : width - 32 > 360 ? 360 : width - 32 ); }, [toast.width, width]); useEffect(() => { - //Control visibility of toast when rendering opacity.value = withTiming(toast.visible ? 1 : 0, { duration: toast?.animationConfig?.duration ?? 300, }); diff --git a/src/components/Toasts.tsx b/src/components/Toasts.tsx index 6ff719d..6396f7f 100644 --- a/src/components/Toasts.tsx +++ b/src/components/Toasts.tsx @@ -4,7 +4,11 @@ import { TextStyle, View, ViewStyle } from 'react-native'; import { Toast as T, useToaster } from '../headless'; import { Toast } from './Toast'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { ExtraInsets } from '../core/types'; +import { + ExtraInsets, + ToastAnimationConfig, + ToastAnimationType, +} from '../core/types'; import { useScreenReader } from 'src/core/utils'; type Props = { @@ -21,6 +25,8 @@ type Props = { text?: TextStyle; indicator?: ViewStyle; }; + globalAnimationType?: ToastAnimationType; + globalAnimationConfig?: ToastAnimationConfig; }; export const Toasts: FunctionComponent = ({ @@ -32,6 +38,8 @@ export const Toasts: FunctionComponent = ({ providerKey = 'DEFAULT', preventScreenReaderFromHiding, defaultStyle, + globalAnimationType, + globalAnimationConfig, }) => { const { toasts, handlers } = useToaster({ providerKey }); const { startPause, endPause } = handlers; @@ -56,7 +64,11 @@ export const Toasts: FunctionComponent = ({ {toasts.map((t) => ( = (arg: TArg) => TValue; export type ValueOrFunction = @@ -55,10 +58,8 @@ export interface Toast { customToast?: (toast: Toast) => JSX.Element; providerKey: string; isSwipeable?: boolean; - animationConfig?: { - flingPositionReturnDuration?: number; - } & WithSpringConfig & - WithTimingConfig; + animationType?: ToastAnimationType; + animationConfig?: ToastAnimationConfig; } export type ToastOptions = Partial< @@ -77,6 +78,7 @@ export type ToastOptions = Partial< | 'providerKey' | 'isSwipeable' | 'animationConfig' + | 'animationType' > >; diff --git a/src/index.tsx b/src/index.tsx index cc5b159..e48b3cf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ export { useToaster } from './core/use-toaster'; export { Toasts } from './components'; export * from './headless'; -export { ToastPosition } from './core/types'; +export { ToastPosition, ToastAnimationType } from './core/types';