react-native-ultra-carousel 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.
Files changed (52) hide show
  1. package/package.json +81 -0
  2. package/src/animations/basic/fade.ts +51 -0
  3. package/src/animations/basic/overlap.ts +69 -0
  4. package/src/animations/basic/parallax.ts +65 -0
  5. package/src/animations/basic/peek.ts +79 -0
  6. package/src/animations/basic/scale.ts +63 -0
  7. package/src/animations/basic/scaleFade.ts +73 -0
  8. package/src/animations/basic/slide.ts +53 -0
  9. package/src/animations/basic/slideFade.ts +60 -0
  10. package/src/animations/basic/vertical.ts +50 -0
  11. package/src/animations/basic/verticalFade.ts +60 -0
  12. package/src/animations/index.ts +45 -0
  13. package/src/animations/registry.ts +175 -0
  14. package/src/animations/types.ts +11 -0
  15. package/src/animations/utils.ts +75 -0
  16. package/src/components/AutoPlayController.tsx +38 -0
  17. package/src/components/Carousel.tsx +371 -0
  18. package/src/components/CarouselItem.tsx +98 -0
  19. package/src/components/Pagination/BarPagination.tsx +141 -0
  20. package/src/components/Pagination/CustomPagination.tsx +48 -0
  21. package/src/components/Pagination/DotPagination.tsx +137 -0
  22. package/src/components/Pagination/NumberPagination.tsx +117 -0
  23. package/src/components/Pagination/Pagination.tsx +82 -0
  24. package/src/components/Pagination/ProgressPagination.tsx +70 -0
  25. package/src/components/Pagination/index.ts +11 -0
  26. package/src/components/ParallaxImage.tsx +89 -0
  27. package/src/gestures/FlingGestureManager.ts +49 -0
  28. package/src/gestures/PanGestureManager.ts +202 -0
  29. package/src/gestures/ScrollViewCompat.ts +28 -0
  30. package/src/gestures/types.ts +6 -0
  31. package/src/hooks/useAnimationProgress.ts +33 -0
  32. package/src/hooks/useAutoPlay.ts +115 -0
  33. package/src/hooks/useCarousel.ts +118 -0
  34. package/src/hooks/useCarouselGesture.ts +109 -0
  35. package/src/hooks/useItemAnimation.ts +44 -0
  36. package/src/hooks/usePagination.ts +39 -0
  37. package/src/hooks/useSnapPoints.ts +31 -0
  38. package/src/hooks/useVirtualization.ts +63 -0
  39. package/src/index.ts +71 -0
  40. package/src/plugins/PluginManager.ts +150 -0
  41. package/src/plugins/types.ts +6 -0
  42. package/src/types/animation.ts +72 -0
  43. package/src/types/carousel.ts +188 -0
  44. package/src/types/gesture.ts +42 -0
  45. package/src/types/index.ts +41 -0
  46. package/src/types/pagination.ts +65 -0
  47. package/src/types/plugin.ts +27 -0
  48. package/src/utils/accessibility.ts +71 -0
  49. package/src/utils/constants.ts +45 -0
  50. package/src/utils/layout.ts +115 -0
  51. package/src/utils/math.ts +78 -0
  52. package/src/utils/platform.ts +33 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @file DotPagination component
