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,65 @@
1
+ /**
2
+ * @file Pagination type definitions
3
+ * @description Types for pagination indicators and controls
4
+ */
5
+
6
+ import type { ViewStyle, StyleProp } from 'react-native';
7
+ import type { SharedValue } from 'react-native-reanimated';
8
+
9
+ /** Pagination display type */
10
+ export type PaginationType = 'dot' | 'bar' | 'number' | 'progress' | 'custom';
11
+
12
+ /** Pagination position relative to the carousel */
13
+ export type PaginationPosition = 'top' | 'bottom' | 'left' | 'right';
14
+
15
+ /** Information passed to custom pagination renderer */
16
+ export interface PaginationRenderInfo {
17
+ /** Current active index */
18
+ currentIndex: number;
19
+ /** Total number of items */
20
+ totalItems: number;
21
+ /** Animated scroll progress */
22
+ progress: SharedValue<number>;
23
+ /** Navigate to specific index */
24
+ goToIndex: (index: number) => void;
25
+ }
26
+
27
+ /** Pagination configuration */
28
+ export interface PaginationConfig {
29
+ /** Type of pagination indicator */
30
+ type: PaginationType;
31
+ /** Position relative to carousel */
32
+ position?: PaginationPosition;
33
+ /** Color of active indicator */
34
+ activeColor?: string;
35
+ /** Color of inactive indicators */
36
+ inactiveColor?: string;
37
+ /** Size of indicator elements */
38
+ size?: number;
39
+ /** Gap between indicators */
40
+ gap?: number;
41
+ /** Custom style for pagination container */
42
+ style?: StyleProp<ViewStyle>;
43
+ /** Custom render function for pagination */
44
+ renderCustom?: (info: PaginationRenderInfo) => React.ReactNode;
45
+ }
46
+
47
+ /** Props shared across all pagination components */
48
+ export interface BasePaginationProps {
49
+ /** Total number of items */
50
+ totalItems: number;
51
+ /** Animated scroll progress value */
52
+ progress: SharedValue<number>;
53
+ /** Active indicator color */
54
+ activeColor: string;
55
+ /** Inactive indicator color */
56
+ inactiveColor: string;
57
+ /** Indicator size */
58
+ size: number;
59
+ /** Gap between indicators */
60
+ gap: number;
61
+ /** Navigate to specific index */
62
+ goToIndex: (index: number) => void;
63
+ /** Container style */
64
+ style?: StyleProp<ViewStyle>;
65
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @file Plugin type definitions
3
+ * @description Types for the carousel plugin system
4
+ */
5
+
6
+ import type { AnimatedItemStyle } from './animation';
7
+ import type { CarouselRef } from './carousel';
8
+
9
+ /** Plugin interface for extending carousel behavior */
10
+ export interface CarouselPlugin {
11
+ /** Unique plugin name */
12
+ name: string;
13
+ /** Called when carousel initializes */
14
+ onInit?: (carousel: CarouselRef) => void;
15
+ /** Called on each animation frame (MUST be a worklet) */
16
+ onAnimate?: (progress: number, index: number) => AnimatedItemStyle | void;
17
+ /** Called when active index changes */
18
+ onIndexChange?: (index: number) => void;
19
+ /** Called when carousel is destroyed */
20
+ onDestroy?: () => void;
21
+ }
22
+
23
+ /** Options for creating a plugin */
24
+ export type CreatePluginOptions = Omit<CarouselPlugin, 'name'> & {
25
+ /** Unique plugin name */
26
+ name: string;
27
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @file Accessibility utility functions
3
+ * @description Helpers for building accessible carousel experiences
4
+ */
5
+
6
+ import type { AccessibilityProps } from 'react-native';
7
+
8
+ /**
9
+ * Generates accessibility props for the carousel container.
10
+ *
11
+ * @param label - Custom accessibility label
12
+ * @param currentIndex - Current active index
13
+ * @param totalItems - Total number of items
14
+ * @returns Accessibility props object
15
+ */
16
+ export const getCarouselAccessibilityProps = (
17
+ label: string | undefined,
18
+ currentIndex: number,
19
+ totalItems: number
20
+ ): AccessibilityProps => ({
21
+ accessible: true,
22
+ accessibilityRole: 'adjustable',
23
+ accessibilityLabel: label ?? 'Carousel',
24
+ accessibilityValue: {
25
+ text: `Item ${currentIndex + 1} of ${totalItems}`,
26
+ min: 0,
27
+ max: totalItems - 1,
28
+ now: currentIndex,
29
+ },
30
+ accessibilityActions: [
31
+ { name: 'increment', label: 'Next item' },
32
+ { name: 'decrement', label: 'Previous item' },
33
+ ],
34
+ });
35
+
36
+ /**
37
+ * Generates accessibility props for a carousel item.
38
+ *
39
+ * @param index - Item index
40
+ * @param totalItems - Total number of items
41
+ * @param isActive - Whether this item is currently active
42
+ * @returns Accessibility props for the item
43
+ */
44
+ export const getItemAccessibilityProps = (
45
+ index: number,
46
+ totalItems: number,
47
+ isActive: boolean
48
+ ): AccessibilityProps => ({
49
+ accessible: true,
50
+ accessibilityRole: 'button',
51
+ accessibilityLabel: `Item ${index + 1} of ${totalItems}`,
52
+ accessibilityState: {
53
+ selected: isActive,
54
+ },
55
+ });
56
+
57
+ /**
58
+ * Generates accessibility props for pagination.
59
+ *
60
+ * @param currentIndex - Current active page
61
+ * @param totalItems - Total number of pages
62
+ * @returns Accessibility props for pagination container
63
+ */
64
+ export const getPaginationAccessibilityProps = (
65
+ currentIndex: number,
66
+ totalItems: number
67
+ ): AccessibilityProps => ({
68
+ accessible: true,
69
+ accessibilityRole: 'tablist' as AccessibilityProps['accessibilityRole'],
70
+ accessibilityLabel: `Page ${currentIndex + 1} of ${totalItems}`,
71
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @file Default constants and configuration values
3
+ * @description Central location for all magic numbers and default settings
4
+ */
5
+
6
+ import { Dimensions } from 'react-native';
7
+
8
+ const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
9
+
10
+ /** Default carousel dimensions */
11
+ export const DEFAULT_WIDTH = SCREEN_WIDTH;
12
+ export const DEFAULT_HEIGHT = 250;
13
+
14
+ /** Default animation spring config */
15
+ export const DEFAULT_SPRING_CONFIG = {
16
+ damping: 20,
17
+ stiffness: 200,
18
+ mass: 1,
19
+ overshootClamping: false,
20
+ restDisplacementThreshold: 0.01,
21
+ restSpeedThreshold: 0.01,
22
+ } as const;
23
+
24
+ /** Default gesture thresholds */
25
+ export const DEFAULT_ACTIVE_OFFSET_X: [number, number] = [-10, 10];
26
+ export const DEFAULT_ACTIVE_OFFSET_Y: [number, number] = [-50, 50];
27
+ export const DEFAULT_VELOCITY_THRESHOLD = 500;
28
+
29
+ /** Default auto play settings */
30
+ export const DEFAULT_AUTO_PLAY_INTERVAL = 3000;
31
+
32
+ /** Default pagination settings */
33
+ export const DEFAULT_PAGINATION_ACTIVE_COLOR = '#007AFF';
34
+ export const DEFAULT_PAGINATION_INACTIVE_COLOR = '#C7C7CC';
35
+ export const DEFAULT_PAGINATION_SIZE = 8;
36
+ export const DEFAULT_PAGINATION_GAP = 6;
37
+
38
+ /** Virtualization defaults */
39
+ export const DEFAULT_RENDER_BUFFER = 2;
40
+
41
+ /** Accessibility minimum touch target size */
42
+ export const MIN_TOUCH_TARGET = 44;
43
+
44
+ /** Screen dimensions for reference */
45
+ export { SCREEN_WIDTH, SCREEN_HEIGHT };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @file Layout calculation utilities
3
+ * @description Helpers for carousel layout, snap points, and item positioning
4
+ */
5
+
6
+ import type { SnapAlignment } from '../types';
7
+ import { DEFAULT_WIDTH } from './constants';
8
+
9
+ /**
10
+ * Calculates the offset for snap alignment.
11
+ *
12
+ * @param containerSize - Container width or height
13
+ * @param itemSize - Item width or height
14
+ * @param alignment - Snap alignment mode
15
+ * @returns Alignment offset in pixels
16
+ */
17
+ export const getAlignmentOffset = (
18
+ containerSize: number,
19
+ itemSize: number,
20
+ alignment: SnapAlignment
21
+ ): number => {
22
+ 'worklet';
23
+ switch (alignment) {
24
+ case 'start':
25
+ return 0;
26
+ case 'center':
27
+ return (containerSize - itemSize) / 2;
28
+ case 'end':
29
+ return containerSize - itemSize;
30
+ }
31
+ };
32
+
33
+ /**
34
+ * Calculates snap point for a given index.
35
+ *
36
+ * @param index - Item index
37
+ * @param itemSize - Item width or height
38
+ * @param gap - Gap between items
39
+ * @param alignmentOffset - Offset from alignment calculation
40
+ * @returns Snap point offset in pixels
41
+ */
42
+ export const getSnapPoint = (
43
+ index: number,
44
+ itemSize: number,
45
+ gap: number,
46
+ alignmentOffset: number
47
+ ): number => {
48
+ 'worklet';
49
+ return index * (itemSize + gap) - alignmentOffset;
50
+ };
51
+
52
+ /**
53
+ * Calculates all snap points for the carousel.
54
+ *
55
+ * @param totalItems - Number of items
56
+ * @param itemSize - Item width or height
57
+ * @param gap - Gap between items
58
+ * @param containerSize - Container width or height
59
+ * @param alignment - Snap alignment
60
+ * @returns Array of snap point offsets
61
+ */
62
+ export const calculateSnapPoints = (
63
+ totalItems: number,
64
+ itemSize: number,
65
+ gap: number,
66
+ containerSize: number,
67
+ alignment: SnapAlignment
68
+ ): number[] => {
69
+ const offset = getAlignmentOffset(containerSize, itemSize, alignment);
70
+ const points: number[] = [];
71
+ for (let i = 0; i < totalItems; i++) {
72
+ points.push(getSnapPoint(i, itemSize, gap, offset));
73
+ }
74
+ return points;
75
+ };
76
+
77
+ /**
78
+ * Finds the nearest snap point index for a given offset.
79
+ *
80
+ * @param offset - Current scroll offset
81
+ * @param snapPoints - Array of snap point offsets
82
+ * @returns Index of the nearest snap point
83
+ */
84
+ export const findNearestSnapIndex = (
85
+ offset: number,
86
+ snapPoints: number[]
87
+ ): number => {
88
+ 'worklet';
89
+ let nearestIndex = 0;
90
+ let nearestDistance = Math.abs(offset - snapPoints[0]);
91
+
92
+ for (let i = 1; i < snapPoints.length; i++) {
93
+ const distance = Math.abs(offset - snapPoints[i]);
94
+ if (distance < nearestDistance) {
95
+ nearestDistance = distance;
96
+ nearestIndex = i;
97
+ }
98
+ }
99
+
100
+ return nearestIndex;
101
+ };
102
+
103
+ /**
104
+ * Calculates effective item size, defaulting to container width.
105
+ *
106
+ * @param itemSize - Explicit item size (or undefined)
107
+ * @param containerSize - Container size as fallback
108
+ * @returns Effective item size
109
+ */
110
+ export const getEffectiveItemSize = (
111
+ itemSize: number | undefined,
112
+ containerSize: number | undefined
113
+ ): number => {
114
+ return itemSize ?? containerSize ?? DEFAULT_WIDTH;
115
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @file Math utility functions
3
+ * @description Shared mathematical helpers for animations and layout
4
+ */
5
+
6
+ /**
7
+ * Clamps a value between a minimum and maximum.
8
+ *
9
+ * @param value - The value to clamp
10
+ * @param min - Minimum bound
11
+ * @param max - Maximum bound
12
+ * @returns Clamped value
13
+ */
14
+ export const clamp = (value: number, min: number, max: number): number => {
15
+ 'worklet';
16
+ return Math.min(Math.max(value, min), max);
17
+ };
18
+
19
+ /**
20
+ * Linearly interpolates between two values.
21
+ *
22
+ * @param from - Start value
23
+ * @param to - End value
24
+ * @param progress - Interpolation factor (0 to 1)
25
+ * @returns Interpolated value
26
+ */
27
+ export const lerp = (from: number, to: number, progress: number): number => {
28
+ 'worklet';
29
+ return from + (to - from) * progress;
30
+ };
31
+
32
+ /**
33
+ * Normalizes a value from one range to another.
34
+ *
35
+ * @param value - Input value
36
+ * @param inputMin - Input range minimum
37
+ * @param inputMax - Input range maximum
38
+ * @param outputMin - Output range minimum
39
+ * @param outputMax - Output range maximum
40
+ * @returns Normalized value
41
+ */
42
+ export const normalize = (
43
+ value: number,
44
+ inputMin: number,
45
+ inputMax: number,
46
+ outputMin: number = 0,
47
+ outputMax: number = 1
48
+ ): number => {
49
+ 'worklet';
50
+ const inputRange = inputMax - inputMin;
51
+ if (inputRange === 0) return outputMin;
52
+ const normalized = (value - inputMin) / inputRange;
53
+ return outputMin + normalized * (outputMax - outputMin);
54
+ };
55
+
56
+ /**
57
+ * Converts degrees to radians.
58
+ *
59
+ * @param degrees - Angle in degrees
60
+ * @returns Angle in radians
61
+ */
62
+ export const toRadians = (degrees: number): number => {
63
+ 'worklet';
64
+ return (degrees * Math.PI) / 180;
65
+ };
66
+
67
+ /**
68
+ * Wraps an index within bounds (for loop mode).
69
+ *
70
+ * @param index - The index to wrap
71
+ * @param total - Total number of items
72
+ * @returns Wrapped index
73
+ */
74
+ export const wrapIndex = (index: number, total: number): number => {
75
+ 'worklet';
76
+ if (total === 0) return 0;
77
+ return ((index % total) + total) % total;
78
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @file Platform-specific utility helpers
3
+ * @description Abstractions for platform differences between iOS and Android
4
+ */
5
+
6
+ import { Platform, AccessibilityInfo } from 'react-native';
7
+
8
+ /** Whether the current platform is iOS */
9
+ export const isIOS = Platform.OS === 'ios';
10
+
11
+ /** Whether the current platform is Android */
12
+ export const isAndroid = Platform.OS === 'android';
13
+
14
+ /** Whether the current platform is web */
15
+ export const isWeb = Platform.OS === 'web';
16
+
17
+ /**
18
+ * Checks if a screen reader is currently active.
19
+ *
20
+ * @returns Promise resolving to true if screen reader is enabled
21
+ */
22
+ export const isScreenReaderEnabled = async (): Promise<boolean> => {
23
+ return AccessibilityInfo.isScreenReaderEnabled();
24
+ };
25
+
26
+ /**
27
+ * Checks if reduce motion is enabled in system settings.
28
+ *
29
+ * @returns Promise resolving to true if reduce motion is on
30
+ */
31
+ export const isReduceMotionEnabled = async (): Promise<boolean> => {
32
+ return AccessibilityInfo.isReduceMotionEnabled();
33
+ };