rnnovaui 0.12.3 → 0.12.4

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.12.3",
3
+ "version": "0.12.4",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -0,0 +1,1240 @@
1
+ import React, {
2
+ forwardRef,
3
+ memo,
4
+ useCallback,
5
+ useEffect,
6
+ useImperativeHandle,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+
12
+ import {
13
+ Dimensions,
14
+ FlatList,
15
+ Image,
16
+ Pressable,
17
+ StyleSheet,
18
+ Text,
19
+ View,
20
+ } from "react-native";
21
+
22
+ import { Ionicons } from "@expo/vector-icons";
23
+
24
+ import Animated, {
25
+ Easing,
26
+ Extrapolation,
27
+ interpolate,
28
+ useAnimatedStyle,
29
+ useSharedValue,
30
+ withDelay,
31
+ withRepeat,
32
+ withSequence,
33
+ withSpring,
34
+ withTiming,
35
+ } from "react-native-reanimated";
36
+
37
+ import { LinearGradient } from "expo-linear-gradient";
38
+
39
+ import { useUITheme } from "../../theme/UIProvider";
40
+
41
+ /* -------------------------------------------------------------------------- */
42
+ /* Constants */
43
+ /* -------------------------------------------------------------------------- */
44
+
45
+ const { width: SCREEN_WIDTH } = Dimensions.get("window");
46
+
47
+ export const CAROUSEL_ANIMATIONS = [
48
+ "none",
49
+
50
+ "fade",
51
+
52
+ "slideUp",
53
+ "slideDown",
54
+ "slideLeft",
55
+ "slideRight",
56
+
57
+ "scale",
58
+ "scaleUp",
59
+ "scaleDown",
60
+
61
+ "zoomIn",
62
+ "zoomOut",
63
+
64
+ "bounce",
65
+
66
+ "pulse",
67
+
68
+ "float",
69
+
70
+ "rotate",
71
+
72
+ "rotateReverse",
73
+
74
+ "swing",
75
+
76
+ "shake",
77
+
78
+ "flip",
79
+
80
+ "flipX",
81
+ "flipY",
82
+
83
+ "elastic",
84
+ ];
85
+
86
+ export const CAROUSEL_TEXT_ANIMATIONS = CAROUSEL_ANIMATIONS;
87
+
88
+ export const CAROUSEL_ASSET_ANIMATIONS = CAROUSEL_ANIMATIONS;
89
+
90
+ export const CAROUSEL_BACKGROUND_ANIMATIONS = [
91
+ "none",
92
+ "fade",
93
+ "zoom",
94
+ "pulse",
95
+ "float",
96
+ "shimmer",
97
+ ];
98
+
99
+ /* -------------------------------------------------------------------------- */
100
+ /* Helpers */
101
+ /* -------------------------------------------------------------------------- */
102
+
103
+ const getAnimationValue = (animation, progress, axis = "y") => {
104
+ if (animation === "none") {
105
+ return 0;
106
+ }
107
+
108
+ if (animation === "slideUp") {
109
+ return axis === "y" ? 60 * (1 - progress) : 0;
110
+ }
111
+
112
+ if (animation === "slideDown") {
113
+ return axis === "y" ? -60 * (1 - progress) : 0;
114
+ }
115
+
116
+ if (animation === "slideLeft") {
117
+ return axis === "x" ? 70 * (1 - progress) : 0;
118
+ }
119
+
120
+ if (animation === "slideRight") {
121
+ return axis === "x" ? -70 * (1 - progress) : 0;
122
+ }
123
+
124
+ return 0;
125
+ };
126
+
127
+ /* -------------------------------------------------------------------------- */
128
+ /* Advanced animated element */
129
+ /* -------------------------------------------------------------------------- */
130
+
131
+ const UIAnimatedElement = memo(function UIAnimatedElement({
132
+ animation = "none",
133
+ delay = 0,
134
+ duration = 600,
135
+ repeat = false,
136
+ repeatCount = -1,
137
+
138
+ children,
139
+
140
+ style,
141
+ }) {
142
+ const progress = useSharedValue(animation === "none" ? 1 : 0);
143
+
144
+ const loop = repeatCount === -1 ? -1 : repeatCount;
145
+
146
+ useEffect(() => {
147
+ if (animation === "none") {
148
+ progress.value = 1;
149
+ return;
150
+ }
151
+
152
+ progress.value = 0;
153
+
154
+ const timing = withTiming(1, {
155
+ duration,
156
+ easing: Easing.out(Easing.cubic),
157
+ });
158
+
159
+ if (repeat) {
160
+ progress.value = withDelay(delay, withRepeat(timing, loop, true));
161
+ } else {
162
+ progress.value = withDelay(delay, timing);
163
+ }
164
+ }, [animation, delay, duration, repeat, repeatCount, progress, loop]);
165
+
166
+ const animatedStyle = useAnimatedStyle(() => {
167
+ const p = progress.value;
168
+
169
+ let opacity = 1;
170
+ let scale = 1;
171
+ let translateX = 0;
172
+ let translateY = 0;
173
+ let rotate = 0;
174
+ let rotateX = 0;
175
+ let rotateY = 0;
176
+
177
+ switch (animation) {
178
+ case "fade":
179
+ opacity = p;
180
+ break;
181
+
182
+ case "slideUp":
183
+ opacity = p;
184
+ translateY = 60 * (1 - p);
185
+ break;
186
+
187
+ case "slideDown":
188
+ opacity = p;
189
+ translateY = -60 * (1 - p);
190
+ break;
191
+
192
+ case "slideLeft":
193
+ opacity = p;
194
+ translateX = 70 * (1 - p);
195
+ break;
196
+
197
+ case "slideRight":
198
+ opacity = p;
199
+ translateX = -70 * (1 - p);
200
+ break;
201
+
202
+ case "scale":
203
+ opacity = p;
204
+ scale = 0.7 + 0.3 * p;
205
+ break;
206
+
207
+ case "scaleUp":
208
+ opacity = p;
209
+ scale = 0.5 + 0.5 * p;
210
+ break;
211
+
212
+ case "scaleDown":
213
+ opacity = p;
214
+ scale = 1.4 - 0.4 * p;
215
+ break;
216
+
217
+ case "zoomIn":
218
+ scale = 0.7 + 0.3 * p;
219
+ break;
220
+
221
+ case "zoomOut":
222
+ scale = 1.3 - 0.3 * p;
223
+ break;
224
+
225
+ case "bounce":
226
+ translateY = interpolate(
227
+ p,
228
+ [0, 0.6, 0.8, 1],
229
+ [50, -12, 5, 0],
230
+ Extrapolation.CLAMP,
231
+ );
232
+ opacity = p;
233
+ break;
234
+
235
+ case "pulse":
236
+ scale = interpolate(p, [0, 0.5, 1], [1, 1.12, 1], Extrapolation.CLAMP);
237
+ break;
238
+
239
+ case "float":
240
+ translateY = interpolate(
241
+ p,
242
+ [0, 0.5, 1],
243
+ [0, -12, 0],
244
+ Extrapolation.CLAMP,
245
+ );
246
+ break;
247
+
248
+ case "rotate":
249
+ rotate = interpolate(p, [0, 1], [-8, 8], Extrapolation.CLAMP);
250
+ break;
251
+
252
+ case "rotateReverse":
253
+ rotate = interpolate(p, [0, 1], [8, -8], Extrapolation.CLAMP);
254
+ break;
255
+
256
+ case "swing":
257
+ rotate = interpolate(
258
+ p,
259
+ [0, 0.25, 0.5, 0.75, 1],
260
+ [-12, 10, -8, 5, 0],
261
+ Extrapolation.CLAMP,
262
+ );
263
+ break;
264
+
265
+ case "shake":
266
+ translateX = interpolate(
267
+ p,
268
+ [0, 0.2, 0.4, 0.6, 0.8, 1],
269
+ [0, -8, 8, -6, 4, 0],
270
+ Extrapolation.CLAMP,
271
+ );
272
+ break;
273
+
274
+ case "flip":
275
+ case "flipY":
276
+ rotateY = interpolate(p, [0, 1], [90, 0], Extrapolation.CLAMP);
277
+ break;
278
+
279
+ case "flipX":
280
+ rotateX = interpolate(p, [0, 1], [90, 0], Extrapolation.CLAMP);
281
+ break;
282
+
283
+ case "elastic":
284
+ scale = interpolate(
285
+ p,
286
+ [0, 0.4, 0.7, 1],
287
+ [0.4, 1.15, 0.95, 1],
288
+ Extrapolation.CLAMP,
289
+ );
290
+ break;
291
+
292
+ default:
293
+ break;
294
+ }
295
+
296
+ return {
297
+ opacity,
298
+
299
+ transform: [
300
+ {
301
+ translateX,
302
+ },
303
+ {
304
+ translateY,
305
+ },
306
+ {
307
+ scale,
308
+ },
309
+ {
310
+ rotate: `${rotate}deg`,
311
+ },
312
+ {
313
+ rotateX: `${rotateX}deg`,
314
+ },
315
+ {
316
+ rotateY: `${rotateY}deg`,
317
+ },
318
+ ],
319
+ };
320
+ }, [animation]);
321
+
322
+ return (
323
+ <Animated.View style={[style, animatedStyle]}>{children}</Animated.View>
324
+ );
325
+ });
326
+
327
+ /* -------------------------------------------------------------------------- */
328
+ /* Animated text */
329
+ /* -------------------------------------------------------------------------- */
330
+
331
+ const AnimatedText = memo(function AnimatedText({ item }) {
332
+ const {
333
+ text,
334
+ x = 0,
335
+ y = 0,
336
+
337
+ width,
338
+ fontSize = 20,
339
+ lineHeight,
340
+
341
+ color = "#FFFFFF",
342
+ fontWeight = "700",
343
+
344
+ textAlign = "left",
345
+
346
+ animation = "none",
347
+ delay = 0,
348
+ duration = 600,
349
+
350
+ repeat = false,
351
+ repeatCount = -1,
352
+
353
+ style,
354
+ numberOfLines,
355
+ } = item;
356
+
357
+ return (
358
+ <UIAnimatedElement
359
+ animation={animation}
360
+ delay={delay}
361
+ duration={duration}
362
+ repeat={repeat}
363
+ repeatCount={repeatCount}
364
+ style={[
365
+ carouselStyles.absoluteElement,
366
+ {
367
+ left: x,
368
+ top: y,
369
+ width,
370
+ },
371
+ ]}
372
+ >
373
+ <Text
374
+ numberOfLines={numberOfLines}
375
+ style={[
376
+ {
377
+ fontSize,
378
+ lineHeight,
379
+ color,
380
+ fontWeight,
381
+ textAlign,
382
+ },
383
+ style,
384
+ ]}
385
+ >
386
+ {text}
387
+ </Text>
388
+ </UIAnimatedElement>
389
+ );
390
+ });
391
+
392
+ /* -------------------------------------------------------------------------- */
393
+ /* Animated image */
394
+ /* -------------------------------------------------------------------------- */
395
+
396
+ const AnimatedImage = memo(function AnimatedImage({ item }) {
397
+ const {
398
+ source,
399
+
400
+ x = 0,
401
+ y = 0,
402
+
403
+ width = 100,
404
+ height = 100,
405
+
406
+ rotation,
407
+
408
+ opacity = 1,
409
+
410
+ resizeMode = "contain",
411
+
412
+ animation = "none",
413
+ delay = 0,
414
+ duration = 600,
415
+
416
+ repeat = false,
417
+ repeatCount = -1,
418
+
419
+ style,
420
+ } = item;
421
+
422
+ return (
423
+ <UIAnimatedElement
424
+ animation={animation}
425
+ delay={delay}
426
+ duration={duration}
427
+ repeat={repeat}
428
+ repeatCount={repeatCount}
429
+ style={[
430
+ carouselStyles.absoluteElement,
431
+
432
+ {
433
+ left: x,
434
+ top: y,
435
+
436
+ width,
437
+ height,
438
+
439
+ opacity,
440
+
441
+ transform: rotation
442
+ ? [
443
+ {
444
+ rotate: rotation,
445
+ },
446
+ ]
447
+ : undefined,
448
+ },
449
+
450
+ style,
451
+ ]}
452
+ >
453
+ <Image
454
+ source={
455
+ typeof source === "string"
456
+ ? {
457
+ uri: source,
458
+ }
459
+ : source
460
+ }
461
+ resizeMode={resizeMode}
462
+ style={StyleSheet.absoluteFillObject}
463
+ />
464
+ </UIAnimatedElement>
465
+ );
466
+ });
467
+
468
+ /* -------------------------------------------------------------------------- */
469
+ /* Animated icon */
470
+ /* -------------------------------------------------------------------------- */
471
+
472
+ const AnimatedIcon = memo(function AnimatedIcon({ item }) {
473
+ const {
474
+ name = "star",
475
+
476
+ x = 0,
477
+ y = 0,
478
+
479
+ size = 24,
480
+
481
+ color = "#FFFFFF",
482
+
483
+ rotation,
484
+
485
+ animation = "none",
486
+ delay = 0,
487
+ duration = 600,
488
+
489
+ repeat = false,
490
+ repeatCount = -1,
491
+
492
+ style,
493
+ } = item;
494
+
495
+ const Icon = item.iconComponent;
496
+
497
+ return (
498
+ <UIAnimatedElement
499
+ animation={animation}
500
+ delay={delay}
501
+ duration={duration}
502
+ repeat={repeat}
503
+ repeatCount={repeatCount}
504
+ style={[
505
+ carouselStyles.absoluteElement,
506
+
507
+ {
508
+ left: x,
509
+ top: y,
510
+ },
511
+
512
+ style,
513
+ ]}
514
+ >
515
+ {Icon ? (
516
+ <Icon size={size} color={color} />
517
+ ) : (
518
+ <Ionicons name={name} size={size} color={color} />
519
+ )}
520
+ </UIAnimatedElement>
521
+ );
522
+ });
523
+
524
+ /* -------------------------------------------------------------------------- */
525
+ /* Animated button */
526
+ /* -------------------------------------------------------------------------- */
527
+
528
+ const AnimatedButton = memo(function AnimatedButton({ item, onPress }) {
529
+ const {
530
+ text = "ORDER NOW",
531
+
532
+ x = 20,
533
+ y = 190,
534
+
535
+ width,
536
+ height = 44,
537
+
538
+ backgroundColor = "#FF5A1F",
539
+
540
+ borderRadius = 999,
541
+
542
+ textColor = "#FFFFFF",
543
+
544
+ fontSize = 14,
545
+ fontWeight = "700",
546
+
547
+ icon,
548
+
549
+ iconSize = 18,
550
+ iconColor,
551
+
552
+ animation = "scale",
553
+ delay = 400,
554
+ duration = 500,
555
+
556
+ style,
557
+ textStyle,
558
+ } = item;
559
+
560
+ return (
561
+ <UIAnimatedElement
562
+ animation={animation}
563
+ delay={delay}
564
+ duration={duration}
565
+ style={[
566
+ carouselStyles.absoluteElement,
567
+
568
+ {
569
+ left: x,
570
+ top: y,
571
+ },
572
+ ]}
573
+ >
574
+ <Pressable
575
+ onPress={onPress}
576
+ style={[
577
+ {
578
+ width,
579
+ height,
580
+
581
+ paddingHorizontal: 18,
582
+
583
+ borderRadius,
584
+
585
+ backgroundColor,
586
+
587
+ alignItems: "center",
588
+ justifyContent: "center",
589
+
590
+ flexDirection: "row",
591
+ },
592
+
593
+ style,
594
+ ]}
595
+ >
596
+ {icon ? (
597
+ <Ionicons
598
+ name={icon}
599
+ size={iconSize}
600
+ color={iconColor ?? textColor}
601
+ style={{
602
+ marginRight: 7,
603
+ }}
604
+ />
605
+ ) : null}
606
+
607
+ <Text
608
+ style={[
609
+ {
610
+ color: textColor,
611
+
612
+ fontSize,
613
+
614
+ fontWeight,
615
+ },
616
+
617
+ textStyle,
618
+ ]}
619
+ >
620
+ {text}
621
+ </Text>
622
+ </Pressable>
623
+ </UIAnimatedElement>
624
+ );
625
+ });
626
+
627
+ /* -------------------------------------------------------------------------- */
628
+ /* Advanced slide */
629
+ /* -------------------------------------------------------------------------- */
630
+
631
+ const AdvancedCarouselSlide = memo(function AdvancedCarouselSlide({
632
+ slide,
633
+ width,
634
+ height,
635
+
636
+ onPress,
637
+ onButtonPress,
638
+
639
+ theme,
640
+ }) {
641
+ const background = slide?.background ?? {};
642
+
643
+ const backgroundType = background.type ?? "color";
644
+
645
+ const backgroundColor = background.color ?? theme.colors.card ?? "#FFFFFF";
646
+
647
+ const backgroundSource = background.source;
648
+
649
+ return (
650
+ <View
651
+ style={{
652
+ width,
653
+ height,
654
+
655
+ overflow: "hidden",
656
+
657
+ position: "relative",
658
+
659
+ backgroundColor,
660
+ }}
661
+ >
662
+ {/* Background image */}
663
+
664
+ {backgroundType === "image" && backgroundSource ? (
665
+ <Image
666
+ source={
667
+ typeof backgroundSource === "string"
668
+ ? {
669
+ uri: backgroundSource,
670
+ }
671
+ : backgroundSource
672
+ }
673
+ resizeMode={background.resizeMode ?? "cover"}
674
+ style={[StyleSheet.absoluteFillObject, background.style]}
675
+ />
676
+ ) : null}
677
+
678
+ {/* Background gradient */}
679
+
680
+ {backgroundType === "gradient" ? (
681
+ <LinearGradient
682
+ colors={background.colors ?? ["#111111", "#333333"]}
683
+ start={
684
+ background.start ?? {
685
+ x: 0,
686
+ y: 0,
687
+ }
688
+ }
689
+ end={
690
+ background.end ?? {
691
+ x: 1,
692
+ y: 1,
693
+ }
694
+ }
695
+ style={StyleSheet.absoluteFillObject}
696
+ />
697
+ ) : null}
698
+
699
+ {/* Background overlay */}
700
+
701
+ {background.overlay ? (
702
+ <View
703
+ pointerEvents="none"
704
+ style={[
705
+ StyleSheet.absoluteFillObject,
706
+
707
+ {
708
+ backgroundColor: background.overlayColor ?? "#000000",
709
+
710
+ opacity: background.overlayOpacity ?? 0.25,
711
+ },
712
+ ]}
713
+ />
714
+ ) : null}
715
+
716
+ {/* Custom content */}
717
+
718
+ {typeof slide.render === "function" ? slide.render() : null}
719
+
720
+ {/* Text assets */}
721
+
722
+ {Array.isArray(slide.texts)
723
+ ? slide.texts.map((textItem, index) => (
724
+ <AnimatedText
725
+ key={textItem.id ?? `text-${index}`}
726
+ item={textItem}
727
+ />
728
+ ))
729
+ : null}
730
+
731
+ {/* Image assets */}
732
+
733
+ {Array.isArray(slide.images)
734
+ ? slide.images.map((imageItem, index) => (
735
+ <AnimatedImage
736
+ key={imageItem.id ?? `image-${index}`}
737
+ item={imageItem}
738
+ />
739
+ ))
740
+ : null}
741
+
742
+ {/* Icon assets */}
743
+
744
+ {Array.isArray(slide.icons)
745
+ ? slide.icons.map((iconItem, index) => (
746
+ <AnimatedIcon
747
+ key={iconItem.id ?? `icon-${index}`}
748
+ item={iconItem}
749
+ />
750
+ ))
751
+ : null}
752
+
753
+ {/* Button */}
754
+
755
+ {slide.button ? (
756
+ <AnimatedButton
757
+ item={slide.button}
758
+ onPress={() => onButtonPress?.(slide)}
759
+ />
760
+ ) : null}
761
+
762
+ {/* Entire slide press */}
763
+
764
+ {onPress ? (
765
+ <Pressable
766
+ onPress={() => onPress(slide)}
767
+ style={StyleSheet.absoluteFillObject}
768
+ pointerEvents="box-only"
769
+ />
770
+ ) : null}
771
+ </View>
772
+ );
773
+ });
774
+
775
+ /* -------------------------------------------------------------------------- */
776
+ /* Main UICarousel */
777
+ /* -------------------------------------------------------------------------- */
778
+
779
+ const UICarousel = forwardRef(function UICarousel(
780
+ {
781
+ data = [],
782
+
783
+ slides,
784
+
785
+ width,
786
+
787
+ height = 240,
788
+
789
+ slideWidth,
790
+
791
+ gap = 0,
792
+
793
+ horizontalPadding = 0,
794
+
795
+ borderRadius,
796
+
797
+ margin = 0,
798
+
799
+ marginHorizontal,
800
+ marginVertical,
801
+
802
+ marginTop,
803
+ marginBottom,
804
+ marginLeft,
805
+ marginRight,
806
+
807
+ style,
808
+
809
+ autoplay = false,
810
+
811
+ autoplayInterval = 3500,
812
+
813
+ loop = true,
814
+
815
+ pauseOnInteraction = true,
816
+
817
+ scrollEnabled = true,
818
+
819
+ showPagination = true,
820
+
821
+ dotSize = 8,
822
+
823
+ activeDotWidth,
824
+
825
+ activeDotHeight,
826
+
827
+ dotSpacing = 4,
828
+
829
+ activeDotColor,
830
+
831
+ inactiveDotColor,
832
+
833
+ paginationStyle,
834
+
835
+ onIndexChange,
836
+
837
+ onPress,
838
+
839
+ onButtonPress,
840
+
841
+ initialIndex = 0,
842
+
843
+ reanimated = true,
844
+
845
+ renderItem,
846
+
847
+ renderPagination,
848
+
849
+ ...flatListProps
850
+ },
851
+ ref,
852
+ ) {
853
+ const { theme } = useUITheme();
854
+
855
+ const colors = theme?.colors ?? {};
856
+
857
+ const radius = theme?.radius ?? {};
858
+
859
+ const resolvedData = Array.isArray(slides) ? slides : data;
860
+
861
+ const count = resolvedData.length;
862
+
863
+ const containerWidth = width ?? SCREEN_WIDTH;
864
+
865
+ const resolvedSlideWidth = slideWidth ?? containerWidth;
866
+
867
+ const resolvedRadius = borderRadius ?? radius.card ?? 18;
868
+
869
+ const resolvedGap = gap;
870
+
871
+ const [currentIndex, setCurrentIndex] = useState(
872
+ Math.min(initialIndex, Math.max(count - 1, 0)),
873
+ );
874
+
875
+ const flatListRef = useRef(null);
876
+
877
+ const timerRef = useRef(null);
878
+
879
+ const interactionRef = useRef(false);
880
+
881
+ const scrollToIndex = useCallback(
882
+ (index, animated = true) => {
883
+ if (!count) {
884
+ return;
885
+ }
886
+
887
+ let target = index;
888
+
889
+ if (loop) {
890
+ if (target >= count) {
891
+ target = 0;
892
+ }
893
+
894
+ if (target < 0) {
895
+ target = count - 1;
896
+ }
897
+ } else {
898
+ target = Math.max(0, Math.min(target, count - 1));
899
+ }
900
+
901
+ flatListRef.current?.scrollToOffset({
902
+ offset: target * (resolvedSlideWidth + resolvedGap),
903
+
904
+ animated,
905
+ });
906
+ },
907
+ [count, loop, resolvedSlideWidth, resolvedGap],
908
+ );
909
+
910
+ const next = useCallback(() => {
911
+ if (count <= 1) {
912
+ return;
913
+ }
914
+
915
+ if (currentIndex >= count - 1) {
916
+ if (loop) {
917
+ scrollToIndex(0);
918
+ }
919
+
920
+ return;
921
+ }
922
+
923
+ scrollToIndex(currentIndex + 1);
924
+ }, [count, currentIndex, loop, scrollToIndex]);
925
+
926
+ const previous = useCallback(() => {
927
+ if (count <= 1) {
928
+ return;
929
+ }
930
+
931
+ if (currentIndex <= 0) {
932
+ if (loop) {
933
+ scrollToIndex(count - 1);
934
+ }
935
+
936
+ return;
937
+ }
938
+
939
+ scrollToIndex(currentIndex - 1);
940
+ }, [count, currentIndex, loop, scrollToIndex]);
941
+
942
+ useImperativeHandle(
943
+ ref,
944
+ () => ({
945
+ next,
946
+ previous,
947
+ scrollToIndex,
948
+
949
+ getCurrentIndex: () => currentIndex,
950
+
951
+ getFlatListRef: () => flatListRef.current,
952
+ }),
953
+ [next, previous, scrollToIndex, currentIndex],
954
+ );
955
+
956
+ const stopAutoplay = useCallback(() => {
957
+ if (timerRef.current) {
958
+ clearInterval(timerRef.current);
959
+
960
+ timerRef.current = null;
961
+ }
962
+ }, []);
963
+
964
+ const startAutoplay = useCallback(() => {
965
+ stopAutoplay();
966
+
967
+ if (!autoplay || count <= 1) {
968
+ return;
969
+ }
970
+
971
+ timerRef.current = setInterval(() => {
972
+ if (interactionRef.current) {
973
+ return;
974
+ }
975
+
976
+ next();
977
+ }, autoplayInterval);
978
+ }, [autoplay, count, autoplayInterval, next, stopAutoplay]);
979
+
980
+ useEffect(() => {
981
+ startAutoplay();
982
+
983
+ return stopAutoplay;
984
+ }, [startAutoplay, stopAutoplay]);
985
+
986
+ const handleScrollEnd = useCallback(
987
+ (event) => {
988
+ const offset = event.nativeEvent.contentOffset.x;
989
+
990
+ const index = Math.round(offset / (resolvedSlideWidth + resolvedGap));
991
+
992
+ const safeIndex = Math.max(0, Math.min(index, count - 1));
993
+
994
+ if (safeIndex !== currentIndex) {
995
+ setCurrentIndex(safeIndex);
996
+
997
+ onIndexChange?.(safeIndex, resolvedData[safeIndex]);
998
+ }
999
+ },
1000
+ [
1001
+ resolvedSlideWidth,
1002
+ resolvedGap,
1003
+ count,
1004
+ currentIndex,
1005
+ onIndexChange,
1006
+ resolvedData,
1007
+ ],
1008
+ );
1009
+
1010
+ const handleBeginDrag = useCallback(
1011
+ (event) => {
1012
+ interactionRef.current = true;
1013
+
1014
+ if (pauseOnInteraction) {
1015
+ stopAutoplay();
1016
+ }
1017
+
1018
+ flatListProps.onScrollBeginDrag?.(event);
1019
+ },
1020
+ [pauseOnInteraction, stopAutoplay, flatListProps],
1021
+ );
1022
+
1023
+ const handleEndDrag = useCallback(
1024
+ (event) => {
1025
+ interactionRef.current = false;
1026
+
1027
+ if (pauseOnInteraction) {
1028
+ startAutoplay();
1029
+ }
1030
+
1031
+ flatListProps.onScrollEndDrag?.(event);
1032
+ },
1033
+ [pauseOnInteraction, startAutoplay, flatListProps],
1034
+ );
1035
+
1036
+ const renderCarouselItem = useCallback(
1037
+ ({ item, index }) => {
1038
+ if (typeof renderItem === "function") {
1039
+ return (
1040
+ <View
1041
+ style={{
1042
+ width: resolvedSlideWidth,
1043
+
1044
+ marginRight: resolvedGap,
1045
+ }}
1046
+ >
1047
+ {renderItem({
1048
+ item,
1049
+ index,
1050
+ })}
1051
+ </View>
1052
+ );
1053
+ }
1054
+
1055
+ return (
1056
+ <View
1057
+ style={{
1058
+ width: resolvedSlideWidth,
1059
+
1060
+ marginRight: resolvedGap,
1061
+
1062
+ overflow: "hidden",
1063
+
1064
+ borderRadius: resolvedRadius,
1065
+ }}
1066
+ >
1067
+ <AdvancedCarouselSlide
1068
+ slide={item}
1069
+ width={resolvedSlideWidth}
1070
+ height={height}
1071
+ onPress={onPress ? () => onPress(item, index) : undefined}
1072
+ onButtonPress={
1073
+ onButtonPress ? () => onButtonPress(item, index) : undefined
1074
+ }
1075
+ theme={theme}
1076
+ />
1077
+ </View>
1078
+ );
1079
+ },
1080
+ [
1081
+ renderItem,
1082
+ resolvedSlideWidth,
1083
+ resolvedGap,
1084
+ resolvedRadius,
1085
+ height,
1086
+ onPress,
1087
+ onButtonPress,
1088
+ theme,
1089
+ ],
1090
+ );
1091
+
1092
+ const renderDots = () => {
1093
+ if (!showPagination || count <= 1) {
1094
+ return null;
1095
+ }
1096
+
1097
+ if (typeof renderPagination === "function") {
1098
+ return renderPagination({
1099
+ currentIndex,
1100
+ count,
1101
+ scrollToIndex,
1102
+ });
1103
+ }
1104
+
1105
+ return (
1106
+ <View style={[carouselStyles.pagination, paginationStyle]}>
1107
+ {resolvedData.map((_item, index) => {
1108
+ const active = index === currentIndex;
1109
+
1110
+ return (
1111
+ <Pressable
1112
+ key={`dot-${index}`}
1113
+ hitSlop={8}
1114
+ onPress={() => scrollToIndex(index)}
1115
+ style={{
1116
+ width: active ? (activeDotWidth ?? dotSize * 2) : dotSize,
1117
+
1118
+ height: activeDotHeight ?? dotSize,
1119
+
1120
+ borderRadius: 999,
1121
+
1122
+ marginHorizontal: dotSpacing / 2,
1123
+
1124
+ backgroundColor: active
1125
+ ? (activeDotColor ?? colors.primary ?? "#FF5A1F")
1126
+ : (inactiveDotColor ?? "#D1D5DB"),
1127
+ }}
1128
+ />
1129
+ );
1130
+ })}
1131
+ </View>
1132
+ );
1133
+ };
1134
+
1135
+ if (!count) {
1136
+ return null;
1137
+ }
1138
+
1139
+ return (
1140
+ <View
1141
+ style={[
1142
+ carouselStyles.container,
1143
+
1144
+ {
1145
+ width: containerWidth,
1146
+
1147
+ marginTop: marginTop ?? marginVertical ?? margin,
1148
+
1149
+ marginBottom: marginBottom ?? marginVertical ?? margin,
1150
+
1151
+ marginLeft: marginLeft ?? marginHorizontal ?? margin,
1152
+
1153
+ marginRight: marginRight ?? marginHorizontal ?? margin,
1154
+
1155
+ paddingHorizontal: horizontalPadding,
1156
+ },
1157
+
1158
+ style,
1159
+ ]}
1160
+ >
1161
+ <FlatList
1162
+ ref={flatListRef}
1163
+ data={resolvedData}
1164
+ horizontal
1165
+ showsHorizontalScrollIndicator={false}
1166
+ scrollEnabled={scrollEnabled}
1167
+ bounces={false}
1168
+ decelerationRate="fast"
1169
+ snapToInterval={resolvedSlideWidth + resolvedGap}
1170
+ snapToAlignment="start"
1171
+ disableIntervalMomentum
1172
+ nestedScrollEnabled
1173
+ keyExtractor={(item, index) => String(item?.id ?? index)}
1174
+ renderItem={renderCarouselItem}
1175
+ onMomentumScrollEnd={handleScrollEnd}
1176
+ onScrollBeginDrag={handleBeginDrag}
1177
+ onScrollEndDrag={handleEndDrag}
1178
+ scrollEventThrottle={16}
1179
+ initialScrollIndex={initialIndex}
1180
+ getItemLayout={(_data, index) => ({
1181
+ length: resolvedSlideWidth + resolvedGap,
1182
+
1183
+ offset: (resolvedSlideWidth + resolvedGap) * index,
1184
+
1185
+ index,
1186
+ })}
1187
+ {...flatListProps}
1188
+ />
1189
+
1190
+ {renderDots()}
1191
+ </View>
1192
+ );
1193
+ });
1194
+
1195
+ /* -------------------------------------------------------------------------- */
1196
+ /* Styles */
1197
+ /* -------------------------------------------------------------------------- */
1198
+
1199
+ const carouselStyles = StyleSheet.create({
1200
+ container: {
1201
+ position: "relative",
1202
+ },
1203
+
1204
+ absoluteElement: {
1205
+ position: "absolute",
1206
+
1207
+ zIndex: 5,
1208
+ },
1209
+
1210
+ pagination: {
1211
+ position: "absolute",
1212
+
1213
+ left: 0,
1214
+ right: 0,
1215
+
1216
+ bottom: 12,
1217
+
1218
+ flexDirection: "row",
1219
+
1220
+ justifyContent: "center",
1221
+
1222
+ alignItems: "center",
1223
+
1224
+ zIndex: 100,
1225
+ },
1226
+ });
1227
+
1228
+ /* -------------------------------------------------------------------------- */
1229
+ /* Exports */
1230
+ /* -------------------------------------------------------------------------- */
1231
+
1232
+ export {
1233
+ UICarousel,
1234
+ UIAnimatedElement,
1235
+ AnimatedText,
1236
+ AnimatedImage,
1237
+ AnimatedIcon,
1238
+ };
1239
+
1240
+ export default memo(UICarousel);
@@ -0,0 +1,12 @@
1
+ export {
2
+ default,
3
+ UICarousel,
4
+ UIAnimatedElement,
5
+ AnimatedText,
6
+ AnimatedImage,
7
+ AnimatedIcon,
8
+ CAROUSEL_ANIMATIONS as UICarouselAnimations,
9
+ CAROUSEL_TEXT_ANIMATIONS as UICarouselTextAnimations,
10
+ CAROUSEL_ASSET_ANIMATIONS as UICarouselAssetAnimations,
11
+ CAROUSEL_BACKGROUND_ANIMATIONS as UICarouselBackgroundAnimations,
12
+ } from "./Carousel";
@@ -178,3 +178,15 @@ export {
178
178
  UIModalTitlePositions,
179
179
  UIModalAnimations,
180
180
  } from "./UIModal";
181
+ export {
182
+ default,
183
+ UICarousel,
184
+ UIAnimatedElement,
185
+ AnimatedText,
186
+ AnimatedImage,
187
+ AnimatedIcon,
188
+ CAROUSEL_ANIMATIONS as UICarouselAnimations,
189
+ CAROUSEL_TEXT_ANIMATIONS as UICarouselTextAnimations,
190
+ CAROUSEL_ASSET_ANIMATIONS as UICarouselAssetAnimations,
191
+ CAROUSEL_BACKGROUND_ANIMATIONS as UICarouselBackgroundAnimations,
192
+ } from "./Carousel";
package/src/index.js CHANGED
@@ -161,4 +161,5 @@ export {
161
161
  BottomSheetBackdrop,
162
162
  UIBottomSheetIcon,
163
163
  UIModal,
164
+ UICarousel,
164
165
  } from "./components";