@mehul214/react-native-flash-toast 0.0.0-stage → 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.
package/dist/index.js ADDED
@@ -0,0 +1,613 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+ var reactNative = require('react-native');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var React__default = /*#__PURE__*/_interopDefault(React);
12
+
13
+ // src/FlashMessage.tsx
14
+
15
+ // src/FlashMessageManager.ts
16
+ var FlashMessageManager = class {
17
+ constructor() {
18
+ this.defaultInstance = null;
19
+ this.instances = /* @__PURE__ */ new Map();
20
+ this.pendingQueue = [];
21
+ }
22
+ register(ref, id = "default") {
23
+ this.instances.set(id, ref);
24
+ if (!this.defaultInstance || id === "default") {
25
+ this.defaultInstance = ref;
26
+ }
27
+ if (this.pendingQueue.length > 0) {
28
+ const next = this.pendingQueue.shift();
29
+ if (next) {
30
+ ref.showMessage(next);
31
+ }
32
+ }
33
+ }
34
+ unregister(id = "default") {
35
+ this.instances.delete(id);
36
+ if (this.defaultInstance === this.instances.get(id)) {
37
+ this.defaultInstance = this.instances.values().next().value || null;
38
+ }
39
+ }
40
+ showMessage(options, targetId) {
41
+ const target = targetId ? this.instances.get(targetId) : this.defaultInstance;
42
+ if (target) {
43
+ target.showMessage(options);
44
+ } else {
45
+ this.pendingQueue.push(options);
46
+ }
47
+ }
48
+ hideMessage(targetId) {
49
+ const target = targetId ? this.instances.get(targetId) : this.defaultInstance;
50
+ if (target) {
51
+ target.hideMessage();
52
+ }
53
+ }
54
+ };
55
+ var flashManager = new FlashMessageManager();
56
+ function showMessage(options) {
57
+ flashManager.showMessage(options);
58
+ }
59
+ function hideMessage() {
60
+ flashManager.hideMessage();
61
+ }
62
+ var flash = showMessage;
63
+ var dismissFlash = hideMessage;
64
+ var BuiltInIcon = ({
65
+ type,
66
+ size = 22,
67
+ color = "#FFFFFF"
68
+ }) => {
69
+ const containerStyle = [
70
+ styles.circle,
71
+ {
72
+ width: size,
73
+ height: size,
74
+ borderRadius: size / 2,
75
+ borderColor: color
76
+ }
77
+ ];
78
+ switch (type) {
79
+ case "success":
80
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.symbol, { color, fontSize: size * 0.65 }], children: "\u2713" }) });
81
+ case "danger":
82
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.symbol, { color, fontSize: size * 0.65 }], children: "\u2715" }) });
83
+ case "warning":
84
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.symbol, { color, fontSize: size * 0.65 }], children: "!" }) });
85
+ case "info":
86
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.symbol, { color, fontSize: size * 0.65 }], children: "\u2139" }) });
87
+ case "default":
88
+ default:
89
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.symbol, { color, fontSize: size * 0.6 }], children: "\u25CF" }) });
90
+ }
91
+ };
92
+ var styles = reactNative.StyleSheet.create({
93
+ circle: {
94
+ borderWidth: 1.5,
95
+ justifyContent: "center",
96
+ alignItems: "center"
97
+ },
98
+ symbol: {
99
+ fontWeight: "800",
100
+ textAlign: "center",
101
+ includeFontPadding: false,
102
+ lineHeight: 22
103
+ }
104
+ });
105
+ var COLORS = {
106
+ default: {
107
+ background: "#1F2937",
108
+ // Slate 800
109
+ text: "#FFFFFF",
110
+ icon: "#9CA3AF"
111
+ },
112
+ success: {
113
+ background: "#059669",
114
+ // Emerald 600
115
+ text: "#FFFFFF",
116
+ icon: "#D1FAE5"
117
+ },
118
+ danger: {
119
+ background: "#DC2626",
120
+ // Red 600
121
+ text: "#FFFFFF",
122
+ icon: "#FEE2E2"
123
+ },
124
+ warning: {
125
+ background: "#D97706",
126
+ // Amber 600
127
+ text: "#FFFFFF",
128
+ icon: "#FEF3C7"
129
+ },
130
+ info: {
131
+ background: "#2563EB",
132
+ // Blue 600
133
+ text: "#FFFFFF",
134
+ icon: "#DBEAFE"
135
+ }
136
+ };
137
+ var styles2 = reactNative.StyleSheet.create({
138
+ rootOverlay: {
139
+ position: "absolute",
140
+ left: 0,
141
+ right: 0,
142
+ zIndex: 99999,
143
+ elevation: 99999
144
+ },
145
+ rootTop: {
146
+ top: 0
147
+ },
148
+ rootBottom: {
149
+ bottom: 0
150
+ },
151
+ rootCenter: {
152
+ top: 0,
153
+ bottom: 0,
154
+ justifyContent: "center",
155
+ alignItems: "center"
156
+ },
157
+ toastContainer: {
158
+ flexDirection: "row",
159
+ alignItems: "center",
160
+ overflow: "hidden",
161
+ ...reactNative.Platform.select({
162
+ ios: {
163
+ shadowColor: "#000000",
164
+ shadowOffset: { width: 0, height: 6 },
165
+ shadowOpacity: 0.18,
166
+ shadowRadius: 12
167
+ },
168
+ android: {
169
+ elevation: 8
170
+ },
171
+ web: {
172
+ boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.2), 0 8px 10px -6px rgba(0, 0, 0, 0.1)"
173
+ }
174
+ })
175
+ },
176
+ floatingContainer: {
177
+ marginHorizontal: 16,
178
+ borderRadius: 14,
179
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
180
+ borderColor: "rgba(255, 255, 255, 0.15)"
181
+ },
182
+ edgeToEdgeContainer: {
183
+ marginHorizontal: 0,
184
+ borderRadius: 0
185
+ },
186
+ contentWrapper: {
187
+ flex: 1,
188
+ flexDirection: "row",
189
+ alignItems: "center",
190
+ paddingVertical: 14,
191
+ paddingHorizontal: 16
192
+ },
193
+ iconContainer: {
194
+ marginRight: 12,
195
+ justifyContent: "center",
196
+ alignItems: "center"
197
+ },
198
+ textContainer: {
199
+ flex: 1,
200
+ justifyContent: "center"
201
+ },
202
+ titleText: {
203
+ fontSize: 15,
204
+ fontWeight: "600",
205
+ letterSpacing: -0.2,
206
+ color: "#FFFFFF"
207
+ },
208
+ descriptionText: {
209
+ fontSize: 13,
210
+ fontWeight: "400",
211
+ color: "rgba(255, 255, 255, 0.88)",
212
+ marginTop: 2,
213
+ lineHeight: 18
214
+ },
215
+ actionButton: {
216
+ marginLeft: 10,
217
+ paddingHorizontal: 12,
218
+ paddingVertical: 6,
219
+ borderRadius: 8,
220
+ backgroundColor: "rgba(255, 255, 255, 0.2)"
221
+ },
222
+ actionButtonText: {
223
+ fontSize: 13,
224
+ fontWeight: "700",
225
+ color: "#FFFFFF"
226
+ },
227
+ progressBarContainer: {
228
+ position: "absolute",
229
+ bottom: 0,
230
+ left: 0,
231
+ right: 0,
232
+ height: 3,
233
+ backgroundColor: "rgba(255, 255, 255, 0.25)"
234
+ },
235
+ progressBarFill: {
236
+ height: "100%",
237
+ backgroundColor: "#FFFFFF"
238
+ }
239
+ });
240
+ function getDefaultSafeAreaInsets() {
241
+ const { width, height } = reactNative.Dimensions.get("window");
242
+ const isLandscape = width > height;
243
+ if (reactNative.Platform.OS === "android") {
244
+ return {
245
+ top: reactNative.StatusBar.currentHeight ?? 24,
246
+ bottom: 16,
247
+ left: 0,
248
+ right: 0
249
+ };
250
+ }
251
+ if (reactNative.Platform.OS === "ios") {
252
+ const isModernIphone = !reactNative.Platform.isPad && !reactNative.Platform.isTV && (height >= 812 || width >= 812);
253
+ return {
254
+ top: isModernIphone ? isLandscape ? 0 : 50 : 20,
255
+ bottom: isModernIphone ? 34 : 0,
256
+ left: 0,
257
+ right: 0
258
+ };
259
+ }
260
+ return {
261
+ top: 16,
262
+ bottom: 16,
263
+ left: 0,
264
+ right: 0
265
+ };
266
+ }
267
+ var FlashMessage = React__default.default.forwardRef(
268
+ (props, ref) => {
269
+ const {
270
+ position: defaultPosition = "top",
271
+ duration: defaultDuration = 3e3,
272
+ floating: defaultFloating = true,
273
+ statusBarOffset,
274
+ bottomOffset,
275
+ style: containerCustomStyle
276
+ } = props;
277
+ const [currentMessage, setCurrentMessage] = React.useState(null);
278
+ const [isVisible, setIsVisible] = React.useState(false);
279
+ const translateY = React.useRef(new reactNative.Animated.Value(-150)).current;
280
+ const opacity = React.useRef(new reactNative.Animated.Value(0)).current;
281
+ const progress = React.useRef(new reactNative.Animated.Value(1)).current;
282
+ const hideTimeoutRef = React.useRef(null);
283
+ const progressAnimationRef = React.useRef(null);
284
+ const remainingTimeRef = React.useRef(0);
285
+ const timerStartedAtRef = React.useRef(0);
286
+ const safeInsets = getDefaultSafeAreaInsets();
287
+ const position = currentMessage?.position ?? defaultPosition;
288
+ const isTop = position === "top";
289
+ const isBottom = position === "bottom";
290
+ const clearTimers = React.useCallback(() => {
291
+ if (hideTimeoutRef.current) {
292
+ clearTimeout(hideTimeoutRef.current);
293
+ hideTimeoutRef.current = null;
294
+ }
295
+ if (progressAnimationRef.current) {
296
+ progressAnimationRef.current.stop();
297
+ progressAnimationRef.current = null;
298
+ }
299
+ }, []);
300
+ const animateHide = React.useCallback(
301
+ (callback) => {
302
+ clearTimers();
303
+ const targetY = isTop ? -180 : isBottom ? 180 : 0;
304
+ reactNative.Animated.parallel([
305
+ reactNative.Animated.timing(translateY, {
306
+ toValue: targetY,
307
+ duration: 220,
308
+ useNativeDriver: true
309
+ }),
310
+ reactNative.Animated.timing(opacity, {
311
+ toValue: 0,
312
+ duration: 200,
313
+ useNativeDriver: true
314
+ })
315
+ ]).start(() => {
316
+ setIsVisible(false);
317
+ if (currentMessage?.onHide) {
318
+ currentMessage.onHide();
319
+ }
320
+ setCurrentMessage(null);
321
+ callback?.();
322
+ });
323
+ },
324
+ [clearTimers, isTop, isBottom, opacity, translateY, currentMessage]
325
+ );
326
+ const startAutoHideTimer = React.useCallback(
327
+ (durationMs) => {
328
+ clearTimers();
329
+ if (durationMs <= 0) return;
330
+ remainingTimeRef.current = durationMs;
331
+ timerStartedAtRef.current = Date.now();
332
+ progress.setValue(1);
333
+ progressAnimationRef.current = reactNative.Animated.timing(progress, {
334
+ toValue: 0,
335
+ duration: durationMs,
336
+ useNativeDriver: false
337
+ // width needs layout or scaleX
338
+ });
339
+ progressAnimationRef.current.start();
340
+ hideTimeoutRef.current = setTimeout(() => {
341
+ animateHide();
342
+ }, durationMs);
343
+ },
344
+ [animateHide, clearTimers, progress]
345
+ );
346
+ const pauseTimer = React.useCallback(() => {
347
+ if (hideTimeoutRef.current && timerStartedAtRef.current > 0) {
348
+ clearTimeout(hideTimeoutRef.current);
349
+ hideTimeoutRef.current = null;
350
+ const elapsed = Date.now() - timerStartedAtRef.current;
351
+ remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsed);
352
+ if (progressAnimationRef.current) {
353
+ progressAnimationRef.current.stop();
354
+ }
355
+ }
356
+ }, []);
357
+ const resumeTimer = React.useCallback(() => {
358
+ if (remainingTimeRef.current > 0) {
359
+ startAutoHideTimer(remainingTimeRef.current);
360
+ }
361
+ }, [startAutoHideTimer]);
362
+ const show = React.useCallback(
363
+ (options) => {
364
+ clearTimers();
365
+ setCurrentMessage(options);
366
+ setIsVisible(true);
367
+ const pos = options.position ?? defaultPosition;
368
+ const initialY = pos === "top" ? -150 : pos === "bottom" ? 150 : 0;
369
+ translateY.setValue(initialY);
370
+ opacity.setValue(0);
371
+ progress.setValue(1);
372
+ reactNative.Animated.parallel([
373
+ reactNative.Animated.spring(translateY, {
374
+ toValue: 0,
375
+ tension: 70,
376
+ friction: 9,
377
+ useNativeDriver: true
378
+ }),
379
+ reactNative.Animated.timing(opacity, {
380
+ toValue: 1,
381
+ duration: 200,
382
+ useNativeDriver: true
383
+ })
384
+ ]).start(() => {
385
+ if (options.onShow) {
386
+ options.onShow();
387
+ }
388
+ });
389
+ const shouldAutoHide = options.autoHide ?? true;
390
+ const dur = shouldAutoHide ? options.duration ?? defaultDuration : 0;
391
+ if (dur > 0) {
392
+ startAutoHideTimer(dur);
393
+ }
394
+ },
395
+ [clearTimers, defaultDuration, defaultPosition, opacity, progress, startAutoHideTimer, translateY]
396
+ );
397
+ React.useImperativeHandle(
398
+ ref,
399
+ () => ({
400
+ showMessage: show,
401
+ hideMessage: () => animateHide()
402
+ }),
403
+ [show, animateHide]
404
+ );
405
+ React.useEffect(() => {
406
+ const instanceRef = {
407
+ showMessage: show,
408
+ hideMessage: () => animateHide()
409
+ };
410
+ flashManager.register(instanceRef);
411
+ return () => {
412
+ flashManager.unregister();
413
+ clearTimers();
414
+ };
415
+ }, [show, animateHide, clearTimers]);
416
+ const panResponder = React.useRef(
417
+ reactNative.PanResponder.create({
418
+ onStartShouldSetPanResponder: () => true,
419
+ onMoveShouldSetPanResponder: (_, gestureState) => {
420
+ return Math.abs(gestureState.dy) > 5;
421
+ },
422
+ onPanResponderGrant: () => {
423
+ if (currentMessage?.pauseOnPress ?? true) {
424
+ pauseTimer();
425
+ }
426
+ },
427
+ onPanResponderMove: (_, gestureState) => {
428
+ if (!(currentMessage?.swipeToDismiss ?? true)) return;
429
+ if (isTop && gestureState.dy < 0) {
430
+ translateY.setValue(gestureState.dy);
431
+ } else if (isBottom && gestureState.dy > 0) {
432
+ translateY.setValue(gestureState.dy);
433
+ }
434
+ },
435
+ onPanResponderRelease: (_, gestureState) => {
436
+ const swipeThreshold = 30;
437
+ const isDismissGesture = isTop && gestureState.dy < -swipeThreshold || isBottom && gestureState.dy > swipeThreshold;
438
+ if (isDismissGesture && (currentMessage?.swipeToDismiss ?? true)) {
439
+ animateHide();
440
+ } else {
441
+ reactNative.Animated.spring(translateY, {
442
+ toValue: 0,
443
+ tension: 80,
444
+ friction: 8,
445
+ useNativeDriver: true
446
+ }).start();
447
+ if (currentMessage?.pauseOnPress ?? true) {
448
+ resumeTimer();
449
+ }
450
+ }
451
+ }
452
+ })
453
+ ).current;
454
+ if (!isVisible || !currentMessage) {
455
+ return null;
456
+ }
457
+ const type = currentMessage.type ?? "default";
458
+ const typeColors = COLORS[type];
459
+ const backgroundColor = currentMessage.backgroundColor ?? typeColors.background;
460
+ const textColor = currentMessage.color ?? typeColors.text;
461
+ const floating = currentMessage.floating ?? defaultFloating;
462
+ const topInset = currentMessage.statusBarHeight ?? statusBarOffset ?? (isTop ? safeInsets.top + (floating ? 8 : 0) : 0);
463
+ const bottomInset = bottomOffset ?? (isBottom ? safeInsets.bottom + (floating ? 8 : 0) : 0);
464
+ const rootPositionStyle = isTop ? [styles2.rootTop, { paddingTop: topInset }] : isBottom ? [styles2.rootBottom, { paddingBottom: bottomInset }] : styles2.rootCenter;
465
+ const renderIcon = () => {
466
+ if (currentMessage.icon === "none") return null;
467
+ if (typeof currentMessage.icon === "function") {
468
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.iconContainer, children: currentMessage.icon({ size: 22, color: textColor }) });
469
+ }
470
+ if (React__default.default.isValidElement(currentMessage.icon)) {
471
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.iconContainer, children: currentMessage.icon });
472
+ }
473
+ const iconType = typeof currentMessage.icon === "string" ? currentMessage.icon : type;
474
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.iconContainer, children: /* @__PURE__ */ jsxRuntime.jsx(BuiltInIcon, { type: iconType, color: textColor, size: 22 }) });
475
+ };
476
+ const handlePress = () => {
477
+ if (currentMessage.onPress) {
478
+ currentMessage.onPress();
479
+ }
480
+ if (currentMessage.hideOnPress ?? true) {
481
+ animateHide();
482
+ }
483
+ };
484
+ const progressWidthInterpolation = progress.interpolate({
485
+ inputRange: [0, 1],
486
+ outputRange: ["0%", "100%"]
487
+ });
488
+ return /* @__PURE__ */ jsxRuntime.jsx(
489
+ reactNative.View,
490
+ {
491
+ pointerEvents: "box-none",
492
+ style: [styles2.rootOverlay, rootPositionStyle, containerCustomStyle],
493
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
494
+ reactNative.Animated.View,
495
+ {
496
+ ...panResponder.panHandlers,
497
+ style: [
498
+ styles2.toastContainer,
499
+ floating ? styles2.floatingContainer : styles2.edgeToEdgeContainer,
500
+ { backgroundColor },
501
+ currentMessage.style,
502
+ {
503
+ transform: [{ translateY }],
504
+ opacity
505
+ }
506
+ ],
507
+ children: [
508
+ currentMessage.customComponent ? currentMessage.customComponent(currentMessage, () => animateHide()) : /* @__PURE__ */ jsxRuntime.jsxs(
509
+ reactNative.Pressable,
510
+ {
511
+ onPress: handlePress,
512
+ onLongPress: currentMessage.onLongPress,
513
+ style: styles2.contentWrapper,
514
+ children: [
515
+ renderIcon(),
516
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.textContainer, children: [
517
+ typeof currentMessage.message === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
518
+ reactNative.Text,
519
+ {
520
+ style: [styles2.titleText, { color: textColor }, currentMessage.titleStyle],
521
+ numberOfLines: 2,
522
+ children: currentMessage.message
523
+ }
524
+ ) : currentMessage.message,
525
+ currentMessage.description ? typeof currentMessage.description === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
526
+ reactNative.Text,
527
+ {
528
+ style: [
529
+ styles2.descriptionText,
530
+ { color: textColor },
531
+ currentMessage.textStyle
532
+ ],
533
+ numberOfLines: 3,
534
+ children: currentMessage.description
535
+ }
536
+ ) : currentMessage.description : null
537
+ ] }),
538
+ currentMessage.action ? /* @__PURE__ */ jsxRuntime.jsx(
539
+ reactNative.Pressable,
540
+ {
541
+ onPress: () => {
542
+ currentMessage.action?.onPress();
543
+ animateHide();
544
+ },
545
+ style: [styles2.actionButton, currentMessage.action.style],
546
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.actionButtonText, currentMessage.action.textStyle], children: currentMessage.action.label })
547
+ }
548
+ ) : null
549
+ ]
550
+ }
551
+ ),
552
+ currentMessage.showProgressBar && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.progressBarContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
553
+ reactNative.Animated.View,
554
+ {
555
+ style: [
556
+ styles2.progressBarFill,
557
+ { width: progressWidthInterpolation }
558
+ ]
559
+ }
560
+ ) })
561
+ ]
562
+ }
563
+ )
564
+ }
565
+ );
566
+ }
567
+ );
568
+ FlashMessage.displayName = "FlashMessage";
569
+ var FlashMessageContext = React.createContext(null);
570
+ var FlashMessageProvider = ({
571
+ children,
572
+ ...containerProps
573
+ }) => {
574
+ const contextValue = React.useMemo(
575
+ () => ({
576
+ show: (options) => flashManager.showMessage(options),
577
+ hide: () => flashManager.hideMessage()
578
+ }),
579
+ []
580
+ );
581
+ return /* @__PURE__ */ jsxRuntime.jsxs(FlashMessageContext.Provider, { value: contextValue, children: [
582
+ children,
583
+ /* @__PURE__ */ jsxRuntime.jsx(FlashMessage, { ...containerProps })
584
+ ] });
585
+ };
586
+ function useFlashMessage() {
587
+ const context = React.useContext(FlashMessageContext);
588
+ if (!context) {
589
+ return {
590
+ show: (options) => flashManager.showMessage(options),
591
+ hide: () => flashManager.hideMessage()
592
+ };
593
+ }
594
+ return context;
595
+ }
596
+
597
+ // src/index.ts
598
+ var index_default = FlashMessage;
599
+
600
+ exports.BuiltInIcon = BuiltInIcon;
601
+ exports.COLORS = COLORS;
602
+ exports.FlashMessage = FlashMessage;
603
+ exports.FlashMessageProvider = FlashMessageProvider;
604
+ exports.default = index_default;
605
+ exports.dismissFlash = dismissFlash;
606
+ exports.flash = flash;
607
+ exports.flashManager = flashManager;
608
+ exports.hideMessage = hideMessage;
609
+ exports.showMessage = showMessage;
610
+ exports.styles = styles2;
611
+ exports.useFlashMessage = useFlashMessage;
612
+ //# sourceMappingURL=index.js.map
613
+ //# sourceMappingURL=index.js.map