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,371 @@
1
+ /**
2
+ * @file Carousel component
3
+ * @description Main carousel component with gesture handling, animations, and accessibility
4
+ */
5
+
6
+ import React, {
7
+ forwardRef,
8
+ useCallback,
9
+ useEffect,
10
+ useImperativeHandle,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ } from 'react';
15
+ import { StyleSheet, View } from 'react-native';
16
+ import { GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
17
+ import { useSharedValue, runOnJS } from 'react-native-reanimated';
18
+ import type { CarouselProps, CarouselRef, CustomAnimationFn } from '../types';
19
+ import { CarouselItem } from './CarouselItem';
20
+ import { Pagination } from './Pagination';
21
+ import { AutoPlayController } from './AutoPlayController';
22
+ import { useCarouselGesture } from '../hooks/useCarouselGesture';
23
+ import { useSnapPoints } from '../hooks/useSnapPoints';
24
+ import { usePagination } from '../hooks/usePagination';
25
+ import type { UseAutoPlayReturn } from '../hooks/useAutoPlay';
26
+ import { getEffectiveItemSize } from '../utils/layout';
27
+ import { getCarouselAccessibilityProps } from '../utils/accessibility';
28
+ import {
29
+ DEFAULT_WIDTH,
30
+ DEFAULT_HEIGHT,
31
+ DEFAULT_PAGINATION_ACTIVE_COLOR,
32
+ DEFAULT_PAGINATION_INACTIVE_COLOR,
33
+ DEFAULT_PAGINATION_SIZE,
34
+ DEFAULT_PAGINATION_GAP,
35
+ } from '../utils/constants';
36
+ import { clamp } from '../utils/math';
37
+
38
+ /**
39
+ * The main Carousel component.
40
+ * Provides gesture-driven navigation, animation presets, pagination, and auto play.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * <Carousel
45
+ * data={items}
46
+ * renderItem={({ item }) => <Card item={item} />}
47
+ * preset="coverflow"
48
+ * pagination
49
+ * autoPlay
50
+ * />
51
+ * ```
52
+ */
53
+ const CarouselComponent = <T,>(
54
+ props: CarouselProps<T>,
55
+ ref: React.Ref<CarouselRef>
56
+ ) => {
57
+ const {
58
+ data,
59
+ renderItem,
60
+ preset = 'slide',
61
+ animationConfig,
62
+ width: containerWidth = DEFAULT_WIDTH,
63
+ height: containerHeight = DEFAULT_HEIGHT,
64
+ itemWidth: itemWidthProp,
65
+ itemHeight: itemHeightProp,
66
+ direction = 'horizontal',
67
+ gap = 0,
68
+ snapAlignment = 'center',
69
+ loop = false,
70
+ initialIndex = 0,
71
+ enabled = true,
72
+ autoPlay,
73
+ autoPlayInterval,
74
+ pagination,
75
+ gestureConfig,
76
+ onIndexChange,
77
+ onScrollStart,
78
+ onScrollEnd,
79
+ scrollProgress: externalScrollProgress,
80
+ plugins,
81
+ maxRenderItems = 0,
82
+ renderBuffer = 2,
83
+ accessible = true,
84
+ accessibilityLabel,
85
+ style,
86
+ itemStyle,
87
+ } = props;
88
+
89
+ const isHorizontal = direction === 'horizontal';
90
+ const containerSize = isHorizontal ? containerWidth : containerHeight;
91
+ const itemWidth = getEffectiveItemSize(itemWidthProp, containerWidth);
92
+ const itemHeight = getEffectiveItemSize(itemHeightProp, containerHeight);
93
+ const itemSize = isHorizontal ? itemWidth : itemHeight;
94
+ const totalItems = data.length;
95
+
96
+ const [currentIndex, setCurrentIndex] = useState(initialIndex);
97
+ const autoPlayRef = useRef<UseAutoPlayReturn | null>(null);
98
+
99
+ const snapPoints = useSnapPoints(
100
+ totalItems,
101
+ itemSize,
102
+ gap,
103
+ containerSize,
104
+ snapAlignment
105
+ );
106
+
107
+ const handleIndexChange = useCallback(
108
+ (index: number) => {
109
+ setCurrentIndex(index);
110
+ onIndexChange?.(index);
111
+
112
+ plugins?.forEach((plugin) => {
113
+ plugin.onIndexChange?.(index);
114
+ });
115
+ },
116
+ [onIndexChange, plugins]
117
+ );
118
+
119
+ const {
120
+ gesture,
121
+ offset,
122
+ activeIndex,
123
+ snapToIndex,
124
+ } = useCarouselGesture({
125
+ totalItems,
126
+ itemSize,
127
+ gap,
128
+ snapPoints,
129
+ direction,
130
+ loop,
131
+ enabled,
132
+ gestureConfig,
133
+ onIndexChange: handleIndexChange,
134
+ onScrollStart,
135
+ onScrollEnd,
136
+ });
137
+
138
+ const { currentPage } = usePagination(offset, itemSize, gap, totalItems);
139
+
140
+ useEffect(() => {
141
+ if (externalScrollProgress) {
142
+ externalScrollProgress.value = offset.value;
143
+ }
144
+ });
145
+
146
+ useEffect(() => {
147
+ if (initialIndex > 0 && initialIndex < totalItems) {
148
+ snapToIndex(initialIndex, false);
149
+ }
150
+ }, []);
151
+
152
+ useEffect(() => {
153
+ plugins?.forEach((plugin) => {
154
+ plugin.onInit?.(carouselRef);
155
+ });
156
+
157
+ return () => {
158
+ plugins?.forEach((plugin) => {
159
+ plugin.onDestroy?.();
160
+ });
161
+ };
162
+ }, [plugins]);
163
+
164
+ const carouselRef: CarouselRef = useMemo(
165
+ () => ({
166
+ scrollTo: (index: number, animated = true) => {
167
+ const clampedIndex = loop ? index : clamp(index, 0, totalItems - 1);
168
+ snapToIndex(clampedIndex, animated);
169
+ },
170
+ next: (animated = true) => {
171
+ const nextIndex = loop
172
+ ? (currentIndex + 1) % totalItems
173
+ : Math.min(currentIndex + 1, totalItems - 1);
174
+ snapToIndex(nextIndex, animated);
175
+ },
176
+ prev: (animated = true) => {
177
+ const prevIndex = loop
178
+ ? (currentIndex - 1 + totalItems) % totalItems
179
+ : Math.max(currentIndex - 1, 0);
180
+ snapToIndex(prevIndex, animated);
181
+ },
182
+ getCurrentIndex: () => currentIndex,
183
+ startAutoPlay: () => autoPlayRef.current?.start(),
184
+ stopAutoPlay: () => autoPlayRef.current?.stop(),
185
+ pauseAutoPlay: () => autoPlayRef.current?.pause(),
186
+ }),
187
+ [currentIndex, totalItems, loop, snapToIndex]
188
+ );
189
+
190
+ useImperativeHandle(ref, () => carouselRef, [carouselRef]);
191
+
192
+ const handleAutoPlayAdvance = useCallback(
193
+ (advanceDirection: 'forward' | 'backward') => {
194
+ if (advanceDirection === 'forward') {
195
+ carouselRef.next();
196
+ } else {
197
+ carouselRef.prev();
198
+ }
199
+ },
200
+ [carouselRef]
201
+ );
202
+
203
+ const handleAutoPlayControlsReady = useCallback(
204
+ (controls: UseAutoPlayReturn) => {
205
+ autoPlayRef.current = controls;
206
+ },
207
+ []
208
+ );
209
+
210
+ const autoPlayConfig = useMemo(() => {
211
+ if (typeof autoPlay === 'boolean') {
212
+ return {
213
+ enabled: autoPlay,
214
+ interval: autoPlayInterval,
215
+ };
216
+ }
217
+ return autoPlay;
218
+ }, [autoPlay, autoPlayInterval]);
219
+
220
+ const a11yProps = accessible
221
+ ? getCarouselAccessibilityProps(accessibilityLabel, currentIndex, totalItems)
222
+ : {};
223
+
224
+ const handleAccessibilityAction = useCallback(
225
+ (event: { nativeEvent: { actionName: string } }) => {
226
+ switch (event.nativeEvent.actionName) {
227
+ case 'increment':
228
+ carouselRef.next();
229
+ break;
230
+ case 'decrement':
231
+ carouselRef.prev();
232
+ break;
233
+ }
234
+ },
235
+ [carouselRef]
236
+ );
237
+
238
+ const paginationConfig = useMemo(() => {
239
+ if (!pagination) return null;
240
+ if (typeof pagination === 'boolean') {
241
+ return {
242
+ type: 'dot' as const,
243
+ activeColor: DEFAULT_PAGINATION_ACTIVE_COLOR,
244
+ inactiveColor: DEFAULT_PAGINATION_INACTIVE_COLOR,
245
+ size: DEFAULT_PAGINATION_SIZE,
246
+ gap: DEFAULT_PAGINATION_GAP,
247
+ };
248
+ }
249
+ return {
250
+ ...pagination,
251
+ activeColor: pagination.activeColor ?? DEFAULT_PAGINATION_ACTIVE_COLOR,
252
+ inactiveColor: pagination.inactiveColor ?? DEFAULT_PAGINATION_INACTIVE_COLOR,
253
+ size: pagination.size ?? DEFAULT_PAGINATION_SIZE,
254
+ gap: pagination.gap ?? DEFAULT_PAGINATION_GAP,
255
+ };
256
+ }, [pagination]);
257
+
258
+ const goToIndex = useCallback(
259
+ (index: number) => {
260
+ carouselRef.scrollTo(index);
261
+ },
262
+ [carouselRef]
263
+ );
264
+
265
+ const renderItems = useMemo(() => {
266
+ return data.map((item, index) => {
267
+ const isActive = index === currentIndex;
268
+ const progress = useSharedValue(0);
269
+
270
+ return (
271
+ <CarouselItem
272
+ key={index}
273
+ index={index}
274
+ totalItems={totalItems}
275
+ scrollOffset={offset}
276
+ itemWidth={itemWidth}
277
+ itemHeight={itemHeight}
278
+ gap={gap}
279
+ preset={preset as string | CustomAnimationFn}
280
+ animationConfig={animationConfig}
281
+ isActive={isActive}
282
+ accessible={accessible}
283
+ style={itemStyle}
284
+ >
285
+ {renderItem({
286
+ item,
287
+ index,
288
+ animationProgress: progress,
289
+ isActive,
290
+ })}
291
+ </CarouselItem>
292
+ );
293
+ });
294
+ }, [
295
+ data,
296
+ currentIndex,
297
+ totalItems,
298
+ offset,
299
+ itemWidth,
300
+ itemHeight,
301
+ gap,
302
+ preset,
303
+ animationConfig,
304
+ accessible,
305
+ itemStyle,
306
+ renderItem,
307
+ ]);
308
+
309
+ return (
310
+ <GestureHandlerRootView style={styles.root}>
311
+ <View
312
+ style={[
313
+ styles.container,
314
+ {
315
+ width: containerWidth,
316
+ height: containerHeight,
317
+ flexDirection: isHorizontal ? 'row' : 'column',
318
+ },
319
+ style,
320
+ ]}
321
+ {...a11yProps}
322
+ onAccessibilityAction={handleAccessibilityAction}
323
+ >
324
+ <GestureDetector gesture={gesture}>
325
+ <View style={styles.gestureContainer}>
326
+ {renderItems}
327
+ </View>
328
+ </GestureDetector>
329
+
330
+ {paginationConfig && (
331
+ <Pagination
332
+ config={paginationConfig}
333
+ totalItems={totalItems}
334
+ progress={currentPage}
335
+ goToIndex={goToIndex}
336
+ />
337
+ )}
338
+
339
+ {autoPlayConfig && (
340
+ <AutoPlayController
341
+ config={autoPlayConfig}
342
+ onAdvance={handleAutoPlayAdvance}
343
+ onControlsReady={handleAutoPlayControlsReady}
344
+ />
345
+ )}
346
+ </View>
347
+ </GestureHandlerRootView>
348
+ );
349
+ };
350
+
351
+ CarouselComponent.displayName = 'Carousel';
352
+
353
+ const styles = StyleSheet.create({
354
+ root: {
355
+ flex: 0,
356
+ },
357
+ container: {
358
+ overflow: 'hidden',
359
+ position: 'relative',
360
+ },
361
+ gestureContainer: {
362
+ flex: 1,
363
+ position: 'relative',
364
+ },
365
+ });
366
+
367
+ export const Carousel = forwardRef(CarouselComponent) as <T>(
368
+ props: CarouselProps<T> & { ref?: React.Ref<CarouselRef> }
369
+ ) => React.ReactElement;
370
+
371
+ (Carousel as React.FC).displayName = 'Carousel';
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @file CarouselItem component
3
+ * @description Wraps each carousel item with animation and accessibility
4
+ */
5
+
6
+ import React, { memo } from 'react';
7
+ import { StyleSheet, type ViewStyle, type StyleProp } from 'react-native';
8
+ import Animated, { type SharedValue } from 'react-native-reanimated';
9
+ import type { CustomAnimationFn } from '../types';
10
+ import { useAnimationProgress } from '../hooks/useAnimationProgress';
11
+ import { useItemAnimation } from '../hooks/useItemAnimation';
12
+ import { getItemAccessibilityProps } from '../utils/accessibility';
13
+
14
+ /** Props for the CarouselItem component */
15
+ export interface CarouselItemProps {
16
+ /** Item index in the data array */
17
+ index: number;
18
+ /** Total number of items */
19
+ totalItems: number;
20
+ /** Current scroll offset shared value */
21
+ scrollOffset: SharedValue<number>;
22
+ /** Item width in pixels */
23
+ itemWidth: number;
24
+ /** Item height in pixels */
25
+ itemHeight: number;
26
+ /** Gap between items */
27
+ gap: number;
28
+ /** Animation preset name or custom function */
29
+ preset?: string | CustomAnimationFn;
30
+ /** Animation config overrides */
31
+ animationConfig?: Record<string, number>;
32
+ /** Whether this item is currently active */
33
+ isActive: boolean;
34
+ /** Whether accessibility features are enabled */
35
+ accessible: boolean;
36
+ /** Custom item container style */
37
+ style?: StyleProp<ViewStyle>;
38
+ /** Content to render inside the item */
39
+ children: React.ReactNode;
40
+ }
41
+
42
+ /**
43
+ * Individual carousel item wrapper component.
44
+ * Applies animation transforms based on scroll progress.
45
+ */
46
+ const CarouselItemComponent: React.FC<CarouselItemProps> = ({
47
+ index,
48
+ totalItems,
49
+ scrollOffset,
50
+ itemWidth,
51
+ itemHeight,
52
+ gap,
53
+ preset,
54
+ animationConfig,
55
+ isActive,
56
+ accessible,
57
+ style,
58
+ children,
59
+ }) => {
60
+ const progress = useAnimationProgress(index, scrollOffset, itemWidth, gap);
61
+ const animatedStyle = useItemAnimation(
62
+ progress,
63
+ preset,
64
+ animationConfig,
65
+ index,
66
+ totalItems
67
+ );
68
+
69
+ const a11yProps = accessible
70
+ ? getItemAccessibilityProps(index, totalItems, isActive)
71
+ : {};
72
+
73
+ return (
74
+ <Animated.View
75
+ style={[
76
+ styles.item,
77
+ { width: itemWidth, height: itemHeight },
78
+ animatedStyle,
79
+ style,
80
+ ]}
81
+ {...a11yProps}
82
+ >
83
+ {children}
84
+ </Animated.View>
85
+ );
86
+ };
87
+
88
+ CarouselItemComponent.displayName = 'CarouselItem';
89
+
90
+ const styles = StyleSheet.create({
91
+ item: {
92
+ justifyContent: 'center',
93
+ alignItems: 'center',
94
+ overflow: 'hidden',
95
+ },
96
+ });
97
+
98
+ export const CarouselItem = memo(CarouselItemComponent);
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @file BarPagination component
3
+ * @description Thin bar indicators with animated active width
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
+ /** Width multiplier for active bar */
17
+ const ACTIVE_WIDTH_MULTIPLIER = 2.5;
18
+ /** Bar height in pixels */
19
+ const BAR_HEIGHT = 3;
20
+
21
+ /**
22
+ * Bar-based pagination indicator.
23
+ * Active bar stretches wider with smooth animation.
24
+ */
25
+ const BarPaginationComponent: React.FC<BasePaginationProps> = ({
26
+ totalItems,
27
+ progress,
28
+ activeColor,
29
+ inactiveColor,
30
+ size,
31
+ gap,
32
+ goToIndex,
33
+ style,
34
+ }) => {
35
+ return (
36
+ <View style={[styles.container, style]} accessibilityRole="tablist">
37
+ {Array.from({ length: totalItems }).map((_, index) => (
38
+ <BarIndicator
39
+ key={index}
40
+ index={index}
41
+ progress={progress}
42
+ activeColor={activeColor}
43
+ inactiveColor={inactiveColor}
44
+ size={size}
45
+ gap={gap}
46
+ goToIndex={goToIndex}
47
+ />
48
+ ))}
49
+ </View>
50
+ );
51
+ };
52
+
53
+ BarPaginationComponent.displayName = 'BarPagination';
54
+
55
+ /** Props for an individual bar */
56
+ interface BarIndicatorProps {
57
+ index: number;
58
+ progress: SharedValue<number>;
59
+ activeColor: string;
60
+ inactiveColor: string;
61
+ size: number;
62
+ gap: number;
63
+ goToIndex: (index: number) => void;
64
+ }
65
+
66
+ /** Individual animated bar indicator */
67
+ const BarIndicator: React.FC<BarIndicatorProps> = memo(({
68
+ index,
69
+ progress,
70
+ activeColor,
71
+ inactiveColor,
72
+ size,
73
+ gap,
74
+ goToIndex,
75
+ }) => {
76
+ const baseWidth = size * 2;
77
+
78
+ const animatedStyle = useAnimatedStyle(() => {
79
+ const distance = Math.abs(progress.value - index);
80
+
81
+ const width = interpolate(
82
+ distance,
83
+ [0, 1],
84
+ [baseWidth * ACTIVE_WIDTH_MULTIPLIER, baseWidth],
85
+ Extrapolation.CLAMP
86
+ );
87
+
88
+ const opacity = interpolate(
89
+ distance,
90
+ [0, 1],
91
+ [1, 0.4],
92
+ Extrapolation.CLAMP
93
+ );
94
+
95
+ return {
96
+ width,
97
+ opacity,
98
+ };
99
+ });
100
+
101
+ const isActive = Math.round(progress.value) === index;
102
+
103
+ return (
104
+ <Pressable
105
+ onPress={() => goToIndex(index)}
106
+ accessibilityRole="tab"
107
+ accessibilityLabel={`Page ${index + 1}`}
108
+ accessibilityState={{ selected: isActive }}
109
+ hitSlop={8}
110
+ >
111
+ <Animated.View
112
+ style={[
113
+ styles.bar,
114
+ {
115
+ height: BAR_HEIGHT,
116
+ borderRadius: BAR_HEIGHT / 2,
117
+ marginHorizontal: gap / 2,
118
+ backgroundColor: isActive ? activeColor : inactiveColor,
119
+ },
120
+ animatedStyle,
121
+ ]}
122
+ />
123
+ </Pressable>
124
+ );
125
+ });
126
+
127
+ BarIndicator.displayName = 'BarIndicator';
128
+
129
+ const styles = StyleSheet.create({
130
+ container: {
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ justifyContent: 'center',
134
+ paddingVertical: 12,
135
+ },
136
+ bar: {
137
+ // dynamic styles applied inline
138
+ },
139
+ });
140
+
141
+ export const BarPagination = memo(BarPaginationComponent);
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @file CustomPagination component
3
+ * @description Renders user-provided custom pagination via render function
4
+ */
5
+
6
+ import React, { memo } from 'react';
7
+ import type { SharedValue } from 'react-native-reanimated';
8
+ import type { PaginationRenderInfo } from '../../types';
9
+
10
+ /** Props for the CustomPagination component */
11
+ export interface CustomPaginationProps {
12
+ /** Total number of items */
13
+ totalItems: number;
14
+ /** Animated scroll progress */
15
+ progress: SharedValue<number>;
16
+ /** Navigate to specific index */
17
+ goToIndex: (index: number) => void;
18
+ /** User-provided render function */
19
+ renderCustom: (info: PaginationRenderInfo) => React.ReactNode;
20
+ }
21
+
22
+ /**
23
+ * Custom pagination that delegates rendering to a user-provided function.
24
+ * Passes all pagination state to the render function.
25
+ */
26
+ const CustomPaginationComponent: React.FC<CustomPaginationProps> = ({
27
+ totalItems,
28
+ progress,
29
+ goToIndex,
30
+ renderCustom,
31
+ }) => {
32
+ const currentIndex = Math.round(progress.value);
33
+
34
+ return (
35
+ <>
36
+ {renderCustom({
37
+ currentIndex,
38
+ totalItems,
39
+ progress,
40
+ goToIndex,
41
+ })}
42
+ </>
43
+ );
44
+ };
45
+
46
+ CustomPaginationComponent.displayName = 'CustomPagination';
47
+
48
+ export const CustomPagination = memo(CustomPaginationComponent);