react-native-sooner 1.0.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.
Files changed (80) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +376 -0
  3. package/lib/module/constants.js +40 -0
  4. package/lib/module/constants.js.map +1 -0
  5. package/lib/module/context.js +25 -0
  6. package/lib/module/context.js.map +1 -0
  7. package/lib/module/easings.js +9 -0
  8. package/lib/module/easings.js.map +1 -0
  9. package/lib/module/gestures.js +119 -0
  10. package/lib/module/gestures.js.map +1 -0
  11. package/lib/module/hooks.js +9 -0
  12. package/lib/module/hooks.js.map +1 -0
  13. package/lib/module/icons.js +332 -0
  14. package/lib/module/icons.js.map +1 -0
  15. package/lib/module/index.js +13 -0
  16. package/lib/module/index.js.map +1 -0
  17. package/lib/module/package.json +1 -0
  18. package/lib/module/state.js +200 -0
  19. package/lib/module/state.js.map +1 -0
  20. package/lib/module/theme.js +189 -0
  21. package/lib/module/theme.js.map +1 -0
  22. package/lib/module/toast.js +362 -0
  23. package/lib/module/toast.js.map +1 -0
  24. package/lib/module/toaster.js +198 -0
  25. package/lib/module/toaster.js.map +1 -0
  26. package/lib/module/types.js +4 -0
  27. package/lib/module/types.js.map +1 -0
  28. package/lib/module/use-app-state.js +13 -0
  29. package/lib/module/use-app-state.js.map +1 -0
  30. package/lib/module/use-pauseable-timer.js +18 -0
  31. package/lib/module/use-pauseable-timer.js.map +1 -0
  32. package/lib/module/use-toast-state.js +37 -0
  33. package/lib/module/use-toast-state.js.map +1 -0
  34. package/lib/typescript/package.json +1 -0
  35. package/lib/typescript/src/constants.d.ts +32 -0
  36. package/lib/typescript/src/constants.d.ts.map +1 -0
  37. package/lib/typescript/src/context.d.ts +20 -0
  38. package/lib/typescript/src/context.d.ts.map +1 -0
  39. package/lib/typescript/src/easings.d.ts +6 -0
  40. package/lib/typescript/src/easings.d.ts.map +1 -0
  41. package/lib/typescript/src/gestures.d.ts +17 -0
  42. package/lib/typescript/src/gestures.d.ts.map +1 -0
  43. package/lib/typescript/src/hooks.d.ts +5 -0
  44. package/lib/typescript/src/hooks.d.ts.map +1 -0
  45. package/lib/typescript/src/icons.d.ts +15 -0
  46. package/lib/typescript/src/icons.d.ts.map +1 -0
  47. package/lib/typescript/src/index.d.ts +12 -0
  48. package/lib/typescript/src/index.d.ts.map +1 -0
  49. package/lib/typescript/src/state.d.ts +66 -0
  50. package/lib/typescript/src/state.d.ts.map +1 -0
  51. package/lib/typescript/src/theme.d.ts +163 -0
  52. package/lib/typescript/src/theme.d.ts.map +1 -0
  53. package/lib/typescript/src/toast.d.ts +3 -0
  54. package/lib/typescript/src/toast.d.ts.map +1 -0
  55. package/lib/typescript/src/toaster.d.ts +3 -0
  56. package/lib/typescript/src/toaster.d.ts.map +1 -0
  57. package/lib/typescript/src/types.d.ts +264 -0
  58. package/lib/typescript/src/types.d.ts.map +1 -0
  59. package/lib/typescript/src/use-app-state.d.ts +3 -0
  60. package/lib/typescript/src/use-app-state.d.ts.map +1 -0
  61. package/lib/typescript/src/use-pauseable-timer.d.ts +2 -0
  62. package/lib/typescript/src/use-pauseable-timer.d.ts.map +1 -0
  63. package/lib/typescript/src/use-toast-state.d.ts +7 -0
  64. package/lib/typescript/src/use-toast-state.d.ts.map +1 -0
  65. package/package.json +152 -0
  66. package/src/constants.ts +44 -0
  67. package/src/context.tsx +38 -0
  68. package/src/easings.ts +7 -0
  69. package/src/gestures.tsx +135 -0
  70. package/src/hooks.ts +3 -0
  71. package/src/icons.tsx +227 -0
  72. package/src/index.tsx +48 -0
  73. package/src/state.ts +262 -0
  74. package/src/theme.ts +170 -0
  75. package/src/toast.tsx +429 -0
  76. package/src/toaster.tsx +221 -0
  77. package/src/types.ts +311 -0
  78. package/src/use-app-state.ts +15 -0
  79. package/src/use-pauseable-timer.ts +24 -0
  80. package/src/use-toast-state.ts +43 -0
