react-native-bread 0.1.0 → 0.1.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/README.md +101 -84
- package/lib/commonjs/toast-provider.js +17 -22
- package/lib/commonjs/toast-provider.js.map +1 -1
- package/lib/commonjs/toast.js +118 -82
- package/lib/commonjs/toast.js.map +1 -1
- package/lib/module/toast-provider.js +18 -23
- package/lib/module/toast-provider.js.map +1 -1
- package/lib/module/toast.js +119 -83
- package/lib/module/toast.js.map +1 -1
- package/lib/typescript/toast-provider.d.ts +13 -12
- package/lib/typescript/toast-provider.d.ts.map +1 -1
- package/lib/typescript/toast.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/toast-provider.tsx +18 -22
- package/src/toast.tsx +191 -142
package/package.json
CHANGED
package/src/toast-provider.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from "react";
|
|
2
2
|
import { StyleSheet, View } from "react-native";
|
|
3
3
|
import { ToastContainer } from "./toast";
|
|
4
4
|
import { toastStore } from "./toast-store";
|
|
5
5
|
import type { ToastConfig } from "./types";
|
|
6
6
|
|
|
7
7
|
interface BreadLoafProps {
|
|
8
|
-
children: ReactNode;
|
|
9
8
|
/**
|
|
10
9
|
* Configuration for customizing toast behavior and appearance.
|
|
11
10
|
* All properties are optional and will be merged with defaults.
|
|
@@ -23,17 +22,22 @@ interface BreadLoafProps {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
|
-
* Toast
|
|
27
|
-
*
|
|
25
|
+
* Toast component that enables toast notifications in your app.
|
|
26
|
+
* Add `<BreadLoaf />` to your root layout to start showing toasts.
|
|
28
27
|
*
|
|
29
28
|
* @example
|
|
30
29
|
* ```tsx
|
|
31
30
|
* import { BreadLoaf } from 'react-native-bread';
|
|
32
31
|
*
|
|
33
|
-
* // Basic usage
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
32
|
+
* // Basic usage - add to your root layout
|
|
33
|
+
* export default function RootLayout() {
|
|
34
|
+
* return (
|
|
35
|
+
* <>
|
|
36
|
+
* <Stack />
|
|
37
|
+
* <BreadLoaf />
|
|
38
|
+
* </>
|
|
39
|
+
* );
|
|
40
|
+
* }
|
|
37
41
|
*
|
|
38
42
|
* // With configuration
|
|
39
43
|
* <BreadLoaf
|
|
@@ -47,34 +51,26 @@ interface BreadLoafProps {
|
|
|
47
51
|
* },
|
|
48
52
|
* toastStyle: { borderRadius: 12 },
|
|
49
53
|
* }}
|
|
50
|
-
*
|
|
51
|
-
* <App />
|
|
52
|
-
* </BreadLoaf>
|
|
54
|
+
* />
|
|
53
55
|
* ```
|
|
54
56
|
*/
|
|
55
|
-
export function BreadLoaf({
|
|
57
|
+
export function BreadLoaf({ config }: BreadLoafProps) {
|
|
56
58
|
useEffect(() => {
|
|
57
59
|
toastStore.setConfig(config);
|
|
58
60
|
return () => {
|
|
59
|
-
// Reset to defaults when this provider unmounts
|
|
60
61
|
toastStore.setConfig(undefined);
|
|
61
62
|
};
|
|
62
63
|
}, [config]);
|
|
64
|
+
|
|
63
65
|
return (
|
|
64
|
-
<View style={styles.
|
|
65
|
-
|
|
66
|
-
<View style={styles.portalContainer} pointerEvents="box-none">
|
|
67
|
-
<ToastContainer />
|
|
68
|
-
</View>
|
|
66
|
+
<View style={styles.container} pointerEvents="box-none">
|
|
67
|
+
<ToastContainer />
|
|
69
68
|
</View>
|
|
70
69
|
);
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
const styles = StyleSheet.create({
|
|
74
|
-
|
|
75
|
-
flex: 1,
|
|
76
|
-
},
|
|
77
|
-
portalContainer: {
|
|
73
|
+
container: {
|
|
78
74
|
...StyleSheet.absoluteFillObject,
|
|
79
75
|
zIndex: 9999,
|
|
80
76
|
},
|
package/src/toast.tsx
CHANGED
|
@@ -4,8 +4,8 @@ import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
|
4
4
|
import Animated, {
|
|
5
5
|
Easing,
|
|
6
6
|
interpolate,
|
|
7
|
-
interpolateColor,
|
|
8
7
|
useAnimatedStyle,
|
|
8
|
+
useDerivedValue,
|
|
9
9
|
useSharedValue,
|
|
10
10
|
withTiming,
|
|
11
11
|
} from "react-native-reanimated";
|
|
@@ -17,21 +17,36 @@ import type { IconRenderFn, ToastPosition, ToastTheme } from "./types";
|
|
|
17
17
|
|
|
18
18
|
const ICON_SIZE = 28;
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
const
|
|
20
|
+
/** Memoized default icons to prevent SVG re-renders */
|
|
21
|
+
const MemoizedGreenCheck = memo(({ fill }: { fill: string }) => <GreenCheck width={36} height={36} fill={fill} />);
|
|
22
|
+
const MemoizedRedX = memo(({ fill }: { fill: string }) => <RedX width={ICON_SIZE} height={ICON_SIZE} fill={fill} />);
|
|
23
|
+
const MemoizedInfoIcon = memo(({ fill }: { fill: string }) => (
|
|
24
|
+
<InfoIcon width={ICON_SIZE} height={ICON_SIZE} fill={fill} />
|
|
25
|
+
));
|
|
26
|
+
const MemoizedCloseIcon = memo(() => <CloseIcon width={20} height={20} />);
|
|
27
|
+
|
|
28
|
+
/** Default icon for each toast type - memoized */
|
|
29
|
+
const DefaultIcon = memo(({ type, accentColor }: { type: ToastType; accentColor: string }) => {
|
|
22
30
|
switch (type) {
|
|
23
31
|
case "success":
|
|
24
|
-
return <
|
|
32
|
+
return <MemoizedGreenCheck fill={accentColor} />;
|
|
25
33
|
case "error":
|
|
26
|
-
return <
|
|
34
|
+
return <MemoizedRedX fill={accentColor} />;
|
|
27
35
|
case "loading":
|
|
28
36
|
return <ActivityIndicator size={ICON_SIZE} color={accentColor} />;
|
|
29
37
|
case "info":
|
|
30
|
-
return <
|
|
38
|
+
return <MemoizedInfoIcon fill={accentColor} />;
|
|
31
39
|
default:
|
|
32
|
-
return <
|
|
40
|
+
return <MemoizedGreenCheck fill={accentColor} />;
|
|
33
41
|
}
|
|
34
|
-
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
interface AnimatedIconProps {
|
|
45
|
+
type: ToastType;
|
|
46
|
+
accentColor: string;
|
|
47
|
+
customIcon?: ReactNode | IconRenderFn;
|
|
48
|
+
configIcon?: IconRenderFn;
|
|
49
|
+
}
|
|
35
50
|
|
|
36
51
|
/** Resolves the icon to render - checks per-toast, then config, then default */
|
|
37
52
|
const resolveIcon = (
|
|
@@ -40,31 +55,20 @@ const resolveIcon = (
|
|
|
40
55
|
customIcon?: ReactNode | IconRenderFn,
|
|
41
56
|
configIcon?: IconRenderFn
|
|
42
57
|
): ReactNode => {
|
|
43
|
-
// Per-toast custom icon takes priority
|
|
44
58
|
if (customIcon) {
|
|
45
59
|
if (typeof customIcon === "function") {
|
|
46
60
|
return customIcon({ color: accentColor, size: ICON_SIZE });
|
|
47
61
|
}
|
|
48
62
|
return customIcon;
|
|
49
63
|
}
|
|
50
|
-
|
|
51
|
-
// Config-level custom icon
|
|
52
64
|
if (configIcon) {
|
|
53
65
|
return configIcon({ color: accentColor, size: ICON_SIZE });
|
|
54
66
|
}
|
|
55
|
-
|
|
56
|
-
// Default icon
|
|
57
67
|
return <DefaultIcon type={type} accentColor={accentColor} />;
|
|
58
68
|
};
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
accentColor: string;
|
|
63
|
-
customIcon?: ReactNode | IconRenderFn;
|
|
64
|
-
configIcon?: IconRenderFn;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const AnimatedIcon = ({ type, accentColor, customIcon, configIcon }: AnimatedIconProps) => {
|
|
70
|
+
/** Animated icon wrapper with scale/fade animation */
|
|
71
|
+
const AnimatedIcon = memo(({ type, accentColor, customIcon, configIcon }: AnimatedIconProps) => {
|
|
68
72
|
const progress = useSharedValue(0);
|
|
69
73
|
|
|
70
74
|
useEffect(() => {
|
|
@@ -77,7 +81,69 @@ const AnimatedIcon = ({ type, accentColor, customIcon, configIcon }: AnimatedIco
|
|
|
77
81
|
}));
|
|
78
82
|
|
|
79
83
|
return <Animated.View style={style}>{resolveIcon(type, accentColor, customIcon, configIcon)}</Animated.View>;
|
|
80
|
-
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
interface ToastContentProps {
|
|
87
|
+
type: ToastType;
|
|
88
|
+
title: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
accentColor: string;
|
|
91
|
+
customIcon?: ReactNode | IconRenderFn;
|
|
92
|
+
configIcon?: IconRenderFn;
|
|
93
|
+
showCloseButton: boolean;
|
|
94
|
+
onDismiss: () => void;
|
|
95
|
+
titleStyle?: object;
|
|
96
|
+
descriptionStyle?: object;
|
|
97
|
+
optionsTitleStyle?: object;
|
|
98
|
+
optionsDescriptionStyle?: object;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Memoized toast content to prevent inline JSX recreation */
|
|
102
|
+
const ToastContent = memo(
|
|
103
|
+
({
|
|
104
|
+
type,
|
|
105
|
+
title,
|
|
106
|
+
description,
|
|
107
|
+
accentColor,
|
|
108
|
+
customIcon,
|
|
109
|
+
configIcon,
|
|
110
|
+
showCloseButton,
|
|
111
|
+
onDismiss,
|
|
112
|
+
titleStyle,
|
|
113
|
+
descriptionStyle,
|
|
114
|
+
optionsTitleStyle,
|
|
115
|
+
optionsDescriptionStyle,
|
|
116
|
+
}: ToastContentProps) => (
|
|
117
|
+
<View style={styles.content}>
|
|
118
|
+
<View style={styles.icon}>
|
|
119
|
+
<AnimatedIcon key={type} type={type} accentColor={accentColor} customIcon={customIcon} configIcon={configIcon} />
|
|
120
|
+
</View>
|
|
121
|
+
<View style={styles.textContainer}>
|
|
122
|
+
<Text
|
|
123
|
+
maxFontSizeMultiplier={1.35}
|
|
124
|
+
allowFontScaling={false}
|
|
125
|
+
style={[styles.title, { color: accentColor }, titleStyle, optionsTitleStyle]}
|
|
126
|
+
>
|
|
127
|
+
{title}
|
|
128
|
+
</Text>
|
|
129
|
+
{description && (
|
|
130
|
+
<Text
|
|
131
|
+
allowFontScaling={false}
|
|
132
|
+
maxFontSizeMultiplier={1.35}
|
|
133
|
+
style={[styles.description, descriptionStyle, optionsDescriptionStyle]}
|
|
134
|
+
>
|
|
135
|
+
{description}
|
|
136
|
+
</Text>
|
|
137
|
+
)}
|
|
138
|
+
</View>
|
|
139
|
+
{showCloseButton && (
|
|
140
|
+
<Pressable style={styles.closeButton} onPress={onDismiss} hitSlop={12}>
|
|
141
|
+
<MemoizedCloseIcon />
|
|
142
|
+
</Pressable>
|
|
143
|
+
)}
|
|
144
|
+
</View>
|
|
145
|
+
)
|
|
146
|
+
);
|
|
81
147
|
|
|
82
148
|
// singleton instance
|
|
83
149
|
export const ToastContainer = () => {
|
|
@@ -157,17 +223,11 @@ const ToastItem = ({ toast, index, theme, position }: ToastItemProps) => {
|
|
|
157
223
|
// Stack position animation
|
|
158
224
|
const stackIndex = useSharedValue(index);
|
|
159
225
|
|
|
160
|
-
// Title color animation on variant change
|
|
161
|
-
const colorProgress = useSharedValue(1);
|
|
162
|
-
const fromColor = useSharedValue(theme.colors[toast.type].accent);
|
|
163
|
-
const toColor = useSharedValue(theme.colors[toast.type].accent);
|
|
164
|
-
|
|
165
226
|
// Refs for tracking previous values to avoid unnecessary animations
|
|
166
227
|
const lastHandledType = useRef(toast.type);
|
|
167
228
|
const prevIndex = useRef(index);
|
|
168
229
|
const hasEntered = useRef(false);
|
|
169
230
|
|
|
170
|
-
// Combined animation effect for entry, exit, color transitions, and stack position
|
|
171
231
|
useEffect(() => {
|
|
172
232
|
// Entry animation (only once on mount)
|
|
173
233
|
if (!hasEntered.current && !toast.isExiting) {
|
|
@@ -181,13 +241,9 @@ const ToastItem = ({ toast, index, theme, position }: ToastItemProps) => {
|
|
|
181
241
|
translationY.value = withTiming(exitToY, { duration: ExitDuration, easing: EASING });
|
|
182
242
|
}
|
|
183
243
|
|
|
184
|
-
//
|
|
244
|
+
// Track type changes (for icon animation via key)
|
|
185
245
|
if (toast.type !== lastHandledType.current) {
|
|
186
|
-
fromColor.value = theme.colors[lastHandledType.current].accent;
|
|
187
|
-
toColor.value = theme.colors[toast.type].accent;
|
|
188
246
|
lastHandledType.current = toast.type;
|
|
189
|
-
colorProgress.value = 0;
|
|
190
|
-
colorProgress.value = withTiming(1, { duration: 300, easing: EASING });
|
|
191
247
|
}
|
|
192
248
|
|
|
193
249
|
// Stack position animation when index changes
|
|
@@ -195,80 +251,74 @@ const ToastItem = ({ toast, index, theme, position }: ToastItemProps) => {
|
|
|
195
251
|
stackIndex.value = withTiming(index, { duration: 300, easing: EASING });
|
|
196
252
|
prevIndex.current = index;
|
|
197
253
|
}
|
|
198
|
-
}, [
|
|
199
|
-
toast.isExiting,
|
|
200
|
-
toast.type,
|
|
201
|
-
index,
|
|
202
|
-
progress,
|
|
203
|
-
translationY,
|
|
204
|
-
fromColor,
|
|
205
|
-
toColor,
|
|
206
|
-
colorProgress,
|
|
207
|
-
stackIndex,
|
|
208
|
-
exitToY,
|
|
209
|
-
theme.colors,
|
|
210
|
-
]);
|
|
211
|
-
|
|
212
|
-
const titleColorStyle = useAnimatedStyle(() => ({
|
|
213
|
-
color: interpolateColor(colorProgress.value, [0, 1], [fromColor.value, toColor.value]),
|
|
214
|
-
}));
|
|
254
|
+
}, [toast.isExiting, toast.type, index, progress, translationY, stackIndex, exitToY]);
|
|
215
255
|
|
|
216
256
|
const dismissToast = useCallback(() => {
|
|
217
257
|
toastStore.hide(toast.id);
|
|
218
258
|
}, [toast.id]);
|
|
219
259
|
|
|
220
|
-
const panGesture =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
260
|
+
const panGesture = useMemo(
|
|
261
|
+
() =>
|
|
262
|
+
Gesture.Pan()
|
|
263
|
+
.onStart(() => {
|
|
264
|
+
"worklet";
|
|
265
|
+
isBeingDragged.value = true;
|
|
266
|
+
shouldDismiss.value = false;
|
|
267
|
+
})
|
|
268
|
+
.onUpdate(event => {
|
|
269
|
+
"worklet";
|
|
270
|
+
const rawY = event.translationY;
|
|
271
|
+
// For top: negative Y = dismiss direction, positive Y = resistance
|
|
272
|
+
// For bottom: positive Y = dismiss direction, negative Y = resistance
|
|
273
|
+
const dismissDrag = isBottom ? rawY : -rawY;
|
|
274
|
+
const resistDrag = isBottom ? -rawY : rawY;
|
|
275
|
+
|
|
276
|
+
if (dismissDrag > 0) {
|
|
277
|
+
// Moving toward dismiss direction
|
|
278
|
+
const clampedY = isBottom ? Math.min(rawY, 180) : Math.max(rawY, -180);
|
|
279
|
+
translationY.value = clampedY;
|
|
280
|
+
if (dismissDrag > 40 || (isBottom ? event.velocityY > 300 : event.velocityY < -300)) {
|
|
281
|
+
shouldDismiss.value = true;
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
// Moving away from edge - apply resistance
|
|
285
|
+
const exponentialDrag = MaxDragDown * (1 - Math.exp(-resistDrag / 250));
|
|
286
|
+
translationY.value = isBottom
|
|
287
|
+
? -Math.min(exponentialDrag, MaxDragDown)
|
|
288
|
+
: Math.min(exponentialDrag, MaxDragDown);
|
|
289
|
+
shouldDismiss.value = false;
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
.onEnd(() => {
|
|
293
|
+
"worklet";
|
|
294
|
+
isBeingDragged.value = false;
|
|
295
|
+
|
|
296
|
+
if (shouldDismiss.value) {
|
|
297
|
+
progress.value = withTiming(0, {
|
|
298
|
+
duration: ExitDuration,
|
|
299
|
+
easing: EASING,
|
|
300
|
+
});
|
|
301
|
+
const exitOffset = isBottom ? 200 : -200;
|
|
302
|
+
translationY.value = withTiming(translationY.value + exitOffset, {
|
|
303
|
+
duration: ExitDuration,
|
|
304
|
+
easing: EASING,
|
|
305
|
+
});
|
|
306
|
+
scheduleOnRN(dismissToast);
|
|
307
|
+
} else {
|
|
308
|
+
translationY.value = withTiming(0, {
|
|
309
|
+
duration: 650,
|
|
310
|
+
easing: EASING,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}),
|
|
314
|
+
[isBottom, dismissToast, progress, translationY, shouldDismiss, isBeingDragged]
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Memoize disabled gesture to avoid recreation on every render
|
|
318
|
+
const disabledGesture = useMemo(() => Gesture.Pan().enabled(false), []);
|
|
319
|
+
|
|
320
|
+
// Derive zIndex separately - it's not animatable and shouldn't trigger worklet re-runs
|
|
321
|
+
const zIndex = useDerivedValue(() => 1000 - Math.round(stackIndex.value));
|
|
272
322
|
|
|
273
323
|
const animatedStyle = useAnimatedStyle(() => {
|
|
274
324
|
const baseTranslateY = interpolate(progress.value, [0, 1], [entryFromY, ToY]);
|
|
@@ -293,7 +343,7 @@ const ToastItem = ({ toast, index, theme, position }: ToastItemProps) => {
|
|
|
293
343
|
return {
|
|
294
344
|
transform: [{ translateY: finalTranslateY }, { scale }],
|
|
295
345
|
opacity,
|
|
296
|
-
zIndex:
|
|
346
|
+
zIndex: zIndex.value,
|
|
297
347
|
};
|
|
298
348
|
});
|
|
299
349
|
|
|
@@ -311,53 +361,52 @@ const ToastItem = ({ toast, index, theme, position }: ToastItemProps) => {
|
|
|
311
361
|
const shouldShowCloseButton = toast.type !== "loading" && (options?.showCloseButton ?? theme.showCloseButton);
|
|
312
362
|
|
|
313
363
|
// Enable/disable gesture based on dismissible setting
|
|
314
|
-
const gesture = isDismissible ? panGesture :
|
|
364
|
+
const gesture = isDismissible ? panGesture : disabledGesture;
|
|
365
|
+
|
|
366
|
+
const animStyle = [
|
|
367
|
+
styles.toast,
|
|
368
|
+
verticalAnchor,
|
|
369
|
+
{ backgroundColor },
|
|
370
|
+
theme.toastStyle,
|
|
371
|
+
options?.style,
|
|
372
|
+
animatedStyle,
|
|
373
|
+
];
|
|
315
374
|
|
|
316
375
|
return (
|
|
317
376
|
<GestureDetector gesture={gesture}>
|
|
318
|
-
<Animated.View
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
maxFontSizeMultiplier={1.35}
|
|
334
|
-
allowFontScaling={false}
|
|
335
|
-
style={[styles.title, theme.titleStyle, options?.titleStyle, titleColorStyle]}
|
|
336
|
-
>
|
|
337
|
-
{toast.title}
|
|
338
|
-
</Animated.Text>
|
|
339
|
-
{toast.description && (
|
|
340
|
-
<Text
|
|
341
|
-
allowFontScaling={false}
|
|
342
|
-
maxFontSizeMultiplier={1.35}
|
|
343
|
-
style={[styles.description, theme.descriptionStyle, options?.descriptionStyle]}
|
|
344
|
-
>
|
|
345
|
-
{toast.description}
|
|
346
|
-
</Text>
|
|
347
|
-
)}
|
|
348
|
-
</View>
|
|
349
|
-
{shouldShowCloseButton && (
|
|
350
|
-
<Pressable style={styles.closeButton} onPress={dismissToast} hitSlop={12}>
|
|
351
|
-
<CloseIcon width={20} height={20} />
|
|
352
|
-
</Pressable>
|
|
353
|
-
)}
|
|
354
|
-
</View>
|
|
377
|
+
<Animated.View style={animStyle}>
|
|
378
|
+
<ToastContent
|
|
379
|
+
type={toast.type}
|
|
380
|
+
title={toast.title}
|
|
381
|
+
description={toast.description}
|
|
382
|
+
accentColor={accentColor}
|
|
383
|
+
customIcon={customIcon}
|
|
384
|
+
configIcon={configIcon}
|
|
385
|
+
showCloseButton={shouldShowCloseButton}
|
|
386
|
+
onDismiss={dismissToast}
|
|
387
|
+
titleStyle={theme.titleStyle}
|
|
388
|
+
descriptionStyle={theme.descriptionStyle}
|
|
389
|
+
optionsTitleStyle={options?.titleStyle}
|
|
390
|
+
optionsDescriptionStyle={options?.descriptionStyle}
|
|
391
|
+
/>
|
|
355
392
|
</Animated.View>
|
|
356
393
|
</GestureDetector>
|
|
357
394
|
);
|
|
358
395
|
};
|
|
359
396
|
|
|
360
|
-
|
|
397
|
+
// Custom comparison to prevent re-renders when toast object reference changes but content is same
|
|
398
|
+
const MemoizedToastItem = memo(ToastItem, (prev, next) => {
|
|
399
|
+
return (
|
|
400
|
+
prev.toast.id === next.toast.id &&
|
|
401
|
+
prev.toast.type === next.toast.type &&
|
|
402
|
+
prev.toast.title === next.toast.title &&
|
|
403
|
+
prev.toast.description === next.toast.description &&
|
|
404
|
+
prev.toast.isExiting === next.toast.isExiting &&
|
|
405
|
+
prev.index === next.index &&
|
|
406
|
+
prev.position === next.position &&
|
|
407
|
+
prev.theme === next.theme
|
|
408
|
+
);
|
|
409
|
+
});
|
|
361
410
|
|
|
362
411
|
const styles = StyleSheet.create({
|
|
363
412
|
container: {
|