rnnovaui 0.3.0 → 0.3.1

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.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -1,11 +1,13 @@
1
1
  import React, { memo, useCallback, useMemo, useRef } from "react";
2
2
 
3
3
  import {
4
+ ActivityIndicator,
4
5
  Animated,
5
6
  Easing,
6
7
  Image,
7
8
  Pressable,
8
9
  StyleSheet,
10
+ Text,
9
11
  View,
10
12
  } from "react-native";
11
13
 
@@ -19,35 +21,71 @@ const CARD_VARIANTS = {
19
21
  };
20
22
 
21
23
  const CARD_RADIUS = {
22
- none: 0,
23
- sm: 8,
24
- md: 12,
25
- lg: 16,
26
- xl: 20,
24
+ none: "none",
25
+ sm: "sm",
26
+ md: "md",
27
+ lg: "lg",
28
+ xl: "xl",
29
+ };
30
+
31
+ const BADGE_POSITIONS = {
32
+ topLeft: "topLeft",
33
+ topCenter: "topCenter",
34
+ topRight: "topRight",
35
+
36
+ centerLeft: "centerLeft",
37
+ center: "center",
38
+ centerRight: "centerRight",
39
+
40
+ bottomLeft: "bottomLeft",
41
+ bottomCenter: "bottomCenter",
42
+ bottomRight: "bottomRight",
27
43
  };
28
44
 
29
45
  const FALLBACK_COLORS = {
30
- background: "#FFFFFF",
31
- surface: "#F8F8F8",
32
46
  card: "#FFFFFF",
47
+ surface: "#F8F8F8",
33
48
  border: "#E5E5E5",
34
- shadow: "#000000",
49
+
50
+ primary: "#FF5A1F",
51
+ primarySoft: "#FFF0EA",
52
+
53
+ success: "#16A34A",
54
+ danger: "#DC2626",
55
+ warning: "#D97706",
56
+ info: "#2563EB",
57
+
58
+ white: "#FFFFFF",
59
+ black: "#111111",
60
+
61
+ text: "#111111",
62
+ textSecondary: "#525252",
35
63
  };
36
64
 
