Skip to content

Commit 28c0b11

Browse files
authored
Merge pull request #42 from backpackapp-io/feat/extended-animation-config
Feat/extended animation config
2 parents c4f3aa5 + 9afd9c7 commit 28c0b11

File tree

6 files changed

+129
-27
lines changed

6 files changed

+129
-27
lines changed

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ toast('Hello World', {
264264
text: TextStyle,
265265
indicator: ViewStyle
266266
},
267+
animationType: 'timing' | 'spring',
268+
animationConfig: {
269+
flingPositionReturnDuration: number,
270+
...(springConfig | timingConfig)
271+
},
267272
});
268273
```
269274

@@ -402,6 +407,63 @@ Every type has its own duration. You can overwrite them `duration` with the toas
402407

403408
<br />
404409

410+
### Animation Options
411+
You can now control the animation type and configuration for toasts.
412+
413+
#### Props
414+
415+
- **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.
416+
- **animationConfig** (object, optional): Customize the animation configuration for spring or timing.
417+
418+
#### Example Usage
419+
420+
```javascript
421+
import { toast } from 'react-native-toast';
422+
423+
// Show a toast with custom animation settings
424+
toast.show('This is a toast message', {
425+
animationType: 'spring',
426+
animationConfig: {
427+
duration: 500,
428+
stiffness: 100,
429+
},
430+
position: 'top',
431+
});
432+
````
433+
434+
### Global Animation Configuration/Type
435+
436+
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.
437+
438+
#### Props
439+
440+
- **globalAnimationConfig** (object, optional): Provides a default configuration for toast animations using either spring or timing options.
441+
442+
#### Example Usage
443+
444+
```javascript
445+
import { Toasts } from 'react-native-toast';
446+
447+
// In your component
448+
<Toasts
449+
globalAnimationType="spring"
450+
globalAnimationConfig={{
451+
duration: 500,
452+
stiffness: 120,
453+
}}
454+
/>
455+
456+
// Or when showing a toast
457+
toast.show('This is a toast message', {
458+
position: 'bottom',
459+
animationType: 'spring',
460+
animationConfig: {
461+
duration: 400,
462+
damping: 10,
463+
},
464+
});
465+
```
466+
405467

406468
### Dismiss toast programmatically
407469