@@ -0,0 +1,38 @@
1
+ import React, { createContext, useContext } from "react";
2
+ import type { Position, SwipeDirection, ToastIcons, ToastStyles } from "./types";
3
+
4
+ export interface ToasterContextValue {
5
+ position: Position;
6
+ theme: "light" | "dark";
7
+ gap: number;
8
+ swipeToDismiss: boolean;
9
+ swipeDirection: SwipeDirection[];
10
+ richColors: boolean;
11
+ closeButton: boolean;
12
+ icons?: ToastIcons;
13
+ toastStyles?: ToastStyles;
14
+ }
15
+
16
+ const ToasterContext = createContext<ToasterContextValue | null>(null);
17
+
18
+ export function ToasterProvider({
19
+ children,
20
+ value,
21
+ }: {
22
+ children: React.ReactNode;
23
+ value: ToasterContextValue;
24
+ }) {
25
+ return <ToasterContext.Provider value={value}>{children}</ToasterContext.Provider>;
26
+ }
27
+
28
+ export function useToasterContext(): ToasterContextValue {
29
+ const context = useContext(ToasterContext);
30
+ if (!context) {
31
+ throw new Error("useToasterContext must be used within a ToasterProvider");
32
+ }
33
+ return context;
34
+ }
35
+
36
+ export function useToasterContextOptional(): ToasterContextValue | null {
37
+ return useContext(ToasterContext);
38
+ }
package/src/easings.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Easing } from "react-native-reanimated";
2
+
3
+ export const easeOutQuad = Easing.bezier(0.25, 0.46, 0.45, 0.94);
4
+ export const easeOutCubic = Easing.bezier(0.215, 0.61, 0.355, 1);
5
+ export const easeInOutCubic = Easing.bezier(0.645, 0.045, 0.355, 1);
6
+ export const easeOutCirc = Easing.bezier(0.075, 0.82, 0.165, 1);
7
+ export const easeOutExpo = Easing.bezier(0.19, 1, 0.22, 1);
@@ -0,0 +1,135 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ViewStyle } from "react-native";
3
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
+ import Animated, {
5
+ runOnJS,
6
+ useAnimatedStyle,
7
+ useSharedValue,
8
+ withTiming,
9
+ } from "react-native-reanimated";
10
+ import { DAMPING_FACTOR, SWIPE_THRESHOLD, VELOCITY_THRESHOLD } from "./constants";
11
+ import type { SwipeDirection } from "./types";
12
+
13
+ export interface SwipeHandlerProps {
14
+ children: ReactNode;
15
+ enabled: boolean;
16
+ swipeDirection: SwipeDirection[];
17
+ onDismiss: () => void;
18
+ style?: ViewStyle;
19
+ }
20
+
21
+ export function SwipeHandler({
22
+ children,
23
+ enabled,
24
+ swipeDirection,
25
+ onDismiss,
26
+ style,
27
+ }: SwipeHandlerProps) {
28
+ const translateX = useSharedValue(0);
29
+ const opacity = useSharedValue(1);
30
+ const isDragging = useSharedValue(false);
31
+
32
+ const canSwipeLeft = swipeDirection.includes("left");
33
+ const canSwipeRight = swipeDirection.includes("right");
34
+
35
+ const panGesture = Gesture.Pan()
36
+ .enabled(enabled)
37
+ .onStart(() => {
38
+ "worklet";
39
+ isDragging.value = true;
40
+ })
41
+ .onUpdate((event) => {
42
+ "worklet";
43
+ const { translationX } = event;
44
+
45
+ if (canSwipeLeft && translationX < 0) {
46
+ translateX.value = translationX;
47
+ } else if (canSwipeRight && translationX > 0) {
48
+ translateX.value = translationX;
49
+ }
50
+
51
+ if (Math.abs(translateX.value) > SWIPE_THRESHOLD) {
52
+ const excess = Math.abs(translateX.value) - SWIPE_THRESHOLD;
53
+ const direction = translateX.value > 0 ? 1 : -1;
54
+ translateX.value = direction * (SWIPE_THRESHOLD + excess * DAMPING_FACTOR);
55
+ }
56
+ })
57
+ .onEnd((event) => {
58
+ "worklet";
59
+ isDragging.value = false;
60
+
61
+ const shouldDismiss =
62
+ Math.abs(event.translationX) > SWIPE_THRESHOLD ||
63
+ Math.abs(event.velocityX) > VELOCITY_THRESHOLD;
64
+
65
+ if (shouldDismiss) {
66
+ const direction = event.translationX > 0 ? 1 : -1;
67
+ translateX.value = withTiming(direction * 300, { duration: 200 });
68
+ opacity.value = withTiming(0, { duration: 200 });
69
+ runOnJS(onDismiss)();
70
+ } else {
71
+ translateX.value = withTiming(0, { duration: 150 });
72
+ }
73
+ });
74
+
75
+ const animatedStyle = useAnimatedStyle(() => ({
76
+ transform: [{ translateX: translateX.value }],
77
+ opacity: opacity.value,
78
+ }));
79
+
80
+ return (
81
+ <GestureDetector gesture={panGesture}>
82
+ <Animated.View style={style ? [style, animatedStyle] : animatedStyle}>
83
+ {children}
84
+ </Animated.View>
85
+ </GestureDetector>
86
+ );
87
+ }
88
+
89
+ export function useSwipeGesture(
90
+ enabled: boolean,
91
+ swipeDirection: SwipeDirection[],
92
+ onDismiss: () => void
93
+ ) {
94
+ const translateX = useSharedValue(0);
95
+ const opacity = useSharedValue(1);
96
+
97
+ const canSwipeLeft = swipeDirection.includes("left");
98
+ const canSwipeRight = swipeDirection.includes("right");
99
+
100
+ const gesture = Gesture.Pan()
101
+ .enabled(enabled)
102
+ .onUpdate((event) => {
103
+ "worklet";
104
+ const { translationX } = event;
105
+
106
+ if (canSwipeLeft && translationX < 0) {
107
+ translateX.value = translationX;
108
+ } else if (canSwipeRight && translationX > 0) {
109
+ translateX.value = translationX;
110
+ }
111
+
112
+ if (Math.abs(translateX.value) > SWIPE_THRESHOLD) {
113
+ const excess = Math.abs(translateX.value) - SWIPE_THRESHOLD;
114
+ const direction = translateX.value > 0 ? 1 : -1;
115
+ translateX.value = direction * (SWIPE_THRESHOLD + excess * DAMPING_FACTOR);
116
+ }
117
+ })
118
+ .onEnd((event) => {
119
+ "worklet";
120
+ const shouldDismiss =
121
+ Math.abs(event.translationX) > SWIPE_THRESHOLD ||
122
+ Math.abs(event.velocityX) > VELOCITY_THRESHOLD;
123
+
124
+ if (shouldDismiss) {
125
+ const direction = event.translationX > 0 ? 1 : -1;
126
+ translateX.value = withTiming(direction * 300, { duration: 200 });
127
+ opacity.value = withTiming(0, { duration: 200 });
128
+ runOnJS(onDismiss)();
129
+ } else {
130
+ translateX.value = withTiming(0, { duration: 150 });
131
+ }
132
+ });
133
+
134
+ return { gesture, translateX, opacity };
135
+ }
package/src/hooks.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { useAppState } from "./use-app-state";
2
+ export { usePauseableTimer } from "./use-pauseable-timer";
3
+ export { useToastState } from "./use-toast-state";
package/src/icons.tsx ADDED
@@ -0,0 +1,227 @@
1
+ import React from "react";
2
+ import { ActivityIndicator, StyleSheet, View } from "react-native";
3
+ import type { ToastType } from "./types";
4
+
5
+ // Try to import react-native-svg, fall back to simple views if not available
6
+ let Svg: typeof import("react-native-svg").default | null = null;
7
+ let Circle: typeof import("react-native-svg").Circle | null = null;
8
+ let Path: typeof import("react-native-svg").Path | null = null;
9
+
10
+ try {
11
+ const svg = require("react-native-svg");
12
+ Svg = svg.default || svg.Svg;
13
+ Circle = svg.Circle;
14
+ Path = svg.Path;
15
+ } catch {
16
+ // react-native-svg not installed, will use fallback icons
17
+ }
18
+
19
+ interface IconProps {
20
+ size?: number;
21
+ color?: string;
22
+ }
23
+
24
+ const DEFAULT_SIZE = 20;
25
+
26
+ export function SuccessIcon({ size = DEFAULT_SIZE, color = "#22c55e" }: IconProps) {
27
+ if (Svg && Circle && Path) {
28
+ return (
29
+ <Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
30
+ <Circle cx="10" cy="10" r="9" stroke={color} strokeWidth="1.5" />
31
+ <Path
32
+ d="M6 10.5L8.5 13L14 7"
33
+ stroke={color}
34
+ strokeWidth="1.5"
35
+ strokeLinecap="round"
36
+ strokeLinejoin="round"
37
+ />
38
+ </Svg>
39
+ );
40
+ }
41
+
42
+ return (
43
+ <View style={[styles.fallbackIcon, { width: size, height: size, borderColor: color }]}>
44
+ <View style={[styles.checkmark, { borderColor: color }]} />
45
+ </View>
46
+ );
47
+ }
48
+
49
+ export function ErrorIcon({ size = DEFAULT_SIZE, color = "#ef4444" }: IconProps) {
50
+ if (Svg && Circle && Path) {
51
+ return (
52
+ <Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
53
+ <Circle cx="10" cy="10" r="9" stroke={color} strokeWidth="1.5" />
54
+ <Path
55
+ d="M7 7L13 13M13 7L7 13"
56
+ stroke={color}
57
+ strokeWidth="1.5"
58
+ strokeLinecap="round"
59
+ />
60
+ </Svg>
61
+ );
62
+ }
63
+
64
+ return (
65
+ <View style={[styles.fallbackIcon, { width: size, height: size, borderColor: color }]}>
66
+ <View style={[styles.xMark, { backgroundColor: color }]} />
67
+ <View style={[styles.xMark, styles.xMarkRotated, { backgroundColor: color }]} />
68
+ </View>
69
+ );
70
+ }
71
+
72
+ export function WarningIcon({ size = DEFAULT_SIZE, color = "#f59e0b" }: IconProps) {
73
+ if (Svg && Path && Circle) {
74
+ return (
75
+ <Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
76
+ <Path
77
+ d="M10 2L19 18H1L10 2Z"
78
+ stroke={color}
79
+ strokeWidth="1.5"
80
+ strokeLinejoin="round"
81
+ />
82
+ <Path
83
+ d="M10 8V11"
84
+ stroke={color}
85
+ strokeWidth="1.5"
86
+ strokeLinecap="round"
87
+ />
88
+ <Circle cx="10" cy="14" r="0.75" fill={color} />
89
+ </Svg>
90
+ );
91
+ }
92
+
93
+ return (
94
+ <View style={[styles.triangleContainer, { width: size, height: size }]}>
95
+ <View style={[styles.triangle, { borderBottomColor: color }]} />
96
+ </View>
97
+ );
98
+ }
99
+
100
+ export function InfoIcon({ size = DEFAULT_SIZE, color = "#3b82f6" }: IconProps) {
101
+ if (Svg && Circle && Path) {
102
+ return (
103
+ <Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
104
+ <Circle cx="10" cy="10" r="9" stroke={color} strokeWidth="1.5" />
105
+ <Path
106
+ d="M10 9V14"
107
+ stroke={color}
108
+ strokeWidth="1.5"
109
+ strokeLinecap="round"
110
+ />
111
+ <Circle cx="10" cy="6.5" r="0.75" fill={color} />
112
+ </Svg>
113
+ );
114
+ }
115
+
116
+ return (
117
+ <View style={[styles.fallbackIcon, { width: size, height: size, borderColor: color }]}>
118
+ <View style={[styles.infoLine, { backgroundColor: color }]} />
119
+ <View style={[styles.infoDot, { backgroundColor: color }]} />
120
+ </View>
121
+ );
122
+ }
123
+
124
+ export function LoadingIcon({ size = DEFAULT_SIZE, color = "#6b7280" }: IconProps) {
125
+ return <ActivityIndicator size="small" color={color} style={{ width: size, height: size }} />;
126
+ }
127
+
128
+ export function CloseIcon({ size = 12, color = "#9ca3af" }: IconProps) {
129
+ if (Svg && Path) {
130
+ return (
131
+ <Svg width={size} height={size} viewBox="0 0 12 12" fill="none">
132
+ <Path
133
+ d="M2 2L10 10M10 2L2 10"
134
+ stroke={color}
135
+ strokeWidth="1.5"
136
+ strokeLinecap="round"
137
+ />
138
+ </Svg>
139
+ );
140
+ }
141
+
142
+ return (
143
+ <View style={{ width: size, height: size, justifyContent: "center", alignItems: "center" }}>
144
+ <View style={[styles.closeX, { backgroundColor: color }]} />
145
+ <View style={[styles.closeX, styles.closeXRotated, { backgroundColor: color }]} />
146
+ </View>
147
+ );
148
+ }
149
+
150
+ export function getIcon(type: ToastType, color?: string): React.ReactNode {
151
+ switch (type) {
152
+ case "success":
153
+ return <SuccessIcon color={color} />;
154
+ case "error":
155
+ return <ErrorIcon color={color} />;
156
+ case "warning":
157
+ return <WarningIcon color={color} />;
158
+ case "info":
159
+ return <InfoIcon color={color} />;
160
+ case "loading":
161
+ return <LoadingIcon color={color} />;
162
+ default:
163
+ return null;
164
+ }
165
+ }
166
+
167
+ const styles = StyleSheet.create({
168
+ fallbackIcon: {
169
+ borderWidth: 1.5,
170
+ borderRadius: 100,
171
+ justifyContent: "center",
172
+ alignItems: "center",
173
+ },
174
+ checkmark: {
175
+ width: 6,
176
+ height: 10,
177
+ borderBottomWidth: 1.5,
178
+ borderRightWidth: 1.5,
179
+ transform: [{ rotate: "45deg" }, { translateY: -1 }],
180
+ },
181
+ xMark: {
182
+ position: "absolute",
183
+ width: 8,
184
+ height: 1.5,
185
+ borderRadius: 1,
186
+ transform: [{ rotate: "45deg" }],
187
+ },
188
+ xMarkRotated: {
189
+ transform: [{ rotate: "-45deg" }],
190
+ },
191
+ triangleContainer: {
192
+ justifyContent: "center",
193
+ alignItems: "center",
194
+ },
195
+ triangle: {
196
+ width: 0,
197
+ height: 0,
198
+ borderLeftWidth: 8,
199
+ borderRightWidth: 8,
200
+ borderBottomWidth: 14,
201
+ borderLeftColor: "transparent",
202
+ borderRightColor: "transparent",
203
+ },
204
+ infoLine: {
205
+ width: 1.5,
206
+ height: 6,
207
+ borderRadius: 1,
208
+ marginTop: 2,
209
+ },
210
+ infoDot: {
211
+ width: 2,
212
+ height: 2,
213
+ borderRadius: 1,
214
+ position: "absolute",
215
+ top: 4,
216
+ },
217
+ closeX: {
218
+ position: "absolute",
219
+ width: 10,
220
+ height: 1.5,
221
+ borderRadius: 1,
222
+ transform: [{ rotate: "45deg" }],
223
+ },
224
+ closeXRotated: {
225
+ transform: [{ rotate: "-45deg" }],
226
+ },
227
+ });
package/src/index.tsx ADDED
@@ -0,0 +1,48 @@
1
+ export { Toaster } from "./toaster";
2
+ export { Toast } from "./toast";
3
+ export { toast, ToastState } from "./state";
4
+ export { useToastState, useAppState, usePauseableTimer } from "./hooks";
5
+ export { ToasterProvider, useToasterContext, useToasterContextOptional } from "./context";
6
+
7
+ export type {
8
+ AccessibilityConfig,
9
+ Action,
10
+ AnimationConfig,
11
+ ExternalToast,
12
+ HeightT,
13
+ Position,
14
+ PromiseData,
15
+ ResolvedTheme,
16
+ SwipeDirection,
17
+ Theme,
18
+ ToasterProps,
19
+ ToastIcons,
20
+ ToastProps,
21
+ ToastStyles,
22
+ ToastT,
23
+ ToastType,
24
+ UpdateToastOptions,
25
+ } from "./types";
26
+
27
+ export {
28
+ CloseIcon,
29
+ ErrorIcon,
30
+ InfoIcon,
31
+ LoadingIcon,
32
+ SuccessIcon,
33
+ WarningIcon,
34
+ getIcon,
35
+ } from "./icons";
36
+
37
+ export { colors, richColors, getToastColors, getIconColor, resolveTheme, baseStyles } from "./theme";
38
+
39
+ export {
40
+ ANIMATION_DEFAULTS,
41
+ ENTRY_OFFSET,
42
+ SWIPE_THRESHOLD,
43
+ VELOCITY_THRESHOLD,
44
+ toastDefaults,
45
+ } from "./constants";
46
+
47
+ export { easeOutQuad, easeOutCubic, easeInOutCubic, easeOutCirc, easeOutExpo } from "./easings";
48
+ export { SwipeHandler, useSwipeGesture } from "./gestures";