react-native-bread 0.1.1 → 0.1.3
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 +4 -3
- package/lib/commonjs/icons/CloseIcon.js +1 -22
- package/lib/commonjs/icons/GreenCheck.js +1 -27
- package/lib/commonjs/icons/InfoIcon.js +1 -24
- package/lib/commonjs/icons/RedX.js +1 -27
- package/lib/commonjs/icons/index.js +1 -34
- package/lib/commonjs/index.js +1 -59
- package/lib/commonjs/toast-api.js +1 -127
- package/lib/commonjs/toast-provider.js +1 -66
- package/lib/commonjs/toast-store.js +1 -278
- package/lib/commonjs/toast.js +1 -481
- package/lib/commonjs/types.js +1 -6
- package/lib/module/icons/CloseIcon.js +1 -16
- package/lib/module/icons/GreenCheck.js +1 -21
- package/lib/module/icons/InfoIcon.js +1 -18
- package/lib/module/icons/RedX.js +1 -21
- package/lib/module/icons/index.js +1 -7
- package/lib/module/index.js +1 -14
- package/lib/module/toast-api.js +1 -124
- package/lib/module/toast-provider.js +1 -62
- package/lib/module/toast-store.js +1 -274
- package/lib/module/toast.js +1 -475
- package/lib/module/types.js +1 -4
- package/package.json +7 -6
- package/lib/commonjs/icons/CloseIcon.js.map +0 -1
- package/lib/commonjs/icons/GreenCheck.js.map +0 -1
- package/lib/commonjs/icons/InfoIcon.js.map +0 -1
- package/lib/commonjs/icons/RedX.js.map +0 -1
- package/lib/commonjs/icons/index.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/toast-api.js.map +0 -1
- package/lib/commonjs/toast-provider.js.map +0 -1
- package/lib/commonjs/toast-store.js.map +0 -1
- package/lib/commonjs/toast.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/module/icons/CloseIcon.js.map +0 -1
- package/lib/module/icons/GreenCheck.js.map +0 -1
- package/lib/module/icons/InfoIcon.js.map +0 -1
- package/lib/module/icons/RedX.js.map +0 -1
- package/lib/module/icons/index.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/toast-api.js.map +0 -1
- package/lib/module/toast-provider.js.map +0 -1
- package/lib/module/toast-store.js.map +0 -1
- package/lib/module/toast.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/typescript/icons/CloseIcon.d.ts.map +0 -1
- package/lib/typescript/icons/GreenCheck.d.ts.map +0 -1
- package/lib/typescript/icons/InfoIcon.d.ts.map +0 -1
- package/lib/typescript/icons/RedX.d.ts.map +0 -1
- package/lib/typescript/icons/index.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/toast-api.d.ts.map +0 -1
- package/lib/typescript/toast-provider.d.ts.map +0 -1
- package/lib/typescript/toast-store.d.ts.map +0 -1
- package/lib/typescript/toast.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
- package/src/icons/CloseIcon.tsx +0 -10
- package/src/icons/GreenCheck.tsx +0 -16
- package/src/icons/InfoIcon.tsx +0 -12
- package/src/icons/RedX.tsx +0 -16
- package/src/icons/index.ts +0 -4
- package/src/index.ts +0 -26
- package/src/toast-api.ts +0 -213
- package/src/toast-provider.tsx +0 -77
- package/src/toast-store.ts +0 -270
- package/src/toast.tsx +0 -466
- package/src/types.ts +0 -121
package/src/toast.tsx
DELETED
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
import { memo, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from "react-native";
|
|
3
|
-
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
4
|
-
import Animated, {
|
|
5
|
-
Easing,
|
|
6
|
-
interpolate,
|
|
7
|
-
useAnimatedStyle,
|
|
8
|
-
useDerivedValue,
|
|
9
|
-
useSharedValue,
|
|
10
|
-
withTiming,
|
|
11
|
-
} from "react-native-reanimated";
|
|
12
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
13
|
-
import { scheduleOnRN } from "react-native-worklets";
|
|
14
|
-
import { CloseIcon, GreenCheck, InfoIcon, RedX } from "./icons";
|
|
15
|
-
import { type Toast as ToastData, type ToastState, type ToastType, toastStore } from "./toast-store";
|
|
16
|
-
import type { IconRenderFn, ToastPosition, ToastTheme } from "./types";
|
|
17
|
-
|
|
18
|
-
const ICON_SIZE = 28;
|
|
19
|
-
|
|
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 }) => {
|
|
30
|
-
switch (type) {
|
|
31
|
-
case "success":
|
|
32
|
-
return <MemoizedGreenCheck fill={accentColor} />;
|
|
33
|
-
case "error":
|
|
34
|
-
return <MemoizedRedX fill={accentColor} />;
|
|
35
|
-
case "loading":
|
|
36
|
-
return <ActivityIndicator size={ICON_SIZE} color={accentColor} />;
|
|
37
|
-
case "info":
|
|
38
|
-
return <MemoizedInfoIcon fill={accentColor} />;
|
|
39
|
-
default:
|
|
40
|
-
return <MemoizedGreenCheck fill={accentColor} />;
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
interface AnimatedIconProps {
|
|
45
|
-
type: ToastType;
|
|
46
|
-
accentColor: string;
|
|
47
|
-
customIcon?: ReactNode | IconRenderFn;
|
|
48
|
-
configIcon?: IconRenderFn;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Resolves the icon to render - checks per-toast, then config, then default */
|
|
52
|
-
const resolveIcon = (
|
|
53
|
-
type: ToastType,
|
|
54
|
-
accentColor: string,
|
|
55
|
-
customIcon?: ReactNode | IconRenderFn,
|
|
56
|
-
configIcon?: IconRenderFn
|
|
57
|
-
): ReactNode => {
|
|
58
|
-
if (customIcon) {
|
|
59
|
-
if (typeof customIcon === "function") {
|
|
60
|
-
return customIcon({ color: accentColor, size: ICON_SIZE });
|
|
61
|
-
}
|
|
62
|
-
return customIcon;
|
|
63
|
-
}
|
|
64
|
-
if (configIcon) {
|
|
65
|
-
return configIcon({ color: accentColor, size: ICON_SIZE });
|
|
66
|
-
}
|
|
67
|
-
return <DefaultIcon type={type} accentColor={accentColor} />;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/** Animated icon wrapper with scale/fade animation */
|
|
71
|
-
const AnimatedIcon = memo(({ type, accentColor, customIcon, configIcon }: AnimatedIconProps) => {
|
|
72
|
-
const progress = useSharedValue(0);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
progress.value = withTiming(1, { duration: 350, easing: Easing.out(Easing.back(1.5)) });
|
|
76
|
-
}, [progress]);
|
|
77
|
-
|
|
78
|
-
const style = useAnimatedStyle(() => ({
|
|
79
|
-
opacity: progress.value,
|
|
80
|
-
transform: [{ scale: 0.7 + progress.value * 0.3 }],
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
|
-
return <Animated.View style={style}>{resolveIcon(type, accentColor, customIcon, configIcon)}</Animated.View>;
|
|
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
|
-
);
|
|
147
|
-
|
|
148
|
-
// singleton instance
|
|
149
|
-
export const ToastContainer = () => {
|
|
150
|
-
const [visibleToasts, setVisibleToasts] = useState<ToastData[]>([]);
|
|
151
|
-
const [theme, setTheme] = useState<ToastTheme>(() => toastStore.getTheme());
|
|
152
|
-
const { top, bottom } = useSafeAreaInsets();
|
|
153
|
-
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const initialState = toastStore.getState();
|
|
156
|
-
setVisibleToasts(initialState.visibleToasts);
|
|
157
|
-
setTheme(toastStore.getTheme());
|
|
158
|
-
|
|
159
|
-
return toastStore.subscribe((state: ToastState) => {
|
|
160
|
-
setVisibleToasts(state.visibleToasts);
|
|
161
|
-
setTheme(toastStore.getTheme());
|
|
162
|
-
});
|
|
163
|
-
}, []);
|
|
164
|
-
|
|
165
|
-
// Calculate visual index for each toast (exiting toasts don't count)
|
|
166
|
-
const getVisualIndex = useCallback(
|
|
167
|
-
(toastId: string) => {
|
|
168
|
-
let visualIndex = 0;
|
|
169
|
-
for (const t of visibleToasts) {
|
|
170
|
-
if (t.id === toastId) break;
|
|
171
|
-
if (!t.isExiting) visualIndex++;
|
|
172
|
-
}
|
|
173
|
-
return visualIndex;
|
|
174
|
-
},
|
|
175
|
-
[visibleToasts]
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
// Memoize the reversed array to avoid recreating on each render
|
|
179
|
-
const reversedToasts = useMemo(() => [...visibleToasts].reverse(), [visibleToasts]);
|
|
180
|
-
|
|
181
|
-
if (visibleToasts.length === 0) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const isBottom = theme.position === "bottom";
|
|
186
|
-
const inset = isBottom ? bottom : top;
|
|
187
|
-
const positionStyle = isBottom ? { bottom: inset + theme.offset + 2 } : { top: inset + theme.offset + 2 };
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<View style={[styles.container, positionStyle]} pointerEvents="box-none">
|
|
191
|
-
{reversedToasts.map(toast => {
|
|
192
|
-
const index = toast.isExiting ? -1 : getVisualIndex(toast.id);
|
|
193
|
-
return <MemoizedToastItem key={toast.id} toast={toast} index={index} theme={theme} position={theme.position} />;
|
|
194
|
-
})}
|
|
195
|
-
</View>
|
|
196
|
-
);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
interface ToastItemProps {
|
|
200
|
-
toast: ToastData;
|
|
201
|
-
index: number;
|
|
202
|
-
theme: ToastTheme;
|
|
203
|
-
position: ToastPosition;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const EASING = Easing.bezier(0.25, 0.1, 0.25, 1.0);
|
|
207
|
-
const ToY = 0;
|
|
208
|
-
const Duration = 400;
|
|
209
|
-
const ExitDuration = 350;
|
|
210
|
-
const MaxDragDown = 60;
|
|
211
|
-
|
|
212
|
-
const ToastItem = ({ toast, index, theme, position }: ToastItemProps) => {
|
|
213
|
-
const progress = useSharedValue(0);
|
|
214
|
-
const translationY = useSharedValue(0);
|
|
215
|
-
const isBeingDragged = useSharedValue(false);
|
|
216
|
-
const shouldDismiss = useSharedValue(false);
|
|
217
|
-
|
|
218
|
-
// Position-based animation values
|
|
219
|
-
const isBottom = position === "bottom";
|
|
220
|
-
const entryFromY = isBottom ? 80 : -80;
|
|
221
|
-
const exitToY = isBottom ? 100 : -100;
|
|
222
|
-
|
|
223
|
-
// Stack position animation
|
|
224
|
-
const stackIndex = useSharedValue(index);
|
|
225
|
-
|
|
226
|
-
// Refs for tracking previous values to avoid unnecessary animations
|
|
227
|
-
const lastHandledType = useRef(toast.type);
|
|
228
|
-
const prevIndex = useRef(index);
|
|
229
|
-
const hasEntered = useRef(false);
|
|
230
|
-
|
|
231
|
-
useEffect(() => {
|
|
232
|
-
// Entry animation (only once on mount)
|
|
233
|
-
if (!hasEntered.current && !toast.isExiting) {
|
|
234
|
-
progress.value = withTiming(1, { duration: Duration, easing: EASING });
|
|
235
|
-
hasEntered.current = true;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Exit animation when isExiting becomes true
|
|
239
|
-
if (toast.isExiting) {
|
|
240
|
-
progress.value = withTiming(0, { duration: ExitDuration, easing: EASING });
|
|
241
|
-
translationY.value = withTiming(exitToY, { duration: ExitDuration, easing: EASING });
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Track type changes (for icon animation via key)
|
|
245
|
-
if (toast.type !== lastHandledType.current) {
|
|
246
|
-
lastHandledType.current = toast.type;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Stack position animation when index changes
|
|
250
|
-
if (index >= 0 && prevIndex.current !== index) {
|
|
251
|
-
stackIndex.value = withTiming(index, { duration: 300, easing: EASING });
|
|
252
|
-
prevIndex.current = index;
|
|
253
|
-
}
|
|
254
|
-
}, [toast.isExiting, toast.type, index, progress, translationY, stackIndex, exitToY]);
|
|
255
|
-
|
|
256
|
-
const dismissToast = useCallback(() => {
|
|
257
|
-
toastStore.hide(toast.id);
|
|
258
|
-
}, [toast.id]);
|
|
259
|
-
|
|
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));
|
|
322
|
-
|
|
323
|
-
const animatedStyle = useAnimatedStyle(() => {
|
|
324
|
-
const baseTranslateY = interpolate(progress.value, [0, 1], [entryFromY, ToY]);
|
|
325
|
-
|
|
326
|
-
// Stack offset: each toast behind moves away from edge (up for top, down for bottom)
|
|
327
|
-
const stackOffsetY = isBottom ? stackIndex.value * 10 : stackIndex.value * -10;
|
|
328
|
-
|
|
329
|
-
// Stack scale: each toast behind scales down by 0.05
|
|
330
|
-
const stackScale = 1 - stackIndex.value * 0.05;
|
|
331
|
-
|
|
332
|
-
const finalTranslateY = baseTranslateY + translationY.value + stackOffsetY;
|
|
333
|
-
|
|
334
|
-
const progressOpacity = interpolate(progress.value, [0, 1], [0, 1]);
|
|
335
|
-
// For top: dragging up (negative) fades out. For bottom: dragging down (positive) fades out
|
|
336
|
-
const dismissDirection = isBottom ? translationY.value : -translationY.value;
|
|
337
|
-
const dragOpacity = dismissDirection > 0 ? interpolate(dismissDirection, [0, 130], [1, 0], "clamp") : 1;
|
|
338
|
-
const opacity = progressOpacity * dragOpacity;
|
|
339
|
-
|
|
340
|
-
const dragScale = interpolate(Math.abs(translationY.value), [0, 50], [1, 0.98], "clamp");
|
|
341
|
-
const scale = stackScale * dragScale;
|
|
342
|
-
|
|
343
|
-
return {
|
|
344
|
-
transform: [{ translateY: finalTranslateY }, { scale }],
|
|
345
|
-
opacity,
|
|
346
|
-
zIndex: zIndex.value,
|
|
347
|
-
};
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
const accentColor = theme.colors[toast.type].accent;
|
|
351
|
-
const backgroundColor = theme.colors[toast.type].background;
|
|
352
|
-
const verticalAnchor = isBottom ? { bottom: 0 } : { top: 0 };
|
|
353
|
-
|
|
354
|
-
// Per-toast overrides from options
|
|
355
|
-
const { options } = toast;
|
|
356
|
-
const customIcon = options?.icon;
|
|
357
|
-
const configIcon = theme.icons[toast.type];
|
|
358
|
-
|
|
359
|
-
// Resolve dismissible and showCloseButton (per-toast overrides config)
|
|
360
|
-
const isDismissible = options?.dismissible ?? theme.dismissible;
|
|
361
|
-
const shouldShowCloseButton = toast.type !== "loading" && (options?.showCloseButton ?? theme.showCloseButton);
|
|
362
|
-
|
|
363
|
-
// Enable/disable gesture based on dismissible setting
|
|
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
|
-
];
|
|
374
|
-
|
|
375
|
-
return (
|
|
376
|
-
<GestureDetector gesture={gesture}>
|
|
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
|
-
/>
|
|
392
|
-
</Animated.View>
|
|
393
|
-
</GestureDetector>
|
|
394
|
-
);
|
|
395
|
-
};
|
|
396
|
-
|
|
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
|
-
});
|
|
410
|
-
|
|
411
|
-
const styles = StyleSheet.create({
|
|
412
|
-
container: {
|
|
413
|
-
position: "absolute",
|
|
414
|
-
left: 16,
|
|
415
|
-
right: 16,
|
|
416
|
-
zIndex: 1000,
|
|
417
|
-
},
|
|
418
|
-
closeButton: {
|
|
419
|
-
padding: 4,
|
|
420
|
-
alignItems: "center",
|
|
421
|
-
justifyContent: "center",
|
|
422
|
-
},
|
|
423
|
-
icon: {
|
|
424
|
-
width: 48,
|
|
425
|
-
height: 48,
|
|
426
|
-
alignItems: "center",
|
|
427
|
-
justifyContent: "center",
|
|
428
|
-
marginLeft: 8,
|
|
429
|
-
},
|
|
430
|
-
content: {
|
|
431
|
-
alignItems: "center",
|
|
432
|
-
flexDirection: "row",
|
|
433
|
-
gap: 12,
|
|
434
|
-
minHeight: 36,
|
|
435
|
-
},
|
|
436
|
-
description: {
|
|
437
|
-
color: "#6B7280",
|
|
438
|
-
fontSize: 12,
|
|
439
|
-
fontWeight: "500",
|
|
440
|
-
lineHeight: 16,
|
|
441
|
-
},
|
|
442
|
-
textContainer: {
|
|
443
|
-
flex: 1,
|
|
444
|
-
gap: 1,
|
|
445
|
-
justifyContent: "center",
|
|
446
|
-
},
|
|
447
|
-
title: {
|
|
448
|
-
fontSize: 14,
|
|
449
|
-
fontWeight: "700",
|
|
450
|
-
lineHeight: 20,
|
|
451
|
-
},
|
|
452
|
-
toast: {
|
|
453
|
-
borderRadius: 20,
|
|
454
|
-
borderCurve: "continuous",
|
|
455
|
-
position: "absolute",
|
|
456
|
-
left: 0,
|
|
457
|
-
right: 0,
|
|
458
|
-
paddingHorizontal: 12,
|
|
459
|
-
paddingVertical: 10,
|
|
460
|
-
shadowColor: "#000",
|
|
461
|
-
shadowOffset: { width: 0, height: 8 },
|
|
462
|
-
shadowOpacity: 0.05,
|
|
463
|
-
shadowRadius: 24,
|
|
464
|
-
elevation: 8,
|
|
465
|
-
},
|
|
466
|
-
});
|
package/src/types.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import type { TextStyle, ViewStyle } from "react-native";
|
|
3
|
-
|
|
4
|
-
export type ToastType = "success" | "error" | "info" | "loading";
|
|
5
|
-
|
|
6
|
-
export type ToastPosition = "top" | "bottom";
|
|
7
|
-
|
|
8
|
-
export interface ToastTypeColors {
|
|
9
|
-
/** Accent color used for title text and icons */
|
|
10
|
-
accent: string;
|
|
11
|
-
/** Background color of the toast */
|
|
12
|
-
background: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Props passed to custom icon render functions */
|
|
16
|
-
export interface IconProps {
|
|
17
|
-
/** The accent color from the theme for this toast type */
|
|
18
|
-
color: string;
|
|
19
|
-
/** Default icon size */
|
|
20
|
-
size: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Custom icon render function */
|
|
24
|
-
export type IconRenderFn = (props: IconProps) => ReactNode;
|
|
25
|
-
|
|
26
|
-
export interface ToastTheme {
|
|
27
|
-
/** Position of toasts on screen */
|
|
28
|
-
position: ToastPosition;
|
|
29
|
-
/** Extra offset from safe area edge (in addition to safe area insets) */
|
|
30
|
-
offset: number;
|
|
31
|
-
/** Whether to show multiple toasts stacked (default: true). When false, only one toast shows at a time. */
|
|
32
|
-
stacking: boolean;
|
|
33
|
-
/** Maximum number of toasts visible at once when stacking is enabled (default: 3) */
|
|
34
|
-
maxStack: number;
|
|
35
|
-
/** Whether toasts can be dismissed via swipe gesture (default: true) */
|
|
36
|
-
dismissible: boolean;
|
|
37
|
-
/** Whether to show the close button on toasts (default: true). Loading toasts never show close button. */
|
|
38
|
-
showCloseButton: boolean;
|
|
39
|
-
/** Colors for each toast type */
|
|
40
|
-
colors: Record<ToastType, ToastTypeColors>;
|
|
41
|
-
/** Custom icons for each toast type */
|
|
42
|
-
icons: Partial<Record<ToastType, IconRenderFn>>;
|
|
43
|
-
/** Style overrides for the toast container */
|
|
44
|
-
toastStyle: ViewStyle;
|
|
45
|
-
/** Style overrides for the title text */
|
|
46
|
-
titleStyle: TextStyle;
|
|
47
|
-
/** Style overrides for the description text */
|
|
48
|
-
descriptionStyle: TextStyle;
|
|
49
|
-
/** Default duration in ms for toasts (default: 4000) */
|
|
50
|
-
defaultDuration: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Per-toast options for customizing individual toasts */
|
|
54
|
-
export interface ToastOptions {
|
|
55
|
-
/** Description text */
|
|
56
|
-
description?: string;
|
|
57
|
-
/** Duration in ms (overrides default) */
|
|
58
|
-
duration?: number;
|
|
59
|
-
/** Custom icon (ReactNode or render function) */
|
|
60
|
-
icon?: ReactNode | IconRenderFn;
|
|
61
|
-
/** Style overrides for this toast's container */
|
|
62
|
-
style?: ViewStyle;
|
|
63
|
-
/** Style overrides for this toast's title */
|
|
64
|
-
titleStyle?: TextStyle;
|
|
65
|
-
/** Style overrides for this toast's description */
|
|
66
|
-
descriptionStyle?: TextStyle;
|
|
67
|
-
/** Whether this toast can be dismissed via swipe (overrides config) */
|
|
68
|
-
dismissible?: boolean;
|
|
69
|
-
/** Whether to show the close button on this toast (overrides config) */
|
|
70
|
-
showCloseButton?: boolean;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Configuration options for customizing toast behavior and appearance. All properties are optional. */
|
|
74
|
-
export type ToastConfig = {
|
|
75
|
-
[K in keyof ToastTheme]?: K extends "colors"
|
|
76
|
-
? Partial<Record<ToastType, Partial<ToastTypeColors>>>
|
|
77
|
-
: K extends "icons"
|
|
78
|
-
? Partial<Record<ToastType, IconRenderFn>>
|
|
79
|
-
: ToastTheme[K];
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export interface Toast {
|
|
83
|
-
id: string;
|
|
84
|
-
title: string;
|
|
85
|
-
description?: string;
|
|
86
|
-
type: ToastType;
|
|
87
|
-
duration: number;
|
|
88
|
-
createdAt: number;
|
|
89
|
-
isExiting?: boolean;
|
|
90
|
-
/** Per-toast style/icon overrides */
|
|
91
|
-
options?: ToastOptions;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface ToastState {
|
|
95
|
-
/** Visible toasts (index 0 = front/newest) */
|
|
96
|
-
visibleToasts: Toast[];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// --- Promise helper types ---
|
|
100
|
-
export type MessageInput =
|
|
101
|
-
| string
|
|
102
|
-
| {
|
|
103
|
-
title: string;
|
|
104
|
-
description?: string;
|
|
105
|
-
/** Override duration (ms) after promise settles */
|
|
106
|
-
duration?: number;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
export type ErrorMessageInput = MessageInput | ((error: Error) => MessageInput);
|
|
110
|
-
|
|
111
|
-
export interface PromiseMessages {
|
|
112
|
-
loading: MessageInput;
|
|
113
|
-
success: MessageInput;
|
|
114
|
-
error: ErrorMessageInput;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface PromiseResult<T> {
|
|
118
|
-
data?: T;
|
|
119
|
-
error?: Error;
|
|
120
|
-
success: boolean;
|
|
121
|
-
}
|