rnnovaui 0.10.21 → 0.10.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnnovaui",
3
- "version": "0.10.21",
3
+ "version": "0.10.22",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -47,6 +47,7 @@
47
47
  "react": ">=19.0.0",
48
48
  "react-native": ">=0.76.0",
49
49
  "react-native-reanimated": ">=3.0.0",
50
- "react-native-safe-area-context": ">=4.0.0"
50
+ "react-native-safe-area-context": ">=4.0.0",
51
+ "react-native-svg": ">=15.0.0"
51
52
  }
52
53
  }
@@ -0,0 +1,946 @@
1
+ import React, {
2
+ forwardRef,
3
+ useCallback,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from "react";
9
+ import { PanResponder, Pressable, StyleSheet, Text, View } from "react-native";
10
+ import Svg, {
11
+ Defs,
12
+ G,
13
+ LinearGradient,
14
+ Mask,
15
+ Path,
16
+ Rect,
17
+ Stop,
18
+ } from "react-native-svg";
19
+ import Animated, {
20
+ FadeIn,
21
+ FadeInDown,
22
+ FadeOut,
23
+ useAnimatedStyle,
24
+ useSharedValue,
25
+ withSequence,
26
+ withSpring,
27
+ withTiming,
28
+ } from "react-native-reanimated";
29
+ import { Feather, Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
30
+
31
+ const DEFAULT_WIDTH = 360;
32
+ const DEFAULT_HEIGHT = 230;
33
+
34
+ const THEMES = {
35
+ classic: {
36
+ background: ["#4214C7", "#6B20E8"],
37
+ border: "#8B6BFF",
38
+ title: "#FFFFFF",
39
+ subtitle: "#EDE7FF",
40
+ accent: "#FFD42A",
41
+ scratch: ["#D9DEE5", "#BFC6D0"],
42
+ scratchBorder: "#FFFFFF",
43
+ scratchText: "#263248",
44
+ scratchIcon: "#263248",
45
+ icon: "gift",
46
+ iconType: "material",
47
+ iconColor: "#E9D5FF",
48
+ titleLines: ["Scratch", "& Win"],
49
+ titleAccentLine: 1,
50
+ eyebrow: null,
51
+ subtitleText: "Reveal your reward!",
52
+ scratchText: "Scratch Here",
53
+ },
54
+
55
+ trophy: {
56
+ background: ["#17120A", "#3A2810"],
57
+ border: "#D6A72C",
58
+ title: "#FFFFFF",
59
+ subtitle: "#F5E5B1",
60
+ accent: "#FFD43B",
61
+ scratch: ["#E1E5EA", "#C1C8D1"],
62
+ scratchBorder: "#E7C84A",
63
+ scratchText: "#273043",
64
+ scratchIcon: "#273043",
65
+ icon: "trophy",
66
+ iconType: "material",
67
+ iconColor: "#FFC62B",
68
+ titleLines: ["LUCKY", "SCRATCH"],
69
+ titleAccentLine: 0,
70
+ eyebrow: null,
71
+ subtitleText: "Win Exciting Prizes!",
72
+ scratchText: "Scratch Here",
73
+ },
74
+
75
+ gift: {
76
+ background: ["#E51B18", "#C90F13"],
77
+ border: "#FF6B61",
78
+ title: "#FFFFFF",
79
+ subtitle: "#FFF0EA",
80
+ accent: "#FFD42A",
81
+ scratch: ["#E1E5EA", "#C1C8D1"],
82
+ scratchBorder: "#FFFFFF",
83
+ scratchText: "#273043",
84
+ scratchIcon: "#273043",
85
+ icon: "gift",
86
+ iconType: "material",
87
+ iconColor: "#FFD43B",
88
+ titleLines: ["Scratch", "& Win"],
89
+ titleAccentLine: 1,
90
+ eyebrow: null,
91
+ subtitleText: "Big Prizes Await!",
92
+ scratchText: "Scratch Here",
93
+ },
94
+
95
+ travel: {
96
+ background: ["#12B7A7", "#13A8D2"],
97
+ border: "#6FE7DF",
98
+ title: "#FFFFFF",
99
+ subtitle: "#E7FFFF",
100
+ accent: "#D7FFFF",
101
+ scratch: ["#E1E5EA", "#C1C8D1"],
102
+ scratchBorder: "#FFFFFF",
103
+ scratchText: "#273043",
104
+ scratchIcon: "#273043",
105
+ icon: "airplane",
106
+ iconType: "ion",
107
+ iconColor: "#FFFFFF",
108
+ titleLines: ["TRAVEL", "LUCK"],
109
+ titleAccentLine: null,
110
+ eyebrow: null,
111
+ subtitleText: "Scratch & Get Your Reward",
112
+ scratchText: "Scratch Here",
113
+ },
114
+
115
+ discount: {
116
+ background: ["#F62992", "#A91DDB"],
117
+ border: "#FF8ED0",
118
+ title: "#FFFFFF",
119
+ subtitle: "#FFE8F6",
120
+ accent: "#FFE32E",
121
+ scratch: ["#E1E5EA", "#C1C8D1"],
122
+ scratchBorder: "#FFFFFF",
123
+ scratchText: "#273043",
124
+ scratchIcon: "#273043",
125
+ icon: "percent",
126
+ iconType: "material",
127
+ iconColor: "#FFD52C",
128
+ titleLines: ["Special", "DISCOUNT"],
129
+ titleAccentLine: 1,
130
+ eyebrow: null,
131
+ subtitleText: "Scratch & Save More!",
132
+ scratchText: "Scratch Here",
133
+ },
134
+
135
+ space: {
136
+ background: ["#06317D", "#0756C9"],
137
+ border: "#3D8DFF",
138
+ title: "#FFFFFF",
139
+ subtitle: "#DDEBFF",
140
+ accent: "#55D8FF",
141
+ scratch: ["#E1E5EA", "#C1C8D1"],
142
+ scratchBorder: "#FFFFFF",
143
+ scratchText: "#273043",
144
+ scratchIcon: "#273043",
145
+ icon: "rocket",
146
+ iconType: "material",
147
+ iconColor: "#FF775C",
148
+ titleLines: ["WIN", "BIG"],
149
+ titleAccentLine: null,
150
+ eyebrow: null,
151
+ subtitleText: "Scratch & Claim Your Prize!",
152
+ scratchText: "Scratch Here",
153
+ },
154
+
155
+ premium: {
156
+ background: ["#090909", "#202020"],
157
+ border: "#C89B3C",
158
+ title: "#F5D67B",
159
+ subtitle: "#F5E7BC",
160
+ accent: "#E2B94E",
161
+ scratch: ["#E1E5EA", "#C1C8D1"],
162
+ scratchBorder: "#D8B454",
163
+ scratchText: "#273043",
164
+ scratchIcon: "#273043",
165
+ icon: "crown",
166
+ iconType: "material",
167
+ iconColor: "#E3B943",
168
+ titleLines: ["PREMIUM", "SCRATCH CARD"],
169
+ titleAccentLine: null,
170
+ eyebrow: null,
171
+ subtitleText: "Exclusive Rewards Inside",
172
+ scratchText: "Scratch Here",
173
+ },
174
+
175
+ christmas: {
176
+ background: ["#0874D1", "#16A7E7"],
177
+ border: "#72D9FF",
178
+ title: "#FFFFFF",
179
+ subtitle: "#E4F7FF",
180
+ accent: "#FFFFFF",
181
+ scratch: ["#E1E5EA", "#C1C8D1"],
182
+ scratchBorder: "#FFFFFF",
183
+ scratchText: "#273043",
184
+ scratchIcon: "#273043",
185
+ icon: "snowman",
186
+ iconType: "material",
187
+ iconColor: "#FFFFFF",
188
+ titleLines: ["Holiday", "SURPRISE"],
189
+ titleAccentLine: null,
190
+ eyebrow: null,
191
+ subtitleText: "Scratch & Win Gifts",
192
+ scratchText: "Scratch Here",
193
+ },
194
+
195
+ mystery: {
196
+ background: ["#B58BFF", "#35BFFF"],
197
+ border: "#E3D5FF",
198
+ title: "#FFFFFF",
199
+ subtitle: "#F8F2FF",
200
+ accent: "#FF3A96",
201
+ scratch: ["#E1E5EA", "#C1C8D1"],
202
+ scratchBorder: "#FFFFFF",
203
+ scratchText: "#273043",
204
+ scratchIcon: "#273043",
205
+ icon: "help-circle",
206
+ iconType: "feather",
207
+ iconColor: "#6424C8",
208
+ titleLines: ["Mystery", "Scratch"],
209
+ titleAccentLine: null,
210
+ eyebrow: null,
211
+ subtitleText: "What Will You Get?",
212
+ scratchText: "Scratch Here",
213
+ },
214
+ };
215
+
216
+ const VARIANT_LABELS = {
217
+ classic: "1. Classic Reward",
218
+ trophy: "2. Trophy Theme",
219
+ gift: "3. Gift Theme",
220
+ travel: "4. Travel Theme",
221
+ discount: "5. Discount Theme",
222
+ space: "6. Space Theme",
223
+ premium: "7. Premium Theme",
224
+ christmas: "8. Christmas Theme",
225
+ mystery: "9. Mystery Theme",
226
+ };
227
+
228
+ function getIcon(theme, size = 65) {
229
+ const color = theme.iconColor;
230
+
231
+ if (theme.iconType === "ion") {
232
+ return <Ionicons name="airplane" size={size} color={color} />;
233
+ }
234
+
235
+ if (theme.iconType === "feather") {
236
+ return <Feather name="help-circle" size={size} color={color} />;
237
+ }
238
+
239
+ const names = {
240
+ gift: "gift",
241
+ trophy: "trophy",
242
+ percent: "percent",
243
+ rocket: "rocket-launch",
244
+ crown: "crown",
245
+ snowman: "snowman",
246
+ };
247
+
248
+ return (
249
+ <MaterialCommunityIcons
250
+ name={names[theme.icon] || "gift"}
251
+ size={size}
252
+ color={color}
253
+ />
254
+ );
255
+ }
256
+
257
+ function ThemeIcon({ icon, theme, size = 65 }) {
258
+ if (React.isValidElement(icon)) {
259
+ return icon;
260
+ }
261
+
262
+ return getIcon(theme, size);
263
+ }
264
+
265
+ function ScratchSurface({
266
+ width,
267
+ height,
268
+ coverColors,
269
+ borderColor,
270
+ text,
271
+ iconColor,
272
+ onProgress,
273
+ threshold,
274
+ revealed,
275
+ onReveal,
276
+ style,
277
+ }) {
278
+ const [paths, setPaths] = useState([]);
279
+ const [currentPath, setCurrentPath] = useState("");
280
+ const scratchedDistance = useRef(0);
281
+ const lastPoint = useRef(null);
282
+
283
+ const progressRef = useRef(0);
284
+
285
+ const calculateProgress = useCallback(
286
+ (amount) => {
287
+ scratchedDistance.current += amount;
288
+
289
+ /*
290
+ * This intentionally uses distance rather than number of
291
+ * touch events so slow and fast scratches both behave naturally.
292
+ */
293
+ const estimatedArea =
294
+ scratchedDistance.current / Math.max(width * height * 0.75, 1);
295
+
296
+ const nextProgress = Math.min(100, Math.round(estimatedArea * 100));
297
+
298
+ if (nextProgress > progressRef.current) {
299
+ progressRef.current = nextProgress;
300
+ onProgress?.(nextProgress);
301
+ }
302
+
303
+ if (nextProgress >= threshold && !revealed) {
304
+ onReveal?.();
305
+ }
306
+ },
307
+ [height, onProgress, onReveal, threshold, width, revealed],
308
+ );
309
+
310
+ const scratchAt = useCallback(
311
+ (x, y) => {
312
+ const point = `${x.toFixed(1)},${y.toFixed(1)}`;
313
+
314
+ setCurrentPath((previous) => {
315
+ if (!previous) {
316
+ return `M ${point}`;
317
+ }
318
+
319
+ return `${previous} L ${point}`;
320
+ });
321
+
322
+ if (lastPoint.current) {
323
+ const dx = x - lastPoint.current.x;
324
+ const dy = y - lastPoint.current.y;
325
+ const distance = Math.sqrt(dx * dx + dy * dy);
326
+
327
+ calculateProgress(distance);
328
+ }
329
+
330
+ lastPoint.current = {
331
+ x,
332
+ y,
333
+ };
334
+ },
335
+ [calculateProgress],
336
+ );
337
+
338
+ const finishScratch = useCallback(() => {
339
+ if (currentPath) {
340
+ setPaths((previous) => [...previous, currentPath]);
341
+ }
342
+
343
+ setCurrentPath("");
344
+ lastPoint.current = null;
345
+ }, [currentPath]);
346
+
347
+ const panResponder = useMemo(
348
+ () =>
349
+ PanResponder.create({
350
+ onStartShouldSetPanResponder: () => !revealed,
351
+ onMoveShouldSetPanResponder: () => !revealed,
352
+
353
+ onPanResponderGrant: (event) => {
354
+ const { locationX, locationY } = event.nativeEvent;
355
+
356
+ scratchAt(locationX, locationY);
357
+ },
358
+
359
+ onPanResponderMove: (event) => {
360
+ const { locationX, locationY } = event.nativeEvent;
361
+
362
+ scratchAt(locationX, locationY);
363
+ },
364
+
365
+ onPanResponderRelease: finishScratch,
366
+ onPanResponderTerminate: finishScratch,
367
+ }),
368
+ [finishScratch, revealed, scratchAt],
369
+ );
370
+
371
+ if (revealed) {
372
+ return null;
373
+ }
374
+
375
+ return (
376
+ <View
377
+ {...panResponder.panHandlers}
378
+ style={[
379
+ styles.scratchContainer,
380
+ {
381
+ width,
382
+ height,
383
+ borderColor,
384
+ },
385
+ style,
386
+ ]}
387
+ >
388
+ <Svg width={width} height={height} pointerEvents="none">
389
+ <Defs>
390
+ <LinearGradient id="scratchGradient" x1="0" y1="0" x2="1" y2="1">
391
+ <Stop offset="0" stopColor={coverColors[0]} />
392
+ <Stop offset="0.5" stopColor={coverColors[1]} />
393
+ <Stop offset="1" stopColor={coverColors[0]} />
394
+ </LinearGradient>
395
+
396
+ <Mask id="scratchMask">
397
+ <Rect x="0" y="0" width={width} height={height} fill="white" />
398
+
399
+ {paths.map((path, index) => (
400
+ <Path
401
+ key={`scratch-${index}`}
402
+ d={path}
403
+ stroke="black"
404
+ strokeWidth={34}
405
+ strokeLinecap="round"
406
+ strokeLinejoin="round"
407
+ fill="none"
408
+ />
409
+ ))}
410
+
411
+ {currentPath ? (
412
+ <Path
413
+ d={currentPath}
414
+ stroke="black"
415
+ strokeWidth={34}
416
+ strokeLinecap="round"
417
+ strokeLinejoin="round"
418
+ fill="none"
419
+ />
420
+ ) : null}
421
+ </Mask>
422
+ </Defs>
423
+
424
+ <G mask="url(#scratchMask)">
425
+ <Rect
426
+ x="0"
427
+ y="0"
428
+ width={width}
429
+ height={height}
430
+ fill="url(#scratchGradient)"
431
+ />
432
+
433
+ {/* Scratch texture */}
434
+ {Array.from({
435
+ length: 13,
436
+ }).map((_, index) => (
437
+ <Path
438
+ key={`texture-${index}`}
439
+ d={`M ${-20 + index * 35} ${height}
440
+ L ${60 + index * 35} 0`}
441
+ stroke="#FFFFFF"
442
+ strokeOpacity={0.16}
443
+ strokeWidth={1}
444
+ />
445
+ ))}
446
+
447
+ {Array.from({
448
+ length: 8,
449
+ }).map((_, index) => (
450
+ <Path
451
+ key={`texture2-${index}`}
452
+ d={`M ${index * 55} 0
453
+ L ${index * 55 + 80} ${height}`}
454
+ stroke="#6D7783"
455
+ strokeOpacity={0.08}
456
+ strokeWidth={1}
457
+ />
458
+ ))}
459
+ </G>
460
+ </Svg>
461
+
462
+ <View pointerEvents="none" style={styles.scratchPrompt}>
463
+ <MaterialCommunityIcons
464
+ name="gesture-tap"
465
+ size={30}
466
+ color={iconColor}
467
+ />
468
+
469
+ <Text
470
+ style={[
471
+ styles.scratchText,
472
+ {
473
+ color: "#263248",
474
+ },
475
+ ]}
476
+ >
477
+ {text}
478
+ </Text>
479
+ </View>
480
+ </View>
481
+ );
482
+ }
483
+
484
+ const UIScratchCard = forwardRef(
485
+ (
486
+ {
487
+ variant = "classic",
488
+
489
+ width = DEFAULT_WIDTH,
490
+ height = DEFAULT_HEIGHT,
491
+
492
+ title,
493
+ subtitle,
494
+ scratchText = "Scratch Here",
495
+
496
+ reward,
497
+ rewardIcon,
498
+
499
+ icon,
500
+ image,
501
+
502
+ scratchThreshold = 55,
503
+
504
+ reanimated = true,
505
+
506
+ onProgress,
507
+ onReveal,
508
+ onPress,
509
+
510
+ showThemeLabel = false,
511
+
512
+ borderRadius = 16,
513
+
514
+ titleStyle,
515
+ subtitleStyle,
516
+ rewardStyle,
517
+
518
+ scratchStyle,
519
+ cardStyle,
520
+ contentStyle,
521
+
522
+ backgroundColors,
523
+ backgroundColor,
524
+
525
+ children,
526
+
527
+ disabled = false,
528
+ },
529
+ ref,
530
+ ) => {
531
+ const theme = THEMES[variant] || THEMES.classic;
532
+
533
+ const [revealed, setRevealed] = useState(false);
534
+
535
+ const progressValue = useSharedValue(0);
536
+
537
+ const revealScale = useSharedValue(1);
538
+
539
+ const resolvedTitle = title || theme.titleLines;
540
+
541
+ const resolvedSubtitle = subtitle || theme.subtitleText;
542
+
543
+ const resolvedBackground = backgroundColors || theme.background;
544
+
545
+ const triggerReveal = useCallback(() => {
546
+ if (revealed) {
547
+ return;
548
+ }
549
+
550
+ setRevealed(true);
551
+
552
+ if (reanimated) {
553
+ revealScale.value = withSequence(
554
+ withTiming(0.96, {
555
+ duration: 100,
556
+ }),
557
+ withSpring(1),
558
+ );
559
+ }
560
+
561
+ onReveal?.();
562
+ }, [onReveal, reanimated, revealScale, revealed]);
563
+
564
+ const reset = useCallback(() => {
565
+ setRevealed(false);
566
+ progressValue.value = 0;
567
+ }, [progressValue]);
568
+
569
+ useImperativeHandle(
570
+ ref,
571
+ () => ({
572
+ reset,
573
+ reveal: triggerReveal,
574
+ isRevealed: revealed,
575
+ }),
576
+ [reset, revealed, triggerReveal],
577
+ );
578
+
579
+ const animatedRevealStyle = useAnimatedStyle(() => ({
580
+ transform: [
581
+ {
582
+ scale: revealScale.value,
583
+ },
584
+ ],
585
+ }));
586
+
587
+ const handleProgress = useCallback(
588
+ (progress) => {
589
+ progressValue.value = withTiming(progress, {
590
+ duration: 100,
591
+ });
592
+
593
+ onProgress?.(progress);
594
+ },
595
+ [onProgress, progressValue],
596
+ );
597
+
598
+ const cardContent = (
599
+ <View
600
+ style={[
601
+ styles.card,
602
+ {
603
+ width,
604
+ height,
605
+ borderRadius,
606
+ borderColor: theme.border,
607
+ backgroundColor: backgroundColor || resolvedBackground[0],
608
+ },
609
+ cardStyle,
610
+ ]}
611
+ >
612
+ {/* Background */}
613
+ <Svg
614
+ pointerEvents="none"
615
+ width={width}
616
+ height={height}
617
+ style={StyleSheet.absoluteFill}
618
+ >
619
+ <Defs>
620
+ <LinearGradient
621
+ id={`cardGradient-${variant}`}
622
+ x1="0"
623
+ y1="0"
624
+ x2="1"
625
+ y2="1"
626
+ >
627
+ <Stop offset="0" stopColor={resolvedBackground[0]} />
628
+ <Stop
629
+ offset="1"
630
+ stopColor={resolvedBackground[1] || resolvedBackground[0]}
631
+ />
632
+ </LinearGradient>
633
+ </Defs>
634
+
635
+ <Rect
636
+ x="0"
637
+ y="0"
638
+ width={width}
639
+ height={height}
640
+ rx={borderRadius}
641
+ fill={
642
+ backgroundColor
643
+ ? backgroundColor
644
+ : `url(#cardGradient-${variant})`
645
+ }
646
+ />
647
+ </Svg>
648
+
649
+ {/* Decorative stars / particles */}
650
+ <View pointerEvents="none" style={styles.decorations}>
651
+ <Text
652
+ style={[
653
+ styles.star,
654
+ {
655
+ color: theme.accent,
656
+ },
657
+ ]}
658
+ >
659
+
660
+ </Text>
661
+
662
+ <Text
663
+ style={[
664
+ styles.star,
665
+ styles.starTwo,
666
+ {
667
+ color: theme.accent,
668
+ },
669
+ ]}
670
+ >
671
+
672
+ </Text>
673
+
674
+ <Text
675
+ style={[
676
+ styles.star,
677
+ styles.starThree,
678
+ {
679
+ color: theme.accent,
680
+ },
681
+ ]}
682
+ >
683
+
684
+ </Text>
685
+ </View>
686
+
687
+ {/* Main top content */}
688
+ <View style={[styles.topContent, contentStyle]}>
689
+ <View style={styles.textArea}>
690
+ <Text
691
+ style={[
692
+ styles.title,
693
+ {
694
+ color: theme.title,
695
+ },
696
+ titleStyle,
697
+ ]}
698
+ >
699
+ {Array.isArray(resolvedTitle)
700
+ ? resolvedTitle.map((line, index) => (
701
+ <Text
702
+ key={`${line}-${index}`}
703
+ style={{
704
+ color:
705
+ index === theme.titleAccentLine
706
+ ? theme.accent
707
+ : theme.title,
708
+ }}
709
+ >
710
+ {line}
711
+ {index < resolvedTitle.length - 1 ? "\n" : ""}
712
+ </Text>
713
+ ))
714
+ : resolvedTitle}
715
+ </Text>
716
+
717
+ <Text
718
+ style={[
719
+ styles.subtitle,
720
+ {
721
+ color: theme.subtitle,
722
+ },
723
+ subtitleStyle,
724
+ ]}
725
+ >
726
+ {resolvedSubtitle}
727
+ </Text>
728
+ </View>
729
+
730
+ <View style={styles.visualArea}>
731
+ {image ? (
732
+ typeof image === "function" ? (
733
+ image(theme)
734
+ ) : (
735
+ <Animated.Image
736
+ source={image}
737
+ resizeMode="contain"
738
+ style={styles.visualImage}
739
+ />
740
+ )
741
+ ) : (
742
+ <ThemeIcon icon={icon} theme={theme} />
743
+ )}
744
+ </View>
745
+ </View>
746
+
747
+ {/* Scratch / reward */}
748
+ <View
749
+ style={[
750
+ styles.scratchWrapper,
751
+ {
752
+ left: 24,
753
+ right: 24,
754
+ bottom: 16,
755
+ height: Math.min(82, height * 0.37),
756
+ },
757
+ ]}
758
+ >
759
+ {revealed ? (
760
+ <Animated.View
761
+ entering={reanimated ? FadeIn.duration(220) : undefined}
762
+ style={[
763
+ styles.rewardContainer,
764
+ {
765
+ borderColor: theme.scratchBorder,
766
+ borderRadius: Math.min(14, borderRadius),
767
+ },
768
+ reanimated ? animatedRevealStyle : null,
769
+ scratchStyle,
770
+ ]}
771
+ >
772
+ {rewardIcon ? (
773
+ <View style={styles.rewardIcon}>{rewardIcon}</View>
774
+ ) : null}
775
+
776
+ {reward ? (
777
+ <Text style={[styles.rewardText, rewardStyle]}>{reward}</Text>
778
+ ) : (
779
+ <Text style={[styles.rewardText, rewardStyle]}>
780
+ 🎉 Reward Revealed!
781
+ </Text>
782
+ )}
783
+ </Animated.View>
784
+ ) : (
785
+ <ScratchSurface
786
+ width={width - 48}
787
+ height={Math.min(82, height * 0.37)}
788
+ coverColors={theme.scratch}
789
+ borderColor={theme.scratchBorder}
790
+ iconColor={theme.scratchIcon}
791
+ text={scratchText}
792
+ threshold={scratchThreshold}
793
+ revealed={revealed}
794
+ onProgress={handleProgress}
795
+ onReveal={triggerReveal}
796
+ style={scratchStyle}
797
+ />
798
+ )}
799
+ </View>
800
+
801
+ {children}
802
+ </View>
803
+ );
804
+
805
+ if (disabled) {
806
+ return cardContent;
807
+ }
808
+
809
+ return (
810
+ <Pressable
811
+ onPress={() =>
812
+ onPress?.({
813
+ variant,
814
+ revealed,
815
+ })
816
+ }
817
+ style={({ pressed }) => ({
818
+ opacity: pressed ? 0.96 : 1,
819
+ })}
820
+ >
821
+ {cardContent}
822
+ </Pressable>
823
+ );
824
+ },
825
+ );
826
+
827
+ UIScratchCard.displayName = "UIScratchCard";
828
+
829
+ export { UIScratchCard };
830
+
831
+ export { THEMES as UIScratchCardThemes, VARIANT_LABELS as UIScratchCardLabels };
832
+
833
+ const styles = StyleSheet.create({
834
+ card: {
835
+ overflow: "hidden",
836
+ borderWidth: 1.2,
837
+ position: "relative",
838
+ },
839
+
840
+ topContent: {
841
+ position: "absolute",
842
+ top: 20,
843
+ left: 22,
844
+ right: 18,
845
+ height: 100,
846
+ flexDirection: "row",
847
+ alignItems: "flex-start",
848
+ },
849
+
850
+ textArea: {
851
+ flex: 1,
852
+ paddingRight: 4,
853
+ },
854
+
855
+ title: {
856
+ fontSize: 27,
857
+ lineHeight: 29,
858
+ fontWeight: "900",
859
+ letterSpacing: -0.5,
860
+ },
861
+
862
+ subtitle: {
863
+ marginTop: 7,
864
+ fontSize: 13,
865
+ lineHeight: 17,
866
+ fontWeight: "500",
867
+ },
868
+
869
+ visualArea: {
870
+ width: 94,
871
+ height: 86,
872
+ justifyContent: "center",
873
+ alignItems: "center",
874
+ },
875
+
876
+ visualImage: {
877
+ width: 94,
878
+ height: 86,
879
+ },
880
+
881
+ decorations: {
882
+ ...StyleSheet.absoluteFillObject,
883
+ },
884
+
885
+ star: {
886
+ position: "absolute",
887
+ top: 18,
888
+ right: 18,
889
+ fontSize: 17,
890
+ },
891
+
892
+ starTwo: {
893
+ top: 48,
894
+ right: 112,
895
+ fontSize: 12,
896
+ },
897
+
898
+ starThree: {
899
+ top: 12,
900
+ left: 16,
901
+ fontSize: 13,
902
+ },
903
+
904
+ scratchWrapper: {
905
+ position: "absolute",
906
+ },
907
+
908
+ scratchContainer: {
909
+ overflow: "hidden",
910
+ borderWidth: 1.3,
911
+ borderRadius: 14,
912
+ position: "relative",
913
+ },
914
+
915
+ scratchPrompt: {
916
+ ...StyleSheet.absoluteFillObject,
917
+ justifyContent: "center",
918
+ alignItems: "center",
919
+ },
920
+
921
+ scratchText: {
922
+ marginTop: 2,
923
+ fontSize: 14,
924
+ fontWeight: "700",
925
+ },
926
+
927
+ rewardContainer: {
928
+ flex: 1,
929
+ borderWidth: 1.3,
930
+ backgroundColor: "#FFFFFF",
931
+ justifyContent: "center",
932
+ alignItems: "center",
933
+ paddingHorizontal: 12,
934
+ },
935
+
936
+ rewardText: {
937
+ color: "#263248",
938
+ fontSize: 18,
939
+ fontWeight: "900",
940
+ textAlign: "center",
941
+ },
942
+
943
+ rewardIcon: {
944
+ marginBottom: 4,
945
+ },
946
+ });
@@ -0,0 +1,5 @@
1
+ export {
2
+ default as UIScratchCard,
3
+ UIScratchCardThemes,
4
+ UIScratchCardLabels,
5
+ } from "./UIScratchCard";
@@ -125,3 +125,8 @@ export {
125
125
  export { default as UILayout } from "./layout/UILayout";
126
126
  export { default as UIHeader } from "./layout/UIHeader";
127
127
  export { default as UIBannerCarousel } from "./UIBannerCarousel";
128
+ export {
129
+ default as UIScratchCard,
130
+ UIScratchCardThemes,
131
+ UIScratchCardLabels,
132
+ } from "./UIScratchCard";
package/src/index.js CHANGED
@@ -135,4 +135,7 @@ export {
135
135
  UILayout,
136
136
  UIHeader,
137
137
  UIBannerCarousel,
138
+ UIScratchCard,
139
+ UIScratchCardThemes,
140
+ UIScratchCardLabels,
138
141
  } from "./components";