react-native-bread 0.6.1 → 0.7.0
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 +3 -2
- package/lib/commonjs/toast-icons.js +2 -2
- package/lib/commonjs/toast.js +33 -33
- package/lib/commonjs/use-toast-state.js +11 -11
- package/lib/module/toast-icons.js +2 -2
- package/lib/module/toast.js +34 -34
- package/lib/module/use-toast-state.js +12 -12
- package/lib/typescript/types.d.ts +9 -10
- package/lib/typescript/use-toast-state.d.ts +3 -3
- package/package.json +46 -4
- package/src/constants.ts +23 -0
- package/src/icons/CloseIcon.tsx +10 -0
- package/src/icons/GreenCheck.tsx +16 -0
- package/src/icons/InfoIcon.tsx +12 -0
- package/src/icons/RedX.tsx +16 -0
- package/src/icons/index.ts +4 -0
- package/src/index.ts +28 -0
- package/src/pool.ts +57 -0
- package/src/toast-api.ts +247 -0
- package/src/toast-icons.tsx +55 -0
- package/src/toast-provider.tsx +127 -0
- package/src/toast-store.ts +254 -0
- package/src/toast.tsx +398 -0
- package/src/types.ts +166 -0
- package/src/use-toast-state.ts +78 -0
package/README.md
CHANGED
|
@@ -16,9 +16,10 @@ An extremely lightweight, opinionated toast component for React Native.
|
|
|
16
16
|
- Promise handling with automatic loading → success/error states
|
|
17
17
|
- Toast stacking with configurable limits
|
|
18
18
|
- **Works above modals** - automatic on iOS, simple setup on Android
|
|
19
|
-
- **RTL support** -
|
|
19
|
+
- **RTL support** - code-level RTL for when you're not using native RTL (`I18nManager`)
|
|
20
20
|
- Completely customizable - colors, icons, styles, animations
|
|
21
21
|
- Full Expo compatibility
|
|
22
|
+
- **React Compiler compatible** - uses `.set()` / `.value` API for shared values
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
|
|
@@ -181,7 +182,7 @@ Customize all toasts globally via the `config` prop on `<BreadLoaf />`:
|
|
|
181
182
|
<BreadLoaf
|
|
182
183
|
config={{
|
|
183
184
|
position: 'bottom',
|
|
184
|
-
rtl: false, //
|
|
185
|
+
rtl: false, // Code-level RTL — not needed if using native RTL (I18nManager)
|
|
185
186
|
stacking: true,
|
|
186
187
|
maxStack: 3,
|
|
187
188
|
defaultDuration: 4000,
|
|
@@ -61,10 +61,10 @@ const AnimatedIcon = exports.AnimatedIcon = /*#__PURE__*/(0, _react.memo)(({
|
|
|
61
61
|
}) => {
|
|
62
62
|
const progress = (0, _reactNativeReanimated.useSharedValue)(0);
|
|
63
63
|
(0, _react.useEffect)(() => {
|
|
64
|
-
progress.
|
|
64
|
+
progress.set((0, _reactNativeReanimated.withTiming)(1, {
|
|
65
65
|
duration: _constants.ICON_ANIMATION_DURATION,
|
|
66
66
|
easing: _reactNativeReanimated.Easing.out(_reactNativeReanimated.Easing.back(1.5))
|
|
67
|
-
});
|
|
67
|
+
}));
|
|
68
68
|
}, [progress]);
|
|
69
69
|
const style = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
|
|
70
70
|
opacity: progress.value,
|
package/lib/commonjs/toast.js
CHANGED
|
@@ -28,69 +28,69 @@ const ToastContainer = () => {
|
|
|
28
28
|
theme,
|
|
29
29
|
toastsWithIndex,
|
|
30
30
|
isBottom,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
topToastMutable,
|
|
32
|
+
isBottomMutable,
|
|
33
|
+
isDismissibleMutable
|
|
34
34
|
} = (0, _useToastState.useToastState)();
|
|
35
35
|
const shouldDismiss = (0, _reactNativeReanimated.useSharedValue)(false);
|
|
36
|
-
const panGesture =
|
|
36
|
+
const panGesture = _reactNativeGestureHandler.Gesture.Pan().onStart(() => {
|
|
37
37
|
"worklet";
|
|
38
38
|
|
|
39
39
|
shouldDismiss.set(false);
|
|
40
40
|
}).onUpdate(event => {
|
|
41
41
|
"worklet";
|
|
42
42
|
|
|
43
|
-
if (!
|
|
44
|
-
const ref =
|
|
43
|
+
if (!isDismissibleMutable.value) return;
|
|
44
|
+
const ref = topToastMutable.value;
|
|
45
45
|
if (!ref) return;
|
|
46
46
|
const {
|
|
47
47
|
slot
|
|
48
48
|
} = ref;
|
|
49
|
-
const bottom =
|
|
49
|
+
const bottom = isBottomMutable.value;
|
|
50
50
|
const rawY = event.translationY;
|
|
51
51
|
const dismissDrag = bottom ? rawY : -rawY;
|
|
52
52
|
const resistDrag = bottom ? -rawY : rawY;
|
|
53
53
|
if (dismissDrag > 0) {
|
|
54
54
|
const clampedY = bottom ? Math.min(rawY, _constants.MAX_DRAG_CLAMP) : Math.max(rawY, -_constants.MAX_DRAG_CLAMP);
|
|
55
|
-
slot.translationY.
|
|
55
|
+
slot.translationY.set(clampedY);
|
|
56
56
|
const shouldTriggerDismiss = dismissDrag > _constants.DISMISS_THRESHOLD || (bottom ? event.velocityY > _constants.DISMISS_VELOCITY_THRESHOLD : event.velocityY < -_constants.DISMISS_VELOCITY_THRESHOLD);
|
|
57
57
|
shouldDismiss.set(shouldTriggerDismiss);
|
|
58
58
|
} else {
|
|
59
59
|
const exponentialDrag = _constants.MAX_DRAG_RESISTANCE * (1 - Math.exp(-resistDrag / 250));
|
|
60
|
-
slot.translationY.
|
|
61
|
-
shouldDismiss.
|
|
60
|
+
slot.translationY.set(bottom ? -Math.min(exponentialDrag, _constants.MAX_DRAG_RESISTANCE) : Math.min(exponentialDrag, _constants.MAX_DRAG_RESISTANCE));
|
|
61
|
+
shouldDismiss.set(false);
|
|
62
62
|
}
|
|
63
63
|
}).onEnd(() => {
|
|
64
64
|
"worklet";
|
|
65
65
|
|
|
66
|
-
if (!
|
|
67
|
-
const ref =
|
|
66
|
+
if (!isDismissibleMutable.value) return;
|
|
67
|
+
const ref = topToastMutable.value;
|
|
68
68
|
if (!ref) return;
|
|
69
69
|
const {
|
|
70
70
|
slot
|
|
71
71
|
} = ref;
|
|
72
|
-
const bottom =
|
|
72
|
+
const bottom = isBottomMutable.value;
|
|
73
73
|
if (shouldDismiss.value) {
|
|
74
|
-
slot.progress.
|
|
74
|
+
slot.progress.set((0, _reactNativeReanimated.withTiming)(0, {
|
|
75
75
|
duration: _constants.EXIT_DURATION,
|
|
76
76
|
easing: _constants.EASING
|
|
77
|
-
});
|
|
77
|
+
}));
|
|
78
78
|
const exitOffset = bottom ? _constants.SWIPE_EXIT_OFFSET : -_constants.SWIPE_EXIT_OFFSET;
|
|
79
|
-
slot.translationY.
|
|
79
|
+
slot.translationY.set((0, _reactNativeReanimated.withTiming)(slot.translationY.value + exitOffset, {
|
|
80
80
|
duration: _constants.EXIT_DURATION,
|
|
81
81
|
easing: _constants.EASING
|
|
82
|
-
});
|
|
82
|
+
}));
|
|
83
83
|
(0, _reactNativeWorklets.scheduleOnRN)(ref.dismiss);
|
|
84
84
|
} else {
|
|
85
|
-
slot.translationY.
|
|
85
|
+
slot.translationY.set((0, _reactNativeReanimated.withTiming)(0, {
|
|
86
86
|
duration: _constants.SPRING_BACK_DURATION,
|
|
87
87
|
easing: _constants.EASING
|
|
88
|
-
});
|
|
88
|
+
}));
|
|
89
89
|
}
|
|
90
|
-
})
|
|
90
|
+
});
|
|
91
91
|
const registerTopToast = (0, _react.useCallback)(values => {
|
|
92
|
-
|
|
93
|
-
}, [
|
|
92
|
+
topToastMutable.set(values);
|
|
93
|
+
}, [topToastMutable]);
|
|
94
94
|
if (visibleToasts.length === 0) return null;
|
|
95
95
|
const inset = isBottom ? bottom : top;
|
|
96
96
|
const positionStyle = isBottom ? {
|
|
@@ -137,13 +137,13 @@ const ToastItem = ({
|
|
|
137
137
|
|
|
138
138
|
// biome-ignore lint/correctness/useExhaustiveDependencies: mount-only effect
|
|
139
139
|
(0, _react.useEffect)(() => {
|
|
140
|
-
slot.progress.
|
|
141
|
-
slot.translationY.
|
|
142
|
-
slot.stackIndex.
|
|
143
|
-
slot.progress.
|
|
140
|
+
slot.progress.set(0);
|
|
141
|
+
slot.translationY.set(0);
|
|
142
|
+
slot.stackIndex.set(index);
|
|
143
|
+
slot.progress.set((0, _reactNativeReanimated.withTiming)(1, {
|
|
144
144
|
duration: _constants.ENTRY_DURATION,
|
|
145
145
|
easing: _constants.EASING
|
|
146
|
-
});
|
|
146
|
+
}));
|
|
147
147
|
const iconTimeout = setTimeout(() => setShowIcon(true), 50);
|
|
148
148
|
return () => {
|
|
149
149
|
clearTimeout(iconTimeout);
|
|
@@ -157,20 +157,20 @@ const ToastItem = ({
|
|
|
157
157
|
let loadingTimeout = null;
|
|
158
158
|
if (toast.isExiting && !tracker.wasExiting) {
|
|
159
159
|
tracker.wasExiting = true;
|
|
160
|
-
slot.progress.
|
|
160
|
+
slot.progress.set((0, _reactNativeReanimated.withTiming)(0, {
|
|
161
161
|
duration: _constants.EXIT_DURATION,
|
|
162
162
|
easing: _constants.EASING
|
|
163
|
-
});
|
|
164
|
-
slot.translationY.
|
|
163
|
+
}));
|
|
164
|
+
slot.translationY.set((0, _reactNativeReanimated.withTiming)(exitToY, {
|
|
165
165
|
duration: _constants.EXIT_DURATION,
|
|
166
166
|
easing: _constants.EASING
|
|
167
|
-
});
|
|
167
|
+
}));
|
|
168
168
|
}
|
|
169
169
|
if (tracker.initialized && index !== tracker.prevIndex) {
|
|
170
|
-
slot.stackIndex.
|
|
170
|
+
slot.stackIndex.set((0, _reactNativeReanimated.withTiming)(index, {
|
|
171
171
|
duration: _constants.STACK_TRANSITION_DURATION,
|
|
172
172
|
easing: _constants.EASING
|
|
173
|
-
});
|
|
173
|
+
}));
|
|
174
174
|
}
|
|
175
175
|
tracker.prevIndex = index;
|
|
176
176
|
tracker.initialized = true;
|
|
@@ -10,9 +10,9 @@ var _toastStore = require("./toast-store.js");
|
|
|
10
10
|
const useToastState = () => {
|
|
11
11
|
const [visibleToasts, setVisibleToasts] = (0, _react.useState)([]);
|
|
12
12
|
const [theme, setTheme] = (0, _react.useState)(() => _toastStore.toastStore.getTheme());
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
const [topToastMutable] = (0, _react.useState)(() => (0, _reactNativeReanimated.makeMutable)(null));
|
|
14
|
+
const [isBottomMutable] = (0, _react.useState)(() => (0, _reactNativeReanimated.makeMutable)(theme.position === "bottom"));
|
|
15
|
+
const [isDismissibleMutable] = (0, _react.useState)(() => (0, _reactNativeReanimated.makeMutable)(true));
|
|
16
16
|
const isBottom = theme.position === "bottom";
|
|
17
17
|
const topToast = visibleToasts.find(t => !t.isExiting);
|
|
18
18
|
const isTopDismissible = topToast?.options?.dismissible ?? theme.dismissible;
|
|
@@ -21,8 +21,8 @@ const useToastState = () => {
|
|
|
21
21
|
const initialTheme = _toastStore.toastStore.getTheme();
|
|
22
22
|
setVisibleToasts(initialToasts);
|
|
23
23
|
const initialTopToast = initialToasts.find(t => !t.isExiting);
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
isBottomMutable.set(initialTheme.position === "bottom");
|
|
25
|
+
isDismissibleMutable.set(initialTopToast?.options?.dismissible ?? initialTheme.dismissible);
|
|
26
26
|
let pendingToasts = null;
|
|
27
27
|
let rafId = null;
|
|
28
28
|
const unsubscribe = _toastStore.toastStore.subscribe(state => {
|
|
@@ -38,13 +38,13 @@ const useToastState = () => {
|
|
|
38
38
|
rafId = null;
|
|
39
39
|
setTheme(prev => prev === currentTheme ? prev : currentTheme);
|
|
40
40
|
const topToast = currentToasts.find(t => !t.isExiting);
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
isBottomMutable.set(currentTheme.position === "bottom");
|
|
42
|
+
isDismissibleMutable.set(topToast?.options?.dismissible ?? currentTheme.dismissible);
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
return unsubscribe;
|
|
47
|
-
}, []);
|
|
47
|
+
}, [isBottomMutable, isDismissibleMutable]);
|
|
48
48
|
const toastsWithIndex = (0, _react.useMemo)(() => {
|
|
49
49
|
const indices = new Map();
|
|
50
50
|
let visualIndex = 0;
|
|
@@ -63,9 +63,9 @@ const useToastState = () => {
|
|
|
63
63
|
toastsWithIndex,
|
|
64
64
|
isBottom,
|
|
65
65
|
isTopDismissible,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
topToastMutable,
|
|
67
|
+
isBottomMutable,
|
|
68
|
+
isDismissibleMutable
|
|
69
69
|
};
|
|
70
70
|
};
|
|
71
71
|
exports.useToastState = useToastState;
|
|
@@ -55,10 +55,10 @@ export const AnimatedIcon = /*#__PURE__*/memo(({
|
|
|
55
55
|
}) => {
|
|
56
56
|
const progress = useSharedValue(0);
|
|
57
57
|
useEffect(() => {
|
|
58
|
-
progress.
|
|
58
|
+
progress.set(withTiming(1, {
|
|
59
59
|
duration: ICON_ANIMATION_DURATION,
|
|
60
60
|
easing: Easing.out(Easing.back(1.5))
|
|
61
|
-
});
|
|
61
|
+
}));
|
|
62
62
|
}, [progress]);
|
|
63
63
|
const style = useAnimatedStyle(() => ({
|
|
64
64
|
opacity: progress.value,
|
package/lib/module/toast.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { memo, useCallback, useEffect,
|
|
3
|
+
import { memo, useCallback, useEffect, useState } from "react";
|
|
4
4
|
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
5
5
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
6
6
|
import Animated, { interpolate, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
|
@@ -23,69 +23,69 @@ export const ToastContainer = () => {
|
|
|
23
23
|
theme,
|
|
24
24
|
toastsWithIndex,
|
|
25
25
|
isBottom,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
topToastMutable,
|
|
27
|
+
isBottomMutable,
|
|
28
|
+
isDismissibleMutable
|
|
29
29
|
} = useToastState();
|
|
30
30
|
const shouldDismiss = useSharedValue(false);
|
|
31
|
-
const panGesture =
|
|
31
|
+
const panGesture = Gesture.Pan().onStart(() => {
|
|
32
32
|
"worklet";
|
|
33
33
|
|
|
34
34
|
shouldDismiss.set(false);
|
|
35
35
|
}).onUpdate(event => {
|
|
36
36
|
"worklet";
|
|
37
37
|
|
|
38
|
-
if (!
|
|
39
|
-
const ref =
|
|
38
|
+
if (!isDismissibleMutable.value) return;
|
|
39
|
+
const ref = topToastMutable.value;
|
|
40
40
|
if (!ref) return;
|
|
41
41
|
const {
|
|
42
42
|
slot
|
|
43
43
|
} = ref;
|
|
44
|
-
const bottom =
|
|
44
|
+
const bottom = isBottomMutable.value;
|
|
45
45
|
const rawY = event.translationY;
|
|
46
46
|
const dismissDrag = bottom ? rawY : -rawY;
|
|
47
47
|
const resistDrag = bottom ? -rawY : rawY;
|
|
48
48
|
if (dismissDrag > 0) {
|
|
49
49
|
const clampedY = bottom ? Math.min(rawY, MAX_DRAG_CLAMP) : Math.max(rawY, -MAX_DRAG_CLAMP);
|
|
50
|
-
slot.translationY.
|
|
50
|
+
slot.translationY.set(clampedY);
|
|
51
51
|
const shouldTriggerDismiss = dismissDrag > DISMISS_THRESHOLD || (bottom ? event.velocityY > DISMISS_VELOCITY_THRESHOLD : event.velocityY < -DISMISS_VELOCITY_THRESHOLD);
|
|
52
52
|
shouldDismiss.set(shouldTriggerDismiss);
|
|
53
53
|
} else {
|
|
54
54
|
const exponentialDrag = MAX_DRAG_RESISTANCE * (1 - Math.exp(-resistDrag / 250));
|
|
55
|
-
slot.translationY.
|
|
56
|
-
shouldDismiss.
|
|
55
|
+
slot.translationY.set(bottom ? -Math.min(exponentialDrag, MAX_DRAG_RESISTANCE) : Math.min(exponentialDrag, MAX_DRAG_RESISTANCE));
|
|
56
|
+
shouldDismiss.set(false);
|
|
57
57
|
}
|
|
58
58
|
}).onEnd(() => {
|
|
59
59
|
"worklet";
|
|
60
60
|
|
|
61
|
-
if (!
|
|
62
|
-
const ref =
|
|
61
|
+
if (!isDismissibleMutable.value) return;
|
|
62
|
+
const ref = topToastMutable.value;
|
|
63
63
|
if (!ref) return;
|
|
64
64
|
const {
|
|
65
65
|
slot
|
|
66
66
|
} = ref;
|
|
67
|
-
const bottom =
|
|
67
|
+
const bottom = isBottomMutable.value;
|
|
68
68
|
if (shouldDismiss.value) {
|
|
69
|
-
slot.progress.
|
|
69
|
+
slot.progress.set(withTiming(0, {
|
|
70
70
|
duration: EXIT_DURATION,
|
|
71
71
|
easing: EASING
|
|
72
|
-
});
|
|
72
|
+
}));
|
|
73
73
|
const exitOffset = bottom ? SWIPE_EXIT_OFFSET : -SWIPE_EXIT_OFFSET;
|
|
74
|
-
slot.translationY.
|
|
74
|
+
slot.translationY.set(withTiming(slot.translationY.value + exitOffset, {
|
|
75
75
|
duration: EXIT_DURATION,
|
|
76
76
|
easing: EASING
|
|
77
|
-
});
|
|
77
|
+
}));
|
|
78
78
|
scheduleOnRN(ref.dismiss);
|
|
79
79
|
} else {
|
|
80
|
-
slot.translationY.
|
|
80
|
+
slot.translationY.set(withTiming(0, {
|
|
81
81
|
duration: SPRING_BACK_DURATION,
|
|
82
82
|
easing: EASING
|
|
83
|
-
});
|
|
83
|
+
}));
|
|
84
84
|
}
|
|
85
|
-
})
|
|
85
|
+
});
|
|
86
86
|
const registerTopToast = useCallback(values => {
|
|
87
|
-
|
|
88
|
-
}, [
|
|
87
|
+
topToastMutable.set(values);
|
|
88
|
+
}, [topToastMutable]);
|
|
89
89
|
if (visibleToasts.length === 0) return null;
|
|
90
90
|
const inset = isBottom ? bottom : top;
|
|
91
91
|
const positionStyle = isBottom ? {
|
|
@@ -131,13 +131,13 @@ const ToastItem = ({
|
|
|
131
131
|
|
|
132
132
|
// biome-ignore lint/correctness/useExhaustiveDependencies: mount-only effect
|
|
133
133
|
useEffect(() => {
|
|
134
|
-
slot.progress.
|
|
135
|
-
slot.translationY.
|
|
136
|
-
slot.stackIndex.
|
|
137
|
-
slot.progress.
|
|
134
|
+
slot.progress.set(0);
|
|
135
|
+
slot.translationY.set(0);
|
|
136
|
+
slot.stackIndex.set(index);
|
|
137
|
+
slot.progress.set(withTiming(1, {
|
|
138
138
|
duration: ENTRY_DURATION,
|
|
139
139
|
easing: EASING
|
|
140
|
-
});
|
|
140
|
+
}));
|
|
141
141
|
const iconTimeout = setTimeout(() => setShowIcon(true), 50);
|
|
142
142
|
return () => {
|
|
143
143
|
clearTimeout(iconTimeout);
|
|
@@ -151,20 +151,20 @@ const ToastItem = ({
|
|
|
151
151
|
let loadingTimeout = null;
|
|
152
152
|
if (toast.isExiting && !tracker.wasExiting) {
|
|
153
153
|
tracker.wasExiting = true;
|
|
154
|
-
slot.progress.
|
|
154
|
+
slot.progress.set(withTiming(0, {
|
|
155
155
|
duration: EXIT_DURATION,
|
|
156
156
|
easing: EASING
|
|
157
|
-
});
|
|
158
|
-
slot.translationY.
|
|
157
|
+
}));
|
|
158
|
+
slot.translationY.set(withTiming(exitToY, {
|
|
159
159
|
duration: EXIT_DURATION,
|
|
160
160
|
easing: EASING
|
|
161
|
-
});
|
|
161
|
+
}));
|
|
162
162
|
}
|
|
163
163
|
if (tracker.initialized && index !== tracker.prevIndex) {
|
|
164
|
-
slot.stackIndex.
|
|
164
|
+
slot.stackIndex.set(withTiming(index, {
|
|
165
165
|
duration: STACK_TRANSITION_DURATION,
|
|
166
166
|
easing: EASING
|
|
167
|
-
});
|
|
167
|
+
}));
|
|
168
168
|
}
|
|
169
169
|
tracker.prevIndex = index;
|
|
170
170
|
tracker.initialized = true;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo,
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { makeMutable } from "react-native-reanimated";
|
|
5
5
|
import { toastStore } from "./toast-store.js";
|
|
6
6
|
export const useToastState = () => {
|
|
7
7
|
const [visibleToasts, setVisibleToasts] = useState([]);
|
|
8
8
|
const [theme, setTheme] = useState(() => toastStore.getTheme());
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
9
|
+
const [topToastMutable] = useState(() => makeMutable(null));
|
|
10
|
+
const [isBottomMutable] = useState(() => makeMutable(theme.position === "bottom"));
|
|
11
|
+
const [isDismissibleMutable] = useState(() => makeMutable(true));
|
|
12
12
|
const isBottom = theme.position === "bottom";
|
|
13
13
|
const topToast = visibleToasts.find(t => !t.isExiting);
|
|
14
14
|
const isTopDismissible = topToast?.options?.dismissible ?? theme.dismissible;
|
|
@@ -17,8 +17,8 @@ export const useToastState = () => {
|
|
|
17
17
|
const initialTheme = toastStore.getTheme();
|
|
18
18
|
setVisibleToasts(initialToasts);
|
|
19
19
|
const initialTopToast = initialToasts.find(t => !t.isExiting);
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
isBottomMutable.set(initialTheme.position === "bottom");
|
|
21
|
+
isDismissibleMutable.set(initialTopToast?.options?.dismissible ?? initialTheme.dismissible);
|
|
22
22
|
let pendingToasts = null;
|
|
23
23
|
let rafId = null;
|
|
24
24
|
const unsubscribe = toastStore.subscribe(state => {
|
|
@@ -34,13 +34,13 @@ export const useToastState = () => {
|
|
|
34
34
|
rafId = null;
|
|
35
35
|
setTheme(prev => prev === currentTheme ? prev : currentTheme);
|
|
36
36
|
const topToast = currentToasts.find(t => !t.isExiting);
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
isBottomMutable.set(currentTheme.position === "bottom");
|
|
38
|
+
isDismissibleMutable.set(topToast?.options?.dismissible ?? currentTheme.dismissible);
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
return unsubscribe;
|
|
43
|
-
}, []);
|
|
43
|
+
}, [isBottomMutable, isDismissibleMutable]);
|
|
44
44
|
const toastsWithIndex = useMemo(() => {
|
|
45
45
|
const indices = new Map();
|
|
46
46
|
let visualIndex = 0;
|
|
@@ -59,9 +59,9 @@ export const useToastState = () => {
|
|
|
59
59
|
toastsWithIndex,
|
|
60
60
|
isBottom,
|
|
61
61
|
isTopDismissible,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
topToastMutable,
|
|
63
|
+
isBottomMutable,
|
|
64
|
+
isDismissibleMutable
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
67
|
//# sourceMappingURL=use-toast-state.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { TextStyle, ViewStyle } from "react-native";
|
|
3
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
3
4
|
export type ToastType = "success" | "error" | "info" | "loading";
|
|
4
5
|
export type ToastPosition = "top" | "bottom";
|
|
5
6
|
export interface ToastTypeColors {
|
|
@@ -35,7 +36,11 @@ export interface ToastTheme {
|
|
|
35
36
|
position: ToastPosition;
|
|
36
37
|
/** Extra offset from safe area edge (in addition to safe area insets) */
|
|
37
38
|
offset: number;
|
|
38
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Enable right-to-left layout at the code level (reverses icon/text order and text alignment).
|
|
41
|
+
* Only needed when you handle RTL in JavaScript — native RTL (e.g. via `I18nManager.forceRTL`)
|
|
42
|
+
* already flips the entire layout automatically, so this option is unnecessary in that case.
|
|
43
|
+
*/
|
|
39
44
|
rtl: boolean;
|
|
40
45
|
/** Whether to show multiple toasts stacked (default: true). When false, only one toast shows at a time. */
|
|
41
46
|
stacking: boolean;
|
|
@@ -121,15 +126,9 @@ export interface PromiseResult<T> {
|
|
|
121
126
|
}
|
|
122
127
|
export interface TopToastRef {
|
|
123
128
|
slot: {
|
|
124
|
-
progress:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
translationY: {
|
|
128
|
-
value: number;
|
|
129
|
-
};
|
|
130
|
-
stackIndex: {
|
|
131
|
-
value: number;
|
|
132
|
-
};
|
|
129
|
+
progress: SharedValue<number>;
|
|
130
|
+
translationY: SharedValue<number>;
|
|
131
|
+
stackIndex: SharedValue<number>;
|
|
133
132
|
};
|
|
134
133
|
dismiss: () => void;
|
|
135
134
|
}
|
|
@@ -9,8 +9,8 @@ export declare const useToastState: () => {
|
|
|
9
9
|
}[];
|
|
10
10
|
isBottom: boolean;
|
|
11
11
|
isTopDismissible: boolean;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
topToastMutable: import("react-native-reanimated/lib/typescript/commonTypes").Mutable<TopToastRef | null>;
|
|
13
|
+
isBottomMutable: import("react-native-reanimated/lib/typescript/commonTypes").Mutable<boolean>;
|
|
14
|
+
isDismissibleMutable: import("react-native-reanimated/lib/typescript/commonTypes").Mutable<boolean>;
|
|
15
15
|
};
|
|
16
16
|
//# sourceMappingURL=use-toast-state.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-bread",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A lightweight toast library for React Native with premium feeling animations and complex gesture support",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
7
7
|
"types": "lib/typescript/index.d.ts",
|
|
8
8
|
"react-native": "lib/module/index.js",
|
|
9
|
-
"files": [
|
|
9
|
+
"files": [
|
|
10
|
+
"lib",
|
|
11
|
+
"src",
|
|
12
|
+
"!**/__tests__",
|
|
13
|
+
"!**/__fixtures__",
|
|
14
|
+
"!**/__mocks__",
|
|
15
|
+
"!**/*.map",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
10
18
|
"sideEffects": false,
|
|
11
19
|
"repository": {
|
|
12
20
|
"type": "git",
|
|
@@ -31,21 +39,55 @@
|
|
|
31
39
|
"reanimated"
|
|
32
40
|
],
|
|
33
41
|
"scripts": {
|
|
42
|
+
"prepack": "cp ../README.md ./README.md",
|
|
43
|
+
"postpack": "rm ./README.md",
|
|
34
44
|
"build": "bob build",
|
|
35
45
|
"typecheck": "tsc --noEmit",
|
|
36
46
|
"clean": "rm -rf lib",
|
|
37
|
-
"prepare": "bob build"
|
|
47
|
+
"prepare": "bob build",
|
|
48
|
+
"release": "release-it"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"registry": "https://registry.npmjs.org/"
|
|
52
|
+
},
|
|
53
|
+
"release-it": {
|
|
54
|
+
"git": {
|
|
55
|
+
"commitMessage": "chore: release v${version}",
|
|
56
|
+
"tagName": "v${version}"
|
|
57
|
+
},
|
|
58
|
+
"npm": {
|
|
59
|
+
"publish": true,
|
|
60
|
+
"skipChecks": true,
|
|
61
|
+
"publishArgs": [
|
|
62
|
+
"--provenance --access public"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"github": {
|
|
66
|
+
"release": true
|
|
67
|
+
},
|
|
68
|
+
"hooks": {
|
|
69
|
+
"before:init": "bun run build"
|
|
70
|
+
},
|
|
71
|
+
"plugins": {
|
|
72
|
+
"@release-it/conventional-changelog": {
|
|
73
|
+
"preset": "conventionalcommits",
|
|
74
|
+
"infile": "../CHANGELOG.md"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
38
77
|
},
|
|
39
78
|
"devDependencies": {
|
|
79
|
+
"@release-it/conventional-changelog": "^10.0.5",
|
|
80
|
+
"@types/react": "^19.1.0",
|
|
40
81
|
"react": "19.1.0",
|
|
41
82
|
"react-native": "0.81.5",
|
|
42
83
|
"react-native-builder-bob": "^0.35.2",
|
|
43
84
|
"react-native-gesture-handler": "~2.28.0",
|
|
44
85
|
"react-native-reanimated": "~4.2.1",
|
|
45
|
-
"react-native-safe-area-context": "~5.
|
|
86
|
+
"react-native-safe-area-context": "~5.6.0",
|
|
46
87
|
"react-native-screens": "~4.16.0",
|
|
47
88
|
"react-native-svg": "15.12.1",
|
|
48
89
|
"react-native-worklets": "0.7.2",
|
|
90
|
+
"release-it": "^19.2.4",
|
|
49
91
|
"terser": "^5.44.1",
|
|
50
92
|
"typescript": "~5.9.2"
|
|
51
93
|
},
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Easing } from "react-native-reanimated";
|
|
2
|
+
|
|
3
|
+
export const ICON_SIZE = 28;
|
|
4
|
+
export const POOL_SIZE = 5;
|
|
5
|
+
|
|
6
|
+
export const ENTRY_DURATION = 400;
|
|
7
|
+
export const EXIT_DURATION = 350;
|
|
8
|
+
export const STACK_TRANSITION_DURATION = 300;
|
|
9
|
+
export const SPRING_BACK_DURATION = 650;
|
|
10
|
+
export const ICON_ANIMATION_DURATION = 350;
|
|
11
|
+
|
|
12
|
+
export const ENTRY_OFFSET = 80;
|
|
13
|
+
export const EXIT_OFFSET = 100;
|
|
14
|
+
export const SWIPE_EXIT_OFFSET = 200;
|
|
15
|
+
export const MAX_DRAG_CLAMP = 180;
|
|
16
|
+
export const MAX_DRAG_RESISTANCE = 60;
|
|
17
|
+
export const DISMISS_THRESHOLD = 40;
|
|
18
|
+
export const DISMISS_VELOCITY_THRESHOLD = 300;
|
|
19
|
+
|
|
20
|
+
export const STACK_OFFSET_PER_ITEM = 10;
|
|
21
|
+
export const STACK_SCALE_PER_ITEM = 0.05;
|
|
22
|
+
|
|
23
|
+
export const EASING = Easing.bezier(0.25, 0.1, 0.25, 1.0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Svg, { Path, type SvgProps } from "react-native-svg";
|
|
2
|
+
|
|
3
|
+
export const CloseIcon = (props: SvgProps) => (
|
|
4
|
+
<Svg viewBox="0 0 24 24" width={24} height={24} fill="none" {...props}>
|
|
5
|
+
<Path
|
|
6
|
+
fill={props.fill ?? "#8993A4"}
|
|
7
|
+
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
|
|
8
|
+
/>
|
|
9
|
+
</Svg>
|
|
10
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Svg, { Path, type SvgProps } from "react-native-svg";
|
|
2
|
+
|
|
3
|
+
export const GreenCheck = (props: SvgProps) => (
|
|
4
|
+
<Svg viewBox="0 0 30 31" width={30} height={31} fill="none" {...props}>
|
|
5
|
+
<Path
|
|
6
|
+
fill={props.fill ?? "#28B770"}
|
|
7
|
+
fillRule="evenodd"
|
|
8
|
+
d="m19.866 13.152-5.772 5.773a.933.933 0 0 1-1.326 0L9.88 16.039a.938.938 0 0 1 1.325-1.327l2.225 2.224 5.109-5.11a.938.938 0 1 1 1.326 1.326Zm.28-9.652H9.602C5.654 3.5 3 6.276 3 10.409v9.935c0 4.131 2.654 6.906 6.602 6.906h10.543c3.95 0 6.605-2.775 6.605-6.906v-9.935c0-4.133-2.654-6.909-6.604-6.909Z"
|
|
9
|
+
clipRule="evenodd"
|
|
10
|
+
/>
|
|
11
|
+
<Path
|
|
12
|
+
fill="#fff"
|
|
13
|
+
d="m19.866 13.152-5.772 5.773a.933.933 0 0 1-1.326 0L9.88 16.039a.938.938 0 0 1 1.325-1.327l2.225 2.224 5.109-5.11a.938.938 0 1 1 1.326 1.326Z"
|
|
14
|
+
/>
|
|
15
|
+
</Svg>
|
|
16
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Svg, { Path, type SvgProps } from "react-native-svg";
|
|
2
|
+
|
|
3
|
+
export const InfoIcon = (props: SvgProps) => (
|
|
4
|
+
<Svg viewBox="0 0 24 24" width={24} height={24} fill="none" {...props}>
|
|
5
|
+
<Path
|
|
6
|
+
fill={props.fill ?? "#EDBE43"}
|
|
7
|
+
fillRule="evenodd"
|
|
8
|
+
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm1 15h-2v-6h2v6Zm0-8h-2V7h2v2Z"
|
|
9
|
+
clipRule="evenodd"
|
|
10
|
+
/>
|
|
11
|
+
</Svg>
|
|
12
|
+
);
|