37
65
  function UICardComponent({
38
66
  children,
39
67
 
40
68
  header = null,
41
-
42
69
  content = null,
43
-
44
70
  footer = null,
45
71
 
46
72
  image = null,
47
-
48
73
  imageSource = null,
49
74
 
50
- imageHeight = 180,
75
+ imageHeight = 190,
76
+ imageResizeMode = "cover",
77
+
78
+ imageStyle,
79
+
80
+ badges = [],
81
+
82
+ badgeGap = 8,
83
+
84
+ badgeStyle,
85
+
86
+ badgeTextStyle,
87
+
88
+ badgeContainerStyle,
51
89
 
52
90
  variant = "default",
53
91
 
@@ -60,25 +98,22 @@ function UICardComponent({
60
98
  style,
61
99
 
62
100
  headerStyle,
101
+ contentStyle,
102
+ footerStyle,
63
103
 
64
- imageStyle,
104
+ imageOverlay = null,
65
105
 
66
- contentStyle,
106
+ imageOverlayStyle,
67
107
 
68
- footerStyle,
108
+ pressable = false,
69
109
 
70
110
  disabled = false,
71
111
 
72
112
  loading = false,
73
113
 
74
- pressable = false,
75
-
76
114
  onPress,
77
-
78
115
  onLongPress,
79
-
80
116
  onPressIn,
81
-
82
117
  onPressOut,
83
118
 
84
119
  pressAnimation = true,
@@ -90,7 +125,6 @@ function UICardComponent({
90
125
  testID,
91
126
 
92
127
  accessibilityLabel,
93
-
94
128
  accessibilityHint,
95
129
 
96
130
  ...props
@@ -107,7 +141,9 @@ function UICardComponent({
107
141
 
108
142
  const safeVariant = CARD_VARIANTS[variant] ? variant : CARD_VARIANTS.default;
109
143
 
110
- const resolvedRadius = CARD_RADIUS[radius] ?? CARD_RADIUS.lg;
144
+ const resolvedRadius = CARD_RADIUS[radius] ? radius : CARD_RADIUS.lg;
145
+
146
+ const radiusValue = getRadiusValue(resolvedRadius, theme);
111
147
 
112
148
  const cardColors = useMemo(
113
149
  () => getCardColors(safeVariant, colors),
@@ -215,14 +251,8 @@ function UICardComponent({
215
251
 
216
252
  borderWidth: cardColors.borderWidth,
217
253
 
218
- borderRadius: resolvedRadius,
219
-
220
- padding,
221
- },
222
-
223
- cardColors.shadow,
254
+ borderRadius: radiusValue,
224
255
 
225
- {
226
256
  opacity: isDisabled ? 0.55 : 1,
227
257
 
228
258
  transform: [
@@ -232,89 +262,128 @@ function UICardComponent({
232
262
  ],
233
263
  },
234
264
 
265
+ cardColors.shadow,
266
+
235
267
  style,
236
268
  ],
237
- [cardColors, resolvedRadius, padding, isDisabled, scale, style],
269
+ [cardColors, radiusValue, isDisabled, scale, style],
238
270
  );
239
271
 
272
+ const renderImage = Boolean(image || imageSource);
273
+
274
+ const renderBody = Boolean(header || content || children || footer);
275
+
240
276
  const cardContent = (
241
277
  <>
242
- {image || imageSource ? (
278
+ {renderImage ? (
243
279
  <View
244
280
  style={[
245
- styles.imageWrapper,
281
+ styles.imageSection,
282
+
246
283
  {
247
284
  height: imageHeight,
248
285
 
249
- borderTopLeftRadius: resolvedRadius,
286
+ borderTopLeftRadius: radiusValue,
250
287
 
251
- borderTopRightRadius: resolvedRadius,
288
+ borderTopRightRadius: radiusValue,
252
289
  },
253
290
  ]}
254
291
  >
255
292
  {image || (
256
293
  <Image
257
294
  source={imageSource}
258
- resizeMode="cover"
295
+ resizeMode={imageResizeMode}
259
296
  style={[styles.image, imageStyle]}
260
297
  />
261
298
  )}
262
- </View>
263
- ) : null}
264
299
 
265
- {header ? (
266
- <View
267
- style={[
268
- styles.header,
269
- {
270
- marginTop: image || imageSource ? gap : 0,
271
- },
272
- headerStyle,
273
- ]}
274
- >
275
- {header}
300
+ {imageOverlay ? (
301
+ <View
302
+ pointerEvents="none"
303
+ style={[styles.imageOverlay, imageOverlayStyle]}
304
+ >
305
+ {imageOverlay}
306
+ </View>
307
+ ) : null}
308
+
309
+ {badges.length > 0 ? (
310
+ <View pointerEvents="box-none" style={styles.badgeLayer}>
311
+ {badges.map((badge, index) => (
312
+ <CardBadge
313
+ key={
314
+ badge.id ||
315
+ badge.key ||
316
+ `${badge.position || "topLeft"}-${index}`
317
+ }
318
+ badge={badge}
319
+ index={index}
320
+ gap={badgeGap}
321
+ defaultStyle={badgeStyle}
322
+ defaultTextStyle={badgeTextStyle}
323
+ defaultContainerStyle={badgeContainerStyle}
324
+ colors={colors}
325
+ />
326
+ ))}
327
+ </View>
328
+ ) : null}
276
329
  </View>
277
330
  ) : null}
278
331
 
279
- {content ? (
332
+ {renderBody ? (
280
333
  <View
281
334
  style={[
282
- styles.content,
283
- {
284
- marginTop: header ? gap : 0,
285
- },
286
- contentStyle,
287
- ]}
288
- >
289
- {content}
290
- </View>
291
- ) : null}
335
+ styles.body,
292
336
 
293
- {children ? (
294
- <View
295
- style={[
296
- styles.content,
297
337
  {
298
- marginTop: header || content ? gap : 0,
338
+ padding,
339
+ gap,
299
340
  },
300
- contentStyle,
301
341
  ]}
302
342
  >
303
- {children}
343
+ {header ? (
344
+ <View style={[styles.header, headerStyle]}>{header}</View>
345
+ ) : null}
346
+
347
+ {content ? (
348
+ <View style={[styles.content, contentStyle]}>{content}</View>
349
+ ) : null}
350
+
351
+ {children ? (
352
+ <View
353
+ style={[
354
+ styles.content,
355
+
356
+ contentStyle,
357
+
358
+ {
359
+ marginTop: header || content ? 0 : undefined,
360
+ },
361
+ ]}
362
+ >
363
+ {children}
364
+ </View>
365
+ ) : null}
366
+
367
+ {footer ? (
368
+ <View style={[styles.footer, footerStyle]}>{footer}</View>
369
+ ) : null}
304
370
  </View>
305
371
  ) : null}
306
372
 
307
- {footer ? (
373
+ {loading ? (
308
374
  <View
375
+ pointerEvents="none"
309
376
  style={[
310
- styles.footer,
377
+ styles.loadingOverlay,
311
378
  {
312
- marginTop: header || content || children ? gap : 0,
379
+ borderRadius: radiusValue,
313
380
  },
314
- footerStyle,
315
381
  ]}
316
382
  >
317
- {footer}
383
+ <ActivityIndicator
384
+ size="small"
385
+ color={colors.primary || FALLBACK_COLORS.primary}
386
+ />
318
387
  </View>
319
388
  ) : null}
320
389
  </>
@@ -357,6 +426,90 @@ function UICardComponent({
357
426
  );
358
427
  }
359
428
 
429
+ /*
430
+ * --------------------------------------------------
431
+ * CARD BADGE
432
+ * --------------------------------------------------
433
+ */
434
+
435
+ const CardBadge = memo(function CardBadge({
436
+ badge,
437
+ index,
438
+ gap,
439
+ defaultStyle,
440
+ defaultTextStyle,
441
+ defaultContainerStyle,
442
+ colors,
443
+ }) {
444
+ const position = BADGE_POSITIONS[badge.position] ? badge.position : "topLeft";
445
+
446
+ const badgeColors = getBadgeColors(badge, colors);
447
+
448
+ const positionStyle = getBadgePosition(position, index, gap);
449
+
450
+ const badgeContent = badge.content ?? badge.label ?? badge.text ?? "";
451
+
452
+ return (
453
+ <View
454
+ pointerEvents="none"
455
+ style={[
456
+ styles.badge,
457
+
458
+ positionStyle,
459
+
460
+ {
461
+ backgroundColor: badgeColors.background,
462
+
463
+ borderColor: badgeColors.border,
464
+
465
+ borderWidth: badgeColors.borderWidth,
466
+ },
467
+
468
+ defaultStyle,
469
+
470
+ badge.style,
471
+
472
+ defaultContainerStyle,
473
+ ]}
474
+ >
475
+ {badge.icon ? <View style={styles.badgeIcon}>{badge.icon}</View> : null}
476
+
477
+ {badgeContent ? (
478
+ <Text
479
+ numberOfLines={1}
480
+ style={[
481
+ styles.badgeText,
482
+
483
+ {
484
+ color: badgeColors.text,
485
+
486
+ fontSize: badge.fontSize || 12,
487
+
488
+ lineHeight: badge.lineHeight || 16,
489
+
490
+ fontWeight: badge.fontWeight || "600",
491
+ },
492
+
493
+ defaultTextStyle,
494
+
495
+ badge.textStyle,
496
+ ]}
497
+ >
498
+ {badgeContent}
499
+ </Text>
500
+ ) : null}
501
+ </View>
502
+ );
503
+ });
504
+
505
+ CardBadge.displayName = "CardBadge";
506
+
507
+ /*
508
+ * --------------------------------------------------
509
+ * CARD COLORS
510
+ * --------------------------------------------------
511
+ */
512
+
360
513
  function getCardColors(variant, colors) {
361
514
  const background = colors.card || colors.surface || FALLBACK_COLORS.card;
362
515
 
@@ -389,7 +542,7 @@ function getCardColors(variant, colors) {
389
542
  return {
390
543
  background: colors.surface || FALLBACK_COLORS.surface,
391
544
 
392
- border,
545
+ border: "transparent",
393
546
 
394
547
  borderWidth: 0,
395
548
 
@@ -401,7 +554,7 @@ function getCardColors(variant, colors) {
401
554
  return {
402
555
  background,
403
556
 
404
- border,
557
+ border: "transparent",
405
558
 
406
559
  borderWidth: 0,
407
560
 
@@ -410,27 +563,249 @@ function getCardColors(variant, colors) {
410
563
  }
411
564
  }
412
565
 
566
+ /*
567
+ * --------------------------------------------------
568
+ * BADGE COLORS
569
+ * --------------------------------------------------
570
+ */
571
+
572
+ function getBadgeColors(badge, colors) {
573
+ const type = badge.variant || badge.type || "primary";
574
+
575
+ switch (type) {
576
+ case "success":
577
+ return {
578
+ background:
579
+ badge.backgroundColor || colors.success || FALLBACK_COLORS.success,
580
+
581
+ border: badge.borderColor || colors.success || FALLBACK_COLORS.success,
582
+
583
+ borderWidth: badge.borderWidth ?? 0,
584
+
585
+ text: badge.textColor || colors.onSuccess || FALLBACK_COLORS.white,
586
+ };
587
+
588
+ case "danger":
589
+ return {
590
+ background:
591
+ badge.backgroundColor || colors.danger || FALLBACK_COLORS.danger,
592
+
593
+ border: badge.borderColor || colors.danger || FALLBACK_COLORS.danger,
594
+
595
+ borderWidth: badge.borderWidth ?? 0,
596
+
597
+ text: badge.textColor || colors.onDanger || FALLBACK_COLORS.white,
598
+ };
599
+
600
+ case "warning":
601
+ return {
602
+ background:
603
+ badge.backgroundColor || colors.warning || FALLBACK_COLORS.warning,
604
+
605
+ border: badge.borderColor || colors.warning || FALLBACK_COLORS.warning,
606
+
607
+ borderWidth: badge.borderWidth ?? 0,
608
+
609
+ text: badge.textColor || colors.onWarning || FALLBACK_COLORS.white,
610
+ };
611
+
612
+ case "info":
613
+ return {
614
+ background:
615
+ badge.backgroundColor || colors.info || FALLBACK_COLORS.info,
616
+
617
+ border: badge.borderColor || colors.info || FALLBACK_COLORS.info,
618
+
619
+ borderWidth: badge.borderWidth ?? 0,
620
+
621
+ text: badge.textColor || colors.onInfo || FALLBACK_COLORS.white,
622
+ };
623
+
624
+ case "soft":
625
+ return {
626
+ background:
627
+ badge.backgroundColor ||
628
+ colors.primarySoft ||
629
+ FALLBACK_COLORS.primarySoft,
630
+
631
+ border: badge.borderColor || "transparent",
632
+
633
+ borderWidth: badge.borderWidth ?? 0,
634
+
635
+ text: badge.textColor || colors.primary || FALLBACK_COLORS.primary,
636
+ };
637
+
638
+ case "custom":
639
+ return {
640
+ background: badge.backgroundColor || FALLBACK_COLORS.primary,
641
+
642
+ border: badge.borderColor || "transparent",
643
+
644
+ borderWidth: badge.borderWidth ?? 0,
645
+
646
+ text: badge.textColor || FALLBACK_COLORS.white,
647
+ };
648
+
649
+ case "primary":
650
+ default:
651
+ return {
652
+ background:
653
+ badge.backgroundColor || colors.primary || FALLBACK_COLORS.primary,
654
+
655
+ border: badge.borderColor || colors.primary || FALLBACK_COLORS.primary,
656
+
657
+ borderWidth: badge.borderWidth ?? 0,
658
+
659
+ text: badge.textColor || colors.onPrimary || FALLBACK_COLORS.white,
660
+ };
661
+ }
662
+ }
663
+
664
+ /*
665
+ * --------------------------------------------------
666
+ * BADGE POSITION
667
+ * --------------------------------------------------
668
+ */
669
+
670
+ function getBadgePosition(position, index, gap) {
671
+ const offset = index * (28 + gap);
672
+
673
+ switch (position) {
674
+ case BADGE_POSITIONS.topCenter:
675
+ return {
676
+ top: 12 + offset,
677
+
678
+ alignSelf: "center",
679
+ };
680
+
681
+ case BADGE_POSITIONS.topRight:
682
+ return {
683
+ top: 12 + offset,
684
+
685
+ right: 12,
686
+ };
687
+
688
+ case BADGE_POSITIONS.centerLeft:
689
+ return {
690
+ left: 12 + offset,
691
+
692
+ top: "50%",
693
+
694
+ transform: [
695
+ {
696
+ translateY: -12,
697
+ },
698
+ ],
699
+ };
700
+
701
+ case BADGE_POSITIONS.center:
702
+ return {
703
+ top: "50%",
704
+
705
+ left: "50%",
706
+
707
+ transform: [
708
+ {
709
+ translateX: -50,
710
+ },
711
+
712
+ {
713
+ translateY: -12,
714
+ },
715
+ ],
716
+ };
717
+
718
+ case BADGE_POSITIONS.centerRight:
719
+ return {
720
+ right: 12 + offset,
721
+
722
+ top: "50%",
723
+
724
+ transform: [
725
+ {
726
+ translateY: -12,
727
+ },
728
+ ],
729
+ };
730
+
731
+ case BADGE_POSITIONS.bottomLeft:
732
+ return {
733
+ bottom: 12,
734
+
735
+ left: 12 + offset,
736
+ };
737
+
738
+ case BADGE_POSITIONS.bottomCenter:
739
+ return {
740
+ bottom: 12 + offset,
741
+
742
+ alignSelf: "center",
743
+ };
744
+
745
+ case BADGE_POSITIONS.bottomRight:
746
+ return {
747
+ bottom: 12,
748
+
749
+ right: 12 + offset,
750
+ };
751
+
752
+ case BADGE_POSITIONS.topLeft:
753
+ default:
754
+ return {
755
+ top: 12 + offset,
756
+
757
+ left: 12,
758
+ };
759
+ }
760
+ }
761
+
762
+ /*
763
+ * --------------------------------------------------
764
+ * RADIUS
765
+ * --------------------------------------------------
766
+ */
767
+
768
+ function getRadiusValue(radius, theme) {
769
+ const themeRadius = theme?.radius || {};
770
+
771
+ switch (radius) {
772
+ case CARD_RADIUS.none:
773
+ return 0;
774
+
775
+ case CARD_RADIUS.sm:
776
+ return themeRadius.sm ?? 8;
777
+
778
+ case CARD_RADIUS.md:
779
+ return themeRadius.md ?? 12;
780
+
781
+ case CARD_RADIUS.xl:
782
+ return themeRadius.xl ?? 20;
783
+
784
+ case CARD_RADIUS.lg:
785
+ default:
786
+ return themeRadius.lg ?? 16;
787
+ }
788
+ }
789
+
413
790
  const styles = StyleSheet.create({
414
791
  card: {
415
792
  width: "100%",
416
793
 
417
794
  overflow: "hidden",
795
+
796
+ position: "relative",
418
797
  },
419
798
 
420
799
  pressable: {
421
800
  width: "100%",
422
801
  },
423
802
 
424
- imageWrapper: {
803
+ imageSection: {
425
804
  width: "100%",
426
805
 
427
- overflow: "hidden",
428
-
429
- marginHorizontal: -16,
806
+ position: "relative",
430
807
 
431
- marginTop: -16,
432
-
433
- width: "calc(100% + 32px)",
808
+ overflow: "hidden",
434
809
  },
435
810
 
436
811
  image: {
@@ -439,6 +814,64 @@ const styles = StyleSheet.create({
439
814
  height: "100%",
440
815
  },
441
816
 
817
+ imageOverlay: {
818
+ ...StyleSheet.absoluteFillObject,
819
+
820
+ justifyContent: "center",
821
+
822
+ alignItems: "center",
823
+ },
824
+
825
+ badgeLayer: {
826
+ ...StyleSheet.absoluteFillObject,
827
+
828
+ pointerEvents: "box-none",
829
+ },
830
+
831
+ badge: {
832
+ position: "absolute",
833
+
834
+ minHeight: 26,
835
+
836
+ paddingHorizontal: 10,
837
+
838
+ paddingVertical: 5,
839
+
840
+ borderRadius: 999,
841
+
842
+ flexDirection: "row",
843
+
844
+ alignItems: "center",
845
+
846
+ justifyContent: "center",
847
+
848
+ maxWidth: "80%",
849
+
850
+ zIndex: 10,
851
+
852
+ elevation: 3,
853
+ },
854
+
855
+ badgeText: {
856
+ includeFontPadding: false,
857
+
858
+ textAlign: "center",
859
+
860
+ flexShrink: 1,
861
+ },
862
+
863
+ badgeIcon: {
864
+ marginRight: 5,
865
+
866
+ alignItems: "center",
867
+
868
+ justifyContent: "center",
869
+ },
870
+
871
+ body: {
872
+ width: "100%",
873
+ },
874
+
442
875
  header: {
443
876
  width: "100%",
444
877
  },
@@ -451,8 +884,20 @@ const styles = StyleSheet.create({
451
884
  width: "100%",
452
885
  },
453
886
 
887
+ loadingOverlay: {
888
+ ...StyleSheet.absoluteFillObject,
889
+
890
+ alignItems: "center",
891
+
892
+ justifyContent: "center",
893
+
894
+ backgroundColor: "rgba(0,0,0,0.08)",
895
+
896
+ zIndex: 100,
897
+ },
898
+
454
899
  elevated: {
455
- shadowColor: FALLBACK_COLORS.shadow,
900
+ shadowColor: "#000000",
456
901
 
457
902
  shadowOffset: {
458
903
  width: 0,
@@ -477,6 +922,10 @@ export const UICard = memo(UICardComponent);
477
922
 
478
923
  UICard.displayName = "UICard";
479
924
 
480
- export { CARD_VARIANTS as UICardVariants, CARD_RADIUS as UICardRadius };
925
+ export {
926
+ CARD_VARIANTS as UICardVariants,
927
+ CARD_RADIUS as UICardRadius,
928
+ BADGE_POSITIONS as UICardBadgePositions,
929
+ };
481
930
 
482
931
  export default UICard;
@@ -1 +1,6 @@
1
- export { UICard, UICardVariants, UICardRadius } from "./UICard";
1
+ export {
2
+ UICard,
3
+ UICardVariants,
4
+ UICardRadius,
5
+ UICardBadgePositions,
6
+ } from "./UICard";
@@ -6,4 +6,9 @@ export {
6
6
  UIIconButtonSizes,
7
7
  UIIconButtonShapes,
8
8
  } from "./IconButton";
9
- export { UICard, UICardVariants, UICardRadius } from "./Card";
9
+ export {
10
+ UICard,
11
+ UICardVariants,
12
+ UICardRadius,
13
+ UICardBadgePositions,
14
+ } from "./Card";
package/src/index.js CHANGED
@@ -27,4 +27,5 @@ export {
27
27
  UICard,
28
28
  UICardVariants,
29
29
  UICardRadius,
30
+ UICardBadgePositions,
30
31
  } from "./components";