3
+ * @description Animated dot indicators for carousel pagination
4
+ */
5
+
6
+ import React, { memo } from 'react';
7
+ import { StyleSheet, Pressable, View } from 'react-native';
8
+ import Animated, {
9
+ useAnimatedStyle,
10
+ interpolate,
11
+ Extrapolation,
12
+ type SharedValue,
13
+ } from 'react-native-reanimated';
14
+ import type { BasePaginationProps } from '../../types';
15
+
16
+ /**
17
+ * Dot-based pagination indicator.
18
+ * Active dot scales up and changes color smoothly based on scroll progress.
19
+ */
20
+ const DotPaginationComponent: React.FC<BasePaginationProps> = ({
21
+ totalItems,
22
+ progress,
23
+ activeColor,
24
+ inactiveColor,
25
+ size,
26
+ gap,
27
+ goToIndex,
28
+ style,
29
+ }) => {
30
+ return (
31
+ <View style={[styles.container, style]} accessibilityRole="tablist">
32
+ {Array.from({ length: totalItems }).map((_, index) => (
33
+ <DotIndicator
34
+ key={index}
35
+ index={index}
36
+ progress={progress}
37
+ activeColor={activeColor}
38
+ inactiveColor={inactiveColor}
39
+ size={size}
40
+ gap={gap}
41
+ goToIndex={goToIndex}
42
+ />
43
+ ))}
44
+ </View>
45
+ );
46
+ };
47
+
48
+ DotPaginationComponent.displayName = 'DotPagination';
49
+
50
+ /** Props for an individual dot */
51
+ interface DotIndicatorProps {
52
+ index: number;
53
+ progress: SharedValue<number>;
54
+ activeColor: string;
55
+ inactiveColor: string;
56
+ size: number;
57
+ gap: number;
58
+ goToIndex: (index: number) => void;
59
+ }
60
+
61
+ /** Individual animated dot indicator */
62
+ const DotIndicator: React.FC<DotIndicatorProps> = memo(({
63
+ index,
64
+ progress,
65
+ activeColor,
66
+ inactiveColor,
67
+ size,
68
+ gap,
69
+ goToIndex,
70
+ }) => {
71
+ const ACTIVE_SCALE = 1.4;
72
+
73
+ const animatedStyle = useAnimatedStyle(() => {
74
+ const distance = Math.abs(progress.value - index);
75
+
76
+ const scale = interpolate(
77
+ distance,
78
+ [0, 1],
79
+ [ACTIVE_SCALE, 1],
80
+ Extrapolation.CLAMP
81
+ );
82
+
83
+ const opacity = interpolate(
84
+ distance,
85
+ [0, 1],
86
+ [1, 0.5],
87
+ Extrapolation.CLAMP
88
+ );
89
+
90
+ return {
91
+ transform: [{ scale }],
92
+ opacity,
93
+ };
94
+ });
95
+
96
+ const isActive = Math.round(progress.value) === index;
97
+
98
+ return (
99
+ <Pressable
100
+ onPress={() => goToIndex(index)}
101
+ accessibilityRole="tab"
102
+ accessibilityLabel={`Page ${index + 1}`}
103
+ accessibilityState={{ selected: isActive }}
104
+ hitSlop={8}
105
+ >
106
+ <Animated.View
107
+ style={[
108
+ styles.dot,
109
+ {
110
+ width: size,
111
+ height: size,
112
+ borderRadius: size / 2,
113
+ marginHorizontal: gap / 2,
114
+ backgroundColor: isActive ? activeColor : inactiveColor,
115
+ },
116
+ animatedStyle,
117
+ ]}
118
+ />
119
+ </Pressable>
120
+ );
121
+ });
122
+
123
+ DotIndicator.displayName = 'DotIndicator';
124
+
125
+ const styles = StyleSheet.create({
126
+ container: {
127
+ flexDirection: 'row',
128
+ alignItems: 'center',
129
+ justifyContent: 'center',
130
+ paddingVertical: 12,
131
+ },
132
+ dot: {
133
+ // dynamic styles applied inline
134
+ },
135
+ });
136
+
137
+ export const DotPagination = memo(DotPaginationComponent);
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @file NumberPagination component
3
+ * @description Numeric page indicator in "X / Y" format
4
+ */
5
+
6
+ import React, { memo } from 'react';
7
+ import { StyleSheet, View, Text } from 'react-native';
8
+ import Animated, {
9
+ useAnimatedStyle,
10
+ interpolate,
11
+ Extrapolation,
12
+ type SharedValue,
13
+ } from 'react-native-reanimated';
14
+ import type { BasePaginationProps } from '../../types';
15
+
16
+ /**
17
+ * Number-based pagination indicator.
18
+ * Displays current page and total in "X / Y" format with animated transitions.
19
+ */
20
+ const NumberPaginationComponent: React.FC<BasePaginationProps> = ({
21
+ totalItems,
22
+ progress,
23
+ activeColor,
24
+ size,
25
+ style,
26
+ }) => {
27
+ const fontSize = size * 2;
28
+
29
+ return (
30
+ <View
31
+ style={[styles.container, style]}
32
+ accessibilityRole="text"
33
+ accessibilityLabel={`Page ${Math.round(progress.value) + 1} of ${totalItems}`}
34
+ >
35
+ <NumberDisplay
36
+ progress={progress}
37
+ totalItems={totalItems}
38
+ color={activeColor}
39
+ fontSize={fontSize}
40
+ />
41
+ </View>
42
+ );
43
+ };
44
+
45
+ NumberPaginationComponent.displayName = 'NumberPagination';
46
+
47
+ /** Props for the number display */
48
+ interface NumberDisplayProps {
49
+ progress: SharedValue<number>;
50
+ totalItems: number;
51
+ color: string;
52
+ fontSize: number;
53
+ }
54
+
55
+ /** Animated number display showing current/total */
56
+ const NumberDisplay: React.FC<NumberDisplayProps> = memo(({
57
+ progress,
58
+ totalItems,
59
+ color,
60
+ fontSize,
61
+ }) => {
62
+ const animatedStyle = useAnimatedStyle(() => {
63
+ const opacity = interpolate(
64
+ Math.abs(progress.value - Math.round(progress.value)),
65
+ [0, 0.5],
66
+ [1, 0.5],
67
+ Extrapolation.CLAMP
68
+ );
69
+
70
+ return { opacity };
71
+ });
72
+
73
+ const currentPage = Math.round(progress.value) + 1;
74
+
75
+ return (
76
+ <Animated.View style={[styles.numberContainer, animatedStyle]}>
77
+ <Text style={[styles.currentNumber, { color, fontSize }]}>
78
+ {currentPage}
79
+ </Text>
80
+ <Text style={[styles.separator, { color, fontSize: fontSize * 0.8 }]}>
81
+ {' / '}
82
+ </Text>
83
+ <Text style={[styles.totalNumber, { color, fontSize: fontSize * 0.8 }]}>
84
+ {totalItems}
85
+ </Text>
86
+ </Animated.View>
87
+ );
88
+ });
89
+
90
+ NumberDisplay.displayName = 'NumberDisplay';
91
+
92
+ const styles = StyleSheet.create({
93
+ container: {
94
+ alignItems: 'center',
95
+ justifyContent: 'center',
96
+ paddingVertical: 12,
97
+ },
98
+ numberContainer: {
99
+ flexDirection: 'row',
100
+ alignItems: 'baseline',
101
+ },
102
+ currentNumber: {
103
+ fontWeight: '700',
104
+ fontVariant: ['tabular-nums'],
105
+ },
106
+ separator: {
107
+ fontWeight: '400',
108
+ opacity: 0.6,
109
+ },
110
+ totalNumber: {
111
+ fontWeight: '400',
112
+ opacity: 0.6,
113
+ fontVariant: ['tabular-nums'],
114
+ },
115
+ });
116
+
117
+ export const NumberPagination = memo(NumberPaginationComponent);
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @file Pagination component
3
+ * @description Main pagination component that dispatches to the correct pagination style
4
+ */
5
+
6
+ import React, { memo } from 'react';
7
+ import type { SharedValue } from 'react-native-reanimated';
8
+ import type { PaginationConfig } from '../../types';
9
+ import { DotPagination } from './DotPagination';
10
+ import { BarPagination } from './BarPagination';
11
+ import { NumberPagination } from './NumberPagination';
12
+ import { ProgressPagination } from './ProgressPagination';
13
+ import { CustomPagination } from './CustomPagination';
14
+ import {
15
+ DEFAULT_PAGINATION_ACTIVE_COLOR,
16
+ DEFAULT_PAGINATION_INACTIVE_COLOR,
17
+ DEFAULT_PAGINATION_SIZE,
18
+ DEFAULT_PAGINATION_GAP,
19
+ } from '../../utils/constants';
20
+
21
+ /** Props for the main Pagination component */
22
+ export interface PaginationProps {
23
+ /** Pagination configuration */
24
+ config: PaginationConfig;
25
+ /** Total number of items */
26
+ totalItems: number;
27
+ /** Current page progress shared value */
28
+ progress: SharedValue<number>;
29
+ /** Navigate to specific index */
30
+ goToIndex: (index: number) => void;
31
+ }
32
+
33
+ /**
34
+ * Main pagination component.
35
+ * Renders the appropriate pagination style based on configuration.
36
+ */
37
+ const PaginationComponent: React.FC<PaginationProps> = ({
38
+ config,
39
+ totalItems,
40
+ progress,
41
+ goToIndex,
42
+ }) => {
43
+ const baseProps = {
44
+ totalItems,
45
+ progress,
46
+ activeColor: config.activeColor ?? DEFAULT_PAGINATION_ACTIVE_COLOR,
47
+ inactiveColor: config.inactiveColor ?? DEFAULT_PAGINATION_INACTIVE_COLOR,
48
+ size: config.size ?? DEFAULT_PAGINATION_SIZE,
49
+ gap: config.gap ?? DEFAULT_PAGINATION_GAP,
50
+ goToIndex,
51
+ style: config.style,
52
+ };
53
+
54
+ switch (config.type) {
55
+ case 'dot':
56
+ return <DotPagination {...baseProps} />;
57
+ case 'bar':
58
+ return <BarPagination {...baseProps} />;
59
+ case 'number':
60
+ return <NumberPagination {...baseProps} />;
61
+ case 'progress':
62
+ return <ProgressPagination {...baseProps} />;
63
+ case 'custom':
64
+ if (config.renderCustom) {
65
+ return (
66
+ <CustomPagination
67
+ totalItems={totalItems}
68
+ progress={progress}
69
+ goToIndex={goToIndex}
70
+ renderCustom={config.renderCustom}
71
+ />
72
+ );
73
+ }
74
+ return null;
75
+ default:
76
+ return <DotPagination {...baseProps} />;
77
+ }
78
+ };
79
+
80
+ PaginationComponent.displayName = 'Pagination';
81
+
82
+ export const Pagination = memo(PaginationComponent);
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @file ProgressPagination component
3
+ * @description Progress bar pagination indicator
4
+ */
5
+
6
+ import React, { memo } from 'react';
7
+ import { StyleSheet, View } from 'react-native';
8
+ import Animated, {
9
+ useAnimatedStyle,
10
+ type SharedValue,
11
+ } from 'react-native-reanimated';
12
+ import type { BasePaginationProps } from '../../types';
13
+
14
+ /** Progress bar height */
15
+ const PROGRESS_HEIGHT = 3;
16
+
17
+ /**
18
+ * Progress bar pagination indicator.
19
+ * Shows overall carousel progress as a horizontal bar.
20
+ */
21
+ const ProgressPaginationComponent: React.FC<BasePaginationProps> = ({
22
+ totalItems,
23
+ progress,
24
+ activeColor,
25
+ inactiveColor,
26
+ style,
27
+ }) => {
28
+ const animatedStyle = useAnimatedStyle(() => {
29
+ const fraction = totalItems > 1
30
+ ? (progress.value / (totalItems - 1)) * 100
31
+ : 0;
32
+ return {
33
+ width: `${Math.max(0, Math.min(100, fraction))}%`,
34
+ };
35
+ });
36
+
37
+ return (
38
+ <View style={[styles.container, style]}>
39
+ <View style={[styles.track, { backgroundColor: inactiveColor }]}>
40
+ <Animated.View
41
+ style={[
42
+ styles.fill,
43
+ { backgroundColor: activeColor },
44
+ animatedStyle,
45
+ ]}
46
+ />
47
+ </View>
48
+ </View>
49
+ );
50
+ };
51
+
52
+ ProgressPaginationComponent.displayName = 'ProgressPagination';
53
+
54
+ const styles = StyleSheet.create({
55
+ container: {
56
+ paddingVertical: 12,
57
+ paddingHorizontal: 16,
58
+ },
59
+ track: {
60
+ height: PROGRESS_HEIGHT,
61
+ borderRadius: PROGRESS_HEIGHT / 2,
62
+ overflow: 'hidden',
63
+ },
64
+ fill: {
65
+ height: '100%',
66
+ borderRadius: PROGRESS_HEIGHT / 2,
67
+ },
68
+ });
69
+
70
+ export const ProgressPagination = memo(ProgressPaginationComponent);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @file Pagination module exports
3
+ * @description Re-exports pagination components
4
+ */
5
+
6
+ export { Pagination } from './Pagination';
7
+ export { DotPagination } from './DotPagination';
8
+ export { BarPagination } from './BarPagination';
9
+ export { NumberPagination } from './NumberPagination';
10
+ export { ProgressPagination } from './ProgressPagination';
11
+ export { CustomPagination } from './CustomPagination';
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @file ParallaxImage component
3
+ * @description Image component with built-in parallax scrolling effect
4
+ */
5
+
6
+ import React, { memo } from 'react';
7
+ import { StyleSheet, type ImageSourcePropType, type ViewStyle, type StyleProp } from 'react-native';
8
+ import Animated, {
9
+ useAnimatedStyle,
10
+ interpolate,
11
+ Extrapolation,
12
+ type SharedValue,
13
+ } from 'react-native-reanimated';
14
+
15
+ /** Props for the ParallaxImage component */
16
+ export interface ParallaxImageProps {
17
+ /** Image source */
18
+ source: ImageSourcePropType;
19
+ /** Container width */
20
+ width: number;
21
+ /** Container height */
22
+ height: number;
23
+ /** Animated scroll progress for this item */
24
+ progress: SharedValue<number>;
25
+ /** Parallax movement factor (default: 0.3) */
26
+ parallaxFactor?: number;
27
+ /** Custom container style */
28
+ style?: StyleProp<ViewStyle>;
29
+ }
30
+
31
+ /**
32
+ * Parallax-aware image component for carousel items.
33
+ * The image moves at a different speed than the container
34
+ * to create a depth parallax effect.
35
+ */
36
+ const ParallaxImageComponent: React.FC<ParallaxImageProps> = ({
37
+ source,
38
+ width,
39
+ height,
40
+ progress,
41
+ parallaxFactor = 0.3,
42
+ style,
43
+ }) => {
44
+ const PARALLAX_OFFSET = width * parallaxFactor;
45
+
46
+ const animatedImageStyle = useAnimatedStyle(() => {
47
+ const translateX = interpolate(
48
+ progress.value,
49
+ [-1, 0, 1],
50
+ [-PARALLAX_OFFSET, 0, PARALLAX_OFFSET],
51
+ Extrapolation.CLAMP
52
+ );
53
+
54
+ return {
55
+ transform: [{ translateX }],
56
+ };
57
+ });
58
+
59
+ return (
60
+ <Animated.View style={[styles.container, { width, height }, style]}>
61
+ <Animated.Image
62
+ source={source}
63
+ style={[
64
+ styles.image,
65
+ {
66
+ width: width + PARALLAX_OFFSET * 2,
67
+ height,
68
+ left: -PARALLAX_OFFSET,
69
+ },
70
+ animatedImageStyle,
71
+ ]}
72
+ resizeMode="cover"
73
+ />
74
+ </Animated.View>
75
+ );
76
+ };
77
+
78
+ ParallaxImageComponent.displayName = 'ParallaxImage';
79
+
80
+ const styles = StyleSheet.create({
81
+ container: {
82
+ overflow: 'hidden',
83
+ },
84
+ image: {
85
+ position: 'absolute',
86
+ },
87
+ });
88
+
89
+ export const ParallaxImage = memo(ParallaxImageComponent);
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @file Fling gesture manager
3
+ * @description Handles fling gesture for quick navigation between carousel items
4
+ */
5
+
6
+ import { Gesture } from 'react-native-gesture-handler';
7
+ import { useMemo } from 'react';
8
+
9
+ /** Options for fling gesture configuration */
10
+ export interface FlingGestureOptions {
11
+ /** Whether the carousel scrolls horizontally */
12
+ isHorizontal: boolean;
13
+ /** Whether fling gesture is enabled */
14
+ enabled: boolean;
15
+ /** Callback when fling is detected in forward direction */
16
+ onFlingForward: () => void;
17
+ /** Callback when fling is detected in backward direction */
18
+ onFlingBackward: () => void;
19
+ }
20
+
21
+ /**
22
+ * Creates fling gestures for quick carousel navigation.
23
+ *
24
+ * @param options - Fling gesture configuration
25
+ * @returns Composed fling gesture
26
+ */
27
+ export const useFlingGesture = (options: FlingGestureOptions) => {
28
+ const { isHorizontal, enabled, onFlingForward, onFlingBackward } = options;
29
+
30
+ const flingForward = useMemo(() => {
31
+ return Gesture.Fling()
32
+ .direction(isHorizontal ? 1 : 8)
33
+ .enabled(enabled)
34
+ .onEnd(() => {
35
+ onFlingBackward();
36
+ });
37
+ }, [isHorizontal, enabled, onFlingBackward]);
38
+
39
+ const flingBackward = useMemo(() => {
40
+ return Gesture.Fling()
41
+ .direction(isHorizontal ? 2 : 4)
42
+ .enabled(enabled)
43
+ .onEnd(() => {
44
+ onFlingForward();
45
+ });
46
+ }, [isHorizontal, enabled, onFlingForward]);
47
+
48
+ return { flingForward, flingBackward };
49
+ };