react-native-popify 0.1.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.
@@ -0,0 +1,683 @@
1
+ "use strict";
2
+
3
+ import React, { useEffect, useRef, useCallback } from 'react';
4
+ import { Modal, View, Text, TouchableOpacity, TouchableWithoutFeedback, StyleSheet, Animated, Dimensions, Platform } from 'react-native';
5
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
+ const {
7
+ width: SCREEN_WIDTH
8
+ } = Dimensions.get('window');
9
+
10
+ // ─── Type Configs ────────────────────────────────────────────
11
+ const TYPE_CONFIG = {
12
+ success: {
13
+ color: '#00C853',
14
+ bg: '#F1FBF4',
15
+ icon: '✓'
16
+ },
17
+ error: {
18
+ color: '#FF3D71',
19
+ bg: '#FFF2F2',
20
+ icon: '✕'
21
+ },
22
+ warning: {
23
+ color: '#FFAA00',
24
+ bg: '#FFFBF0',
25
+ icon: '!'
26
+ },
27
+ info: {
28
+ color: '#3366FF',
29
+ bg: '#F0F4FF',
30
+ icon: 'i'
31
+ }
32
+ };
33
+ const DARK_BG = {
34
+ success: '#152E1B',
35
+ error: '#2E1520',
36
+ warning: '#2E2815',
37
+ info: '#151E2E'
38
+ };
39
+ const PARTICLE_COUNT = 10;
40
+ const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
41
+
42
+ // ─── Animated Pressable Button ──────────────────────────────
43
+ const PressableButton = ({
44
+ onPress,
45
+ style,
46
+ textStyle,
47
+ label,
48
+ color,
49
+ outline
50
+ }) => {
51
+ const scaleAnim = useRef(new Animated.Value(1)).current;
52
+ return /*#__PURE__*/_jsx(AnimatedTouchable, {
53
+ activeOpacity: 0.85,
54
+ onPressIn: () => Animated.spring(scaleAnim, {
55
+ toValue: 0.93,
56
+ useNativeDriver: true,
57
+ speed: 50,
58
+ bounciness: 4
59
+ }).start(),
60
+ onPressOut: () => Animated.spring(scaleAnim, {
61
+ toValue: 1,
62
+ useNativeDriver: true,
63
+ speed: 20,
64
+ bounciness: 10
65
+ }).start(),
66
+ onPress: onPress,
67
+ style: [btnStyles.base, outline ? [btnStyles.outline, {
68
+ borderColor: color
69
+ }] : {
70
+ backgroundColor: color,
71
+ ...Platform.select({
72
+ ios: {
73
+ shadowColor: color,
74
+ shadowOffset: {
75
+ width: 0,
76
+ height: 4
77
+ },
78
+ shadowOpacity: 0.3,
79
+ shadowRadius: 8
80
+ },
81
+ android: {
82
+ elevation: 6
83
+ }
84
+ })
85
+ }, {
86
+ transform: [{
87
+ scale: scaleAnim
88
+ }]
89
+ }, style],
90
+ children: /*#__PURE__*/_jsx(Text, {
91
+ style: [btnStyles.text, outline && {
92
+ color: color
93
+ }, textStyle],
94
+ children: label
95
+ })
96
+ });
97
+ };
98
+ const btnStyles = StyleSheet.create({
99
+ base: {
100
+ paddingVertical: 14,
101
+ paddingHorizontal: 24,
102
+ borderRadius: 16,
103
+ alignItems: 'center',
104
+ justifyContent: 'center',
105
+ minWidth: 110
106
+ },
107
+ text: {
108
+ color: '#FFF',
109
+ fontSize: 15,
110
+ fontWeight: '700',
111
+ letterSpacing: 0.3
112
+ },
113
+ outline: {
114
+ backgroundColor: 'transparent',
115
+ borderWidth: 2
116
+ }
117
+ });
118
+
119
+ // ─── PopifyAlert ────────────────────────────────────────────
120
+ const PopifyAlert = ({
121
+ visible = false,
122
+ type = 'info',
123
+ title,
124
+ message,
125
+ onClose,
126
+ buttons = [],
127
+ // Style overrides
128
+ titleStyle,
129
+ messageStyle,
130
+ containerStyle,
131
+ overlayStyle,
132
+ // Animation
133
+ animationType = 'spring',
134
+ // 'spring' | 'slideUp' | 'fadeIn'
135
+ duration = 350,
136
+ // Features
137
+ autoClose = false,
138
+ autoCloseDuration = 3000,
139
+ showCloseButton = true,
140
+ showIcon = true,
141
+ showParticles = false,
142
+ customIcon,
143
+ // Layout
144
+ buttonLayout = 'auto',
145
+ // 'horizontal' | 'vertical' | 'auto'
146
+ // Theme
147
+ theme = 'light',
148
+ // 'light' | 'dark'
149
+ // Custom renders
150
+ renderHeader,
151
+ renderFooter
152
+ }) => {
153
+ const config = TYPE_CONFIG[type] || TYPE_CONFIG.info;
154
+ const isDark = theme === 'dark';
155
+
156
+ // ── Animated values ──
157
+ const overlayAnim = useRef(new Animated.Value(0)).current;
158
+ const cardScale = useRef(new Animated.Value(0.6)).current;
159
+ const cardOpacity = useRef(new Animated.Value(0)).current;
160
+ const cardTranslateY = useRef(new Animated.Value(60)).current;
161
+ const iconScale = useRef(new Animated.Value(0)).current;
162
+ const iconRotate = useRef(new Animated.Value(0)).current;
163
+ const progressAnim = useRef(new Animated.Value(1)).current;
164
+ const contentOpacity = useRef(new Animated.Value(0)).current;
165
+ const contentTranslateY = useRef(new Animated.Value(15)).current;
166
+
167
+ // Per-button anims (max 6)
168
+ const btnAnims = useRef(Array.from({
169
+ length: 6
170
+ }, () => new Animated.Value(0))).current;
171
+
172
+ // Particles
173
+ const particles = useRef(Array.from({
174
+ length: PARTICLE_COUNT
175
+ }, () => ({
176
+ x: new Animated.Value(0),
177
+ y: new Animated.Value(0),
178
+ opacity: new Animated.Value(0),
179
+ scale: new Animated.Value(0)
180
+ }))).current;
181
+ const autoCloseRef = useRef(null);
182
+
183
+ // ── Reset ──
184
+ const reset = useCallback(() => {
185
+ overlayAnim.setValue(0);
186
+ cardScale.setValue(0.6);
187
+ cardOpacity.setValue(0);
188
+ cardTranslateY.setValue(animationType === 'slideUp' ? 120 : 60);
189
+ iconScale.setValue(0);
190
+ iconRotate.setValue(0);
191
+ progressAnim.setValue(1);
192
+ contentOpacity.setValue(0);
193
+ contentTranslateY.setValue(15);
194
+ btnAnims.forEach(a => a.setValue(0));
195
+ particles.forEach(p => {
196
+ p.x.setValue(0);
197
+ p.y.setValue(0);
198
+ p.opacity.setValue(0);
199
+ p.scale.setValue(0);
200
+ });
201
+ }, [animationType, overlayAnim, cardScale, cardOpacity, cardTranslateY, iconScale, iconRotate, progressAnim, contentOpacity, contentTranslateY, btnAnims, particles]);
202
+
203
+ // ── Animate In ──
204
+ const animateIn = useCallback(() => {
205
+ reset();
206
+
207
+ // Overlay
208
+ Animated.timing(overlayAnim, {
209
+ toValue: 1,
210
+ duration: 280,
211
+ useNativeDriver: true
212
+ }).start();
213
+
214
+ // Card
215
+ const springCfg = {
216
+ tension: 55,
217
+ friction: 8,
218
+ useNativeDriver: true
219
+ };
220
+ const timingCfg = {
221
+ duration,
222
+ useNativeDriver: true
223
+ };
224
+ const isSpring = animationType !== 'fadeIn';
225
+ const mkAnim = (anim, toValue) => isSpring ? Animated.spring(anim, {
226
+ toValue,
227
+ ...springCfg
228
+ }) : Animated.timing(anim, {
229
+ toValue,
230
+ ...timingCfg
231
+ });
232
+ Animated.parallel([mkAnim(cardScale, 1), mkAnim(cardTranslateY, 0), Animated.timing(cardOpacity, {
233
+ toValue: 1,
234
+ duration: 200,
235
+ useNativeDriver: true
236
+ })]).start();
237
+
238
+ // Content fade-in
239
+ setTimeout(() => {
240
+ Animated.parallel([Animated.timing(contentOpacity, {
241
+ toValue: 1,
242
+ duration: 280,
243
+ useNativeDriver: true
244
+ }), Animated.spring(contentTranslateY, {
245
+ toValue: 0,
246
+ tension: 60,
247
+ friction: 10,
248
+ useNativeDriver: true
249
+ })]).start();
250
+ }, 150);
251
+
252
+ // Icon bounce
253
+ setTimeout(() => {
254
+ Animated.spring(iconScale, {
255
+ toValue: 1,
256
+ tension: 35,
257
+ friction: 5,
258
+ useNativeDriver: true
259
+ }).start();
260
+ Animated.timing(iconRotate, {
261
+ toValue: 1,
262
+ duration: 500,
263
+ useNativeDriver: true
264
+ }).start();
265
+ }, 250);
266
+
267
+ // Buttons stagger
268
+ setTimeout(() => {
269
+ Animated.stagger(70, btnAnims.slice(0, buttons.length).map(anim => Animated.spring(anim, {
270
+ toValue: 1,
271
+ tension: 55,
272
+ friction: 9,
273
+ useNativeDriver: true
274
+ }))).start();
275
+ }, 300);
276
+
277
+ // Particles burst
278
+ if (showParticles) {
279
+ setTimeout(() => {
280
+ const pAnims = particles.map((p, i) => {
281
+ const angle = i / PARTICLE_COUNT * 2 * Math.PI + Math.random() * 0.3;
282
+ const radius = 45 + Math.random() * 35;
283
+ return Animated.parallel([Animated.timing(p.x, {
284
+ toValue: Math.cos(angle) * radius,
285
+ duration: 650,
286
+ useNativeDriver: true
287
+ }), Animated.timing(p.y, {
288
+ toValue: Math.sin(angle) * radius,
289
+ duration: 650,
290
+ useNativeDriver: true
291
+ }), Animated.sequence([Animated.timing(p.opacity, {
292
+ toValue: 1,
293
+ duration: 80,
294
+ useNativeDriver: true
295
+ }), Animated.timing(p.opacity, {
296
+ toValue: 0,
297
+ duration: 570,
298
+ useNativeDriver: true
299
+ })]), Animated.sequence([Animated.spring(p.scale, {
300
+ toValue: 1.8,
301
+ tension: 80,
302
+ friction: 5,
303
+ useNativeDriver: true
304
+ }), Animated.timing(p.scale, {
305
+ toValue: 0,
306
+ duration: 450,
307
+ useNativeDriver: true
308
+ })])]);
309
+ });
310
+ Animated.stagger(35, pAnims).start();
311
+ }, 450);
312
+ }
313
+
314
+ // Auto close
315
+ if (autoClose && onClose) {
316
+ Animated.timing(progressAnim, {
317
+ toValue: 0,
318
+ duration: autoCloseDuration,
319
+ useNativeDriver: false
320
+ }).start();
321
+ autoCloseRef.current = setTimeout(onClose, autoCloseDuration);
322
+ }
323
+ }, [animationType, autoClose, autoCloseDuration, showParticles, buttons.length, reset, overlayAnim, cardScale, cardTranslateY, cardOpacity, contentOpacity, contentTranslateY, iconScale, iconRotate, btnAnims, particles, progressAnim, onClose, duration]);
324
+
325
+ // ── Animate Out ──
326
+ const animateOut = useCallback(() => {
327
+ if (autoCloseRef.current) clearTimeout(autoCloseRef.current);
328
+ Animated.parallel([Animated.timing(overlayAnim, {
329
+ toValue: 0,
330
+ duration: 180,
331
+ useNativeDriver: true
332
+ }), Animated.timing(cardScale, {
333
+ toValue: 0.6,
334
+ duration: 200,
335
+ useNativeDriver: true
336
+ }), Animated.timing(cardOpacity, {
337
+ toValue: 0,
338
+ duration: 180,
339
+ useNativeDriver: true
340
+ })]).start();
341
+ }, [overlayAnim, cardScale, cardOpacity]);
342
+ useEffect(() => {
343
+ if (visible) animateIn();else animateOut();
344
+ return () => {
345
+ if (autoCloseRef.current) clearTimeout(autoCloseRef.current);
346
+ };
347
+ }, [visible, animateIn, animateOut]);
348
+
349
+ // ── Derived ──
350
+ const iconSpin = iconRotate.interpolate({
351
+ inputRange: [0, 1],
352
+ outputRange: ['0deg', '360deg']
353
+ });
354
+ const layout = buttonLayout === 'auto' ? buttons.length <= 2 ? 'horizontal' : 'vertical' : buttonLayout;
355
+ const cardBg = isDark ? DARK_BG[type] || '#1A1A2E' : '#FFFFFF';
356
+ const titleColor = isDark ? '#F0F0F5' : '#1A1A2E';
357
+ const msgColor = isDark ? '#A0A0B0' : '#6B6B80';
358
+ const particleColors = [config.color, config.color + 'CC', config.color + '99', config.color + '66', '#FFD700'];
359
+ return /*#__PURE__*/_jsx(Modal, {
360
+ transparent: true,
361
+ visible: visible,
362
+ animationType: "none",
363
+ statusBarTranslucent: true,
364
+ children: /*#__PURE__*/_jsx(TouchableWithoutFeedback, {
365
+ onPress: onClose,
366
+ children: /*#__PURE__*/_jsx(Animated.View, {
367
+ style: [styles.overlay, {
368
+ opacity: overlayAnim
369
+ }, isDark && styles.overlayDark, overlayStyle],
370
+ children: /*#__PURE__*/_jsx(TouchableWithoutFeedback, {
371
+ children: /*#__PURE__*/_jsxs(Animated.View, {
372
+ style: [styles.card, {
373
+ backgroundColor: cardBg,
374
+ opacity: cardOpacity,
375
+ transform: [{
376
+ scale: cardScale
377
+ }, {
378
+ translateY: cardTranslateY
379
+ }]
380
+ }, isDark && styles.cardDark, containerStyle],
381
+ children: [/*#__PURE__*/_jsxs(View, {
382
+ style: [styles.banner, {
383
+ backgroundColor: config.color
384
+ }],
385
+ children: [/*#__PURE__*/_jsx(View, {
386
+ style: styles.bannerShine
387
+ }), /*#__PURE__*/_jsx(View, {
388
+ style: styles.bannerDots,
389
+ children: [0.15, 0.08, 0.12].map((op, i) => /*#__PURE__*/_jsx(View, {
390
+ style: [styles.bannerDot, {
391
+ opacity: op,
392
+ width: 40 + i * 30,
393
+ height: 40 + i * 30,
394
+ borderRadius: 20 + i * 15,
395
+ left: -10 + i * 80,
396
+ top: -10 + i * 5
397
+ }]
398
+ }, i))
399
+ }), renderHeader && renderHeader()]
400
+ }), showCloseButton && onClose && /*#__PURE__*/_jsx(TouchableOpacity, {
401
+ style: styles.closeBtn,
402
+ onPress: onClose,
403
+ hitSlop: {
404
+ top: 12,
405
+ bottom: 12,
406
+ left: 12,
407
+ right: 12
408
+ },
409
+ children: /*#__PURE__*/_jsx(Text, {
410
+ style: styles.closeBtnText,
411
+ children: "\u2715"
412
+ })
413
+ }), showIcon && /*#__PURE__*/_jsxs(View, {
414
+ style: styles.iconWrap,
415
+ children: [/*#__PURE__*/_jsx(Animated.View, {
416
+ style: [styles.iconCircleOuter, {
417
+ backgroundColor: cardBg,
418
+ transform: [{
419
+ scale: iconScale
420
+ }]
421
+ }],
422
+ children: /*#__PURE__*/_jsx(Animated.View, {
423
+ style: [styles.iconCircle, {
424
+ backgroundColor: config.color,
425
+ transform: [{
426
+ rotate: iconSpin
427
+ }],
428
+ ...Platform.select({
429
+ ios: {
430
+ shadowColor: config.color,
431
+ shadowOffset: {
432
+ width: 0,
433
+ height: 6
434
+ },
435
+ shadowOpacity: 0.45,
436
+ shadowRadius: 14
437
+ },
438
+ android: {
439
+ elevation: 14
440
+ }
441
+ })
442
+ }],
443
+ children: /*#__PURE__*/_jsx(View, {
444
+ style: styles.iconRing,
445
+ children: customIcon || /*#__PURE__*/_jsx(Text, {
446
+ style: styles.iconChar,
447
+ children: config.icon
448
+ })
449
+ })
450
+ })
451
+ }), showParticles && particles.map((p, i) => /*#__PURE__*/_jsx(Animated.View, {
452
+ style: [styles.particle, {
453
+ width: 7 + i % 3 * 2,
454
+ height: 7 + i % 3 * 2,
455
+ backgroundColor: particleColors[i % particleColors.length],
456
+ opacity: p.opacity,
457
+ transform: [{
458
+ translateX: p.x
459
+ }, {
460
+ translateY: p.y
461
+ }, {
462
+ scale: p.scale
463
+ }]
464
+ }]
465
+ }, i))]
466
+ }), /*#__PURE__*/_jsxs(Animated.View, {
467
+ style: [styles.content, {
468
+ opacity: contentOpacity,
469
+ transform: [{
470
+ translateY: contentTranslateY
471
+ }]
472
+ }],
473
+ children: [title && /*#__PURE__*/_jsx(Text, {
474
+ style: [styles.title, {
475
+ color: titleColor
476
+ }, titleStyle],
477
+ children: title
478
+ }), message && /*#__PURE__*/_jsx(Text, {
479
+ style: [styles.message, {
480
+ color: msgColor
481
+ }, messageStyle],
482
+ children: message
483
+ })]
484
+ }), buttons.length > 0 && /*#__PURE__*/_jsx(View, {
485
+ style: [styles.btnWrap, layout === 'horizontal' && styles.btnWrapRow],
486
+ children: buttons.map((btn, idx) => {
487
+ const anim = btnAnims[idx];
488
+ return /*#__PURE__*/_jsx(Animated.View, {
489
+ style: [layout === 'horizontal' ? styles.btnItemHorizontal : styles.btnItemVertical, {
490
+ opacity: anim,
491
+ transform: [{
492
+ translateY: anim.interpolate({
493
+ inputRange: [0, 1],
494
+ outputRange: [18, 0]
495
+ })
496
+ }]
497
+ }]
498
+ }, idx);
499
+ })
500
+ }), autoClose && /*#__PURE__*/_jsx(View, {
501
+ style: styles.progressWrap,
502
+ children: /*#__PURE__*/_jsx(Animated.View, {
503
+ style: [styles.progressBar, {
504
+ backgroundColor: config.color,
505
+ width: progressAnim.interpolate({
506
+ inputRange: [0, 1],
507
+ outputRange: ['0%', '100%']
508
+ })
509
+ }]
510
+ })
511
+ }), renderFooter && /*#__PURE__*/_jsx(View, {
512
+ style: styles.footer,
513
+ children: renderFooter()
514
+ })]
515
+ })
516
+ })
517
+ })
518
+ })
519
+ });
520
+ };
521
+
522
+ // ─── Styles ─────────────────────────────────────────────────
523
+ const styles = StyleSheet.create({
524
+ overlay: {
525
+ flex: 1,
526
+ justifyContent: 'center',
527
+ alignItems: 'center',
528
+ backgroundColor: 'rgba(15,15,35,0.55)',
529
+ padding: 20
530
+ },
531
+ card: {
532
+ width: Math.min(SCREEN_WIDTH - 48, 380),
533
+ borderRadius: 28,
534
+ overflow: 'hidden',
535
+ ...Platform.select({
536
+ ios: {
537
+ shadowColor: '#000',
538
+ shadowOffset: {
539
+ width: 0,
540
+ height: 24
541
+ },
542
+ shadowOpacity: 0.2,
543
+ shadowRadius: 32
544
+ },
545
+ android: {
546
+ elevation: 28
547
+ }
548
+ })
549
+ },
550
+ banner: {
551
+ height: 85,
552
+ overflow: 'hidden'
553
+ },
554
+ bannerShine: {
555
+ ...StyleSheet.absoluteFillObject,
556
+ backgroundColor: 'rgba(255,255,255,0.12)'
557
+ },
558
+ bannerDots: {
559
+ ...StyleSheet.absoluteFillObject
560
+ },
561
+ bannerDot: {
562
+ position: 'absolute',
563
+ backgroundColor: '#FFF'
564
+ },
565
+ closeBtn: {
566
+ position: 'absolute',
567
+ top: 14,
568
+ right: 14,
569
+ width: 30,
570
+ height: 30,
571
+ borderRadius: 15,
572
+ backgroundColor: 'rgba(0,0,0,0.18)',
573
+ justifyContent: 'center',
574
+ alignItems: 'center',
575
+ zIndex: 10
576
+ },
577
+ closeBtnText: {
578
+ color: '#FFF',
579
+ fontSize: 13,
580
+ fontWeight: '800'
581
+ },
582
+ iconWrap: {
583
+ alignItems: 'center',
584
+ justifyContent: 'center',
585
+ marginTop: -38,
586
+ height: 76,
587
+ zIndex: 5
588
+ },
589
+ iconCircleOuter: {
590
+ width: 80,
591
+ height: 80,
592
+ borderRadius: 40,
593
+ justifyContent: 'center',
594
+ alignItems: 'center',
595
+ padding: 4
596
+ },
597
+ iconCircle: {
598
+ width: 72,
599
+ height: 72,
600
+ borderRadius: 36,
601
+ justifyContent: 'center',
602
+ alignItems: 'center'
603
+ },
604
+ iconRing: {
605
+ width: 60,
606
+ height: 60,
607
+ borderRadius: 30,
608
+ borderWidth: 2.5,
609
+ borderColor: 'rgba(255,255,255,0.35)',
610
+ justifyContent: 'center',
611
+ alignItems: 'center'
612
+ },
613
+ iconChar: {
614
+ color: '#FFF',
615
+ fontSize: 28,
616
+ fontWeight: '900',
617
+ includeFontPadding: false,
618
+ textAlignVertical: 'center'
619
+ },
620
+ content: {
621
+ paddingHorizontal: 26,
622
+ paddingTop: 14,
623
+ paddingBottom: 6,
624
+ alignItems: 'center'
625
+ },
626
+ title: {
627
+ fontSize: 22,
628
+ fontWeight: '800',
629
+ textAlign: 'center',
630
+ marginBottom: 8,
631
+ letterSpacing: 0.2
632
+ },
633
+ message: {
634
+ fontSize: 15,
635
+ textAlign: 'center',
636
+ lineHeight: 22,
637
+ letterSpacing: 0.15
638
+ },
639
+ btnWrap: {
640
+ paddingHorizontal: 22,
641
+ paddingTop: 14,
642
+ paddingBottom: 22
643
+ },
644
+ btnWrapRow: {
645
+ flexDirection: 'row',
646
+ justifyContent: 'center'
647
+ },
648
+ progressWrap: {
649
+ height: 4,
650
+ backgroundColor: 'rgba(0,0,0,0.06)',
651
+ borderBottomLeftRadius: 28,
652
+ borderBottomRightRadius: 28,
653
+ overflow: 'hidden'
654
+ },
655
+ progressBar: {
656
+ height: 4
657
+ },
658
+ footer: {
659
+ paddingHorizontal: 24,
660
+ paddingBottom: 18
661
+ },
662
+ overlayDark: {
663
+ backgroundColor: 'rgba(0,0,0,0.8)'
664
+ },
665
+ cardDark: {
666
+ borderWidth: 1,
667
+ borderColor: 'rgba(255,255,255,0.08)'
668
+ },
669
+ particle: {
670
+ position: 'absolute',
671
+ borderRadius: 5
672
+ },
673
+ btnItemHorizontal: {
674
+ flex: 1,
675
+ marginHorizontal: 5
676
+ },
677
+ btnItemVertical: {
678
+ width: '100%',
679
+ marginVertical: 5
680
+ }
681
+ });
682
+ export default PopifyAlert;
683
+ //# sourceMappingURL=PopifyAlert.js.map