src/components/Toast.tsx

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ import {
1010
ViewStyle,
1111
} from 'react-native';
1212
import Animated, {
13+
Easing,
14+
ReduceMotion,
1315
runOnJS,
1416
useAnimatedStyle,
1517
useSharedValue,
1618
withSpring,
19+
type WithSpringConfig,
1720
withTiming,
21+
type WithTimingConfig,
1822
} from 'react-native-reanimated';
1923
import { useSafeAreaInsets } from 'react-native-safe-area-context';
2024
import {
@@ -110,15 +114,33 @@ export const Toast: FC<Props> = ({
110114
}, []);
111115

112116
const setPosition = useCallback(() => {
113-
//control the position of the toast when rendering
114-
//based on offset, visibility, keyboard, and toast height
117+
let timingConfig: WithTimingConfig = { duration: 300 };
118+
let springConfig: WithSpringConfig = { stiffness: 80 };
119+
120+
if (toast.animationConfig) {
121+
const {
122+
duration = 300,
123+
easing = Easing.inOut(Easing.quad),
124+
reduceMotion = ReduceMotion.System,
125+
...spring
126+
} = toast.animationConfig;
127+
timingConfig = { duration, easing, reduceMotion };
128+
springConfig = spring;
129+
}
130+
131+
const useSpringAnimation = toast.animationType === 'spring';
132+
133+
const animation = useSpringAnimation ? withSpring : withTiming;
134+
115135
if (toast.position === ToastPosition.TOP) {
116-
offsetY.value = withTiming(toast.visible ? offset : startingY, {
117-
duration: toast?.animationConfig?.animationDuration ?? 300,
118-
});
119-
position.value = withTiming(toast.visible ? offset : startingY, {
120-
duration: toast?.animationConfig?.animationDuration ?? 300,
121-
});
136+
offsetY.value = animation(
137+
toast.visible ? offset : startingY,
138+
useSpringAnimation ? springConfig : timingConfig
139+
);
140+
position.value = animation(
141+
toast.visible ? offset : startingY,
142+
useSpringAnimation ? springConfig : timingConfig
143+
);
122144
} else {
123145
let kbHeight = keyboardVisible ? keyboardHeight : 0;
124146
const val = toast.visible
@@ -131,13 +153,14 @@ export const Toast: FC<Props> = ({
131153
24
132154
: startingY;
133155

134-
offsetY.value = withSpring(val, {
135-
stiffness: toast?.animationConfig?.animationStiffness ?? 80,
136-
});
137-
138-
position.value = withSpring(val, {
139-
stiffness: toast?.animationConfig?.animationStiffness ?? 80,
140-
});
156+
offsetY.value = animation(
157+
val,
158+
useSpringAnimation ? springConfig : timingConfig
159+
);
160+
position.value = animation(
161+
val,
162+
useSpringAnimation ? springConfig : timingConfig
163+
);
141164
}
142165
}, [
143166
offset,
@@ -152,6 +175,7 @@ export const Toast: FC<Props> = ({
152175
offsetY,
153176
extraInsets,
154177
toast.animationConfig,
178+
toast.animationType,
155179
]);
156180

157181
const composedGesture = useMemo(() => {
@@ -190,21 +214,18 @@ export const Toast: FC<Props> = ({
190214
]);
191215

192216
useEffect(() => {
193-
//set the toast height if it updates while rendered
194217
setToastHeight(toast?.height ? toast.height : DEFAULT_TOAST_HEIGHT);
195218
}, [toast.height]);
196219

197220
useEffect(() => {
198-
//set the toast width if it updates while rendered
199221
setToastWidth(
200222
toast?.width ? toast.width : width - 32 > 360 ? 360 : width - 32
201223
);
202224
}, [toast.width, width]);
203225

204226
useEffect(() => {
205-
//Control visibility of toast when rendering
206227
opacity.value = withTiming(toast.visible ? 1 : 0, {
207-
duration: toast?.animationConfig?.animationDuration ?? 300,
228+
duration: toast?.animationConfig?.duration ?? 300,
208229
});
209230
}, [toast.visible, opacity, toast.animationConfig]);
210231

src/components/Toasts.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { TextStyle, View, ViewStyle } from 'react-native';
44
import { Toast as T, useToaster } from '../headless';
55
import { Toast } from './Toast';
66
import { useSafeAreaInsets } from 'react-native-safe-area-context';
7-
import { ExtraInsets } from '../core/types';
7+
import {
8+
ExtraInsets,
9+
ToastAnimationConfig,
10+
ToastAnimationType,
11+
} from '../core/types';
812
import { useScreenReader } from 'src/core/utils';
913
import { useKeyboard } from 'src/utils';
1014

@@ -22,6 +26,8 @@ type Props = {
2226
text?: TextStyle;
2327
indicator?: ViewStyle;
2428
};
29+
globalAnimationType?: ToastAnimationType;
30+
globalAnimationConfig?: ToastAnimationConfig;
2531
};
2632

2733
export const Toasts: FunctionComponent<Props> = ({
@@ -33,6 +39,8 @@ export const Toasts: FunctionComponent<Props> = ({
3339
providerKey = 'DEFAULT',
3440
preventScreenReaderFromHiding,
3541
defaultStyle,
42+
globalAnimationType,
43+
globalAnimationConfig,
3644
}) => {
3745
const { toasts, handlers } = useToaster({ providerKey });
3846
const { startPause, endPause } = handlers;
@@ -58,7 +66,11 @@ export const Toasts: FunctionComponent<Props> = ({
5866
{toasts.map((t) => (
5967
<Toast
6068
key={t.id}
61-
toast={t}
69+
toast={{
70+
...t,
71+
animationType: t.animationType || globalAnimationType,
72+
animationConfig: t.animationConfig || globalAnimationConfig,
73+
}}
6274
startPause={startPause}
6375
endPause={endPause}
6476
updateHeight={handlers.updateHeight}

src/core/toast.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const createToast = (
2828
position: ToastPosition.TOP,
2929
providerKey: 'DEFAULT',
3030
isSwipeable: true,
31+
animationType: opts?.animationType ?? 'timing',
32+
animationConfig: opts?.animationConfig,
3133
...opts,
3234
id: opts?.id || genId(),
3335
});

src/core/types.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { TextStyle, ViewStyle } from 'react-native';
2+
import { WithSpringConfig, WithTimingConfig } from 'react-native-reanimated';
23

34
export type ToastType = 'success' | 'error' | 'loading' | 'blank';
45
export enum ToastPosition {
@@ -12,6 +13,12 @@ export interface IconTheme {
1213
primary: string;
1314
secondary: string;
1415
}
16+
export type ToastAnimationType = 'spring' | 'timing';
17+
18+
export type ToastAnimationConfig = {
19+
flingPositionReturnDuration?: number;
20+
} & WithSpringConfig &
21+
WithTimingConfig;
1522

1623
export type ValueFunction<TValue, TArg> = (arg: TArg) => TValue;
1724
export type ValueOrFunction<TValue, TArg> =
@@ -51,11 +58,8 @@ export interface Toast {
5158
customToast?: (toast: Toast) => JSX.Element;
5259
providerKey: string;
5360
isSwipeable?: boolean;
54-
animationConfig?: {
55-
flingPositionReturnDuration?: number;
56-
animationStiffness?: number;
57-
animationDuration?: number;
58-
};
61+
animationType?: ToastAnimationType;
62+
animationConfig?: ToastAnimationConfig;
5963
}
6064

6165
export type ToastOptions = Partial<
@@ -74,6 +78,7 @@ export type ToastOptions = Partial<
7478
| 'providerKey'
7579
| 'isSwipeable'
7680
| 'animationConfig'
81+
| 'animationType'
7782
>
7883
>;
7984

src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export { useToaster } from './core/use-toaster';
22
export { Toasts } from './components';
33
export * from './headless';
4-
export { ToastPosition } from './core/types';
4+
export { ToastPosition, ToastAnimationType } from './core/types';

0 commit comments

Comments
 (0)