@coinbase/cds-mobile 8.41.0 → 8.43.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 (114) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dts/alpha/data-card/DataCard.d.ts +27 -0
  3. package/dts/alpha/data-card/DataCard.d.ts.map +1 -0
  4. package/dts/alpha/data-card/DataCardLayout.d.ts +38 -0
  5. package/dts/alpha/data-card/DataCardLayout.d.ts.map +1 -0
  6. package/dts/alpha/data-card/__figma__/DataCard.figma.d.ts +2 -0
  7. package/dts/alpha/data-card/__figma__/DataCard.figma.d.ts.map +1 -0
  8. package/dts/alpha/data-card/index.d.ts +3 -0
  9. package/dts/alpha/data-card/index.d.ts.map +1 -0
  10. package/dts/alpha/index.d.ts +1 -0
  11. package/dts/alpha/index.d.ts.map +1 -1
  12. package/dts/cards/AnnouncementCard.d.ts +2 -2
  13. package/dts/cards/AnnouncementCard.d.ts.map +1 -1
  14. package/dts/cards/CardMedia.d.ts +1 -2
  15. package/dts/cards/CardMedia.d.ts.map +1 -1
  16. package/dts/cards/CardRoot.d.ts +32 -0
  17. package/dts/cards/CardRoot.d.ts.map +1 -0
  18. package/dts/cards/ContainedAssetCard.d.ts +27 -0
  19. package/dts/cards/ContainedAssetCard.d.ts.map +1 -1
  20. package/dts/cards/ContentCard/ContentCard.d.ts.map +1 -1
  21. package/dts/cards/ContentCard/ContentCardBody.d.ts +82 -10
  22. package/dts/cards/ContentCard/ContentCardBody.d.ts.map +1 -1
  23. package/dts/cards/ContentCard/ContentCardFooter.d.ts.map +1 -1
  24. package/dts/cards/ContentCard/ContentCardHeader.d.ts +50 -12
  25. package/dts/cards/ContentCard/ContentCardHeader.d.ts.map +1 -1
  26. package/dts/cards/DataCard.d.ts.map +1 -1
  27. package/dts/cards/FeatureEntryCard.d.ts +2 -2
  28. package/dts/cards/FeatureEntryCard.d.ts.map +1 -1
  29. package/dts/cards/FeedCard.d.ts +3 -0
  30. package/dts/cards/FeedCard.d.ts.map +1 -1
  31. package/dts/cards/FloatingAssetCard.d.ts +25 -0
  32. package/dts/cards/FloatingAssetCard.d.ts.map +1 -1
  33. package/dts/cards/MediaCard/MediaCardLayout.d.ts +40 -0
  34. package/dts/cards/MediaCard/MediaCardLayout.d.ts.map +1 -0
  35. package/dts/cards/MediaCard/__figma__/MediaCard.figma.d.ts +2 -0
  36. package/dts/cards/MediaCard/__figma__/MediaCard.figma.d.ts.map +1 -0
  37. package/dts/cards/MediaCard/index.d.ts +28 -0
  38. package/dts/cards/MediaCard/index.d.ts.map +1 -0
  39. package/dts/cards/MessagingCard/MessagingCardLayout.d.ts +62 -0
  40. package/dts/cards/MessagingCard/MessagingCardLayout.d.ts.map +1 -0
  41. package/dts/cards/MessagingCard/__figma__/MessagingCard.figma.d.ts +2 -0
  42. package/dts/cards/MessagingCard/__figma__/MessagingCard.figma.d.ts.map +1 -0
  43. package/dts/cards/MessagingCard/index.d.ts +21 -0
  44. package/dts/cards/MessagingCard/index.d.ts.map +1 -0
  45. package/dts/cards/NudgeCard.d.ts +27 -0
  46. package/dts/cards/NudgeCard.d.ts.map +1 -1
  47. package/dts/cards/UpsellCard.d.ts +27 -0
  48. package/dts/cards/UpsellCard.d.ts.map +1 -1
  49. package/dts/cards/index.d.ts +4 -0
  50. package/dts/cards/index.d.ts.map +1 -1
  51. package/dts/carousel/Carousel.d.ts +88 -14
  52. package/dts/carousel/Carousel.d.ts.map +1 -1
  53. package/dts/carousel/CarouselContext.d.ts +18 -0
  54. package/dts/carousel/CarouselContext.d.ts.map +1 -1
  55. package/dts/carousel/DefaultCarouselNavigation.d.ts +16 -0
  56. package/dts/carousel/DefaultCarouselNavigation.d.ts.map +1 -1
  57. package/dts/carousel/DefaultCarouselPagination.d.ts.map +1 -1
  58. package/dts/carousel/__figma__/Carousel.figma.d.ts +2 -0
  59. package/dts/carousel/__figma__/Carousel.figma.d.ts.map +1 -0
  60. package/dts/examples/ExampleScreen.d.ts.map +1 -1
  61. package/esm/alpha/data-card/DataCard.js +44 -0
  62. package/esm/alpha/data-card/DataCardLayout.js +80 -0
  63. package/esm/alpha/data-card/__figma__/DataCard.figma.js +29 -0
  64. package/esm/alpha/data-card/__stories__/DataCard.stories.js +367 -0
  65. package/esm/alpha/data-card/index.js +1 -0
  66. package/esm/alpha/index.js +1 -0
  67. package/esm/buttons/__figma__/SlideButton.figma.js +1 -1
  68. package/esm/cards/AnnouncementCard.js +2 -2
  69. package/esm/cards/CardMedia.js +5 -4
  70. package/esm/cards/CardRoot.js +27 -0
  71. package/esm/cards/ContainedAssetCard.js +27 -0
  72. package/esm/cards/ContentCard/ContentCard.js +7 -6
  73. package/esm/cards/ContentCard/ContentCardBody.js +74 -19
  74. package/esm/cards/ContentCard/ContentCardFooter.js +4 -5
  75. package/esm/cards/ContentCard/ContentCardHeader.js +60 -23
  76. package/esm/cards/ContentCard/__figma__/ContentCard.figma.js +9 -6
  77. package/esm/cards/DataCard.js +35 -0
  78. package/esm/cards/FeatureEntryCard.js +2 -2
  79. package/esm/cards/FeedCard.js +5 -0
  80. package/esm/cards/FloatingAssetCard.js +25 -0
  81. package/esm/cards/MediaCard/MediaCardLayout.js +68 -0
  82. package/esm/cards/MediaCard/__figma__/MediaCard.figma.js +46 -0
  83. package/esm/cards/MediaCard/index.js +45 -0
  84. package/esm/cards/MessagingCard/MessagingCardLayout.js +175 -0
  85. package/esm/cards/MessagingCard/__figma__/MessagingCard.figma.js +38 -0
  86. package/esm/cards/MessagingCard/index.js +58 -0
  87. package/esm/cards/NudgeCard.js +27 -0
  88. package/esm/cards/UpsellCard.js +27 -0
  89. package/esm/cards/__stories__/ContentCard.stories.js +375 -47
  90. package/esm/cards/__stories__/MediaCard.stories.js +189 -0
  91. package/esm/cards/__stories__/MessagingCard.stories.js +535 -0
  92. package/esm/cards/__stories__/NudgeCard.stories.js +2 -2
  93. package/esm/cards/__stories__/UpsellCard.stories.js +2 -2
  94. package/esm/cards/index.js +8 -1
  95. package/esm/carousel/Carousel.js +114 -57
  96. package/esm/carousel/CarouselContext.js +8 -0
  97. package/esm/carousel/DefaultCarouselNavigation.js +17 -2
  98. package/esm/carousel/DefaultCarouselPagination.js +150 -15
  99. package/esm/carousel/__figma__/Carousel.figma.js +15 -0
  100. package/esm/carousel/__stories__/Carousel.stories.js +60 -11
  101. package/esm/examples/ExampleScreen.js +2 -2
  102. package/package.json +3 -3
  103. package/dts/cards/CardRemoteImage.d.ts +0 -5
  104. package/dts/cards/CardRemoteImage.d.ts.map +0 -1
  105. package/dts/cards/ContentCard/__figma__/ContentCardBody.figma.d.ts +0 -2
  106. package/dts/cards/ContentCard/__figma__/ContentCardBody.figma.d.ts.map +0 -1
  107. package/dts/cards/ContentCard/__figma__/ContentCardFooter.figma.d.ts +0 -2
  108. package/dts/cards/ContentCard/__figma__/ContentCardFooter.figma.d.ts.map +0 -1
  109. package/dts/cards/ContentCard/__figma__/ContentCardHeader.figma.d.ts +0 -2
  110. package/dts/cards/ContentCard/__figma__/ContentCardHeader.figma.d.ts.map +0 -1
  111. package/esm/cards/CardRemoteImage.js +0 -18
  112. package/esm/cards/ContentCard/__figma__/ContentCardBody.figma.js +0 -39
  113. package/esm/cards/ContentCard/__figma__/ContentCardFooter.figma.js +0 -142
  114. package/esm/cards/ContentCard/__figma__/ContentCardHeader.figma.js +0 -25
@@ -1,15 +1,16 @@
1
- const _excluded = ["children", "title", "hideNavigation", "hidePagination", "drag", "snapMode", "NavigationComponent", "PaginationComponent", "style", "styles", "nextPageAccessibilityLabel", "previousPageAccessibilityLabel", "goToPageAccessibilityLabel", "onChangePage", "onDragStart", "onDragEnd", "loop"];
1
+ const _excluded = ["children", "title", "hideNavigation", "hidePagination", "drag", "snapMode", "NavigationComponent", "PaginationComponent", "style", "styles", "nextPageAccessibilityLabel", "previousPageAccessibilityLabel", "startAutoplayAccessibilityLabel", "stopAutoplayAccessibilityLabel", "paginationAccessibilityLabel", "onChangePage", "onDragStart", "onDragEnd", "loop", "autoplay", "autoplayInterval", "paginationVariant"];
2
2
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
3
3
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
4
- import React, { forwardRef, memo, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
4
+ import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
5
  import { View } from 'react-native';
6
6
  import { Gesture, GestureDetector } from 'react-native-gesture-handler';
7
+ import { useCarouselAutoplay } from '@coinbase/cds-common/carousel/useCarouselAutoplay';
7
8
  import { animated, useSpring } from '@react-spring/native';
8
9
  import { useLayout } from '../hooks/useLayout';
9
10
  import { HStack } from '../layout/HStack';
10
11
  import { VStack } from '../layout/VStack';
11
12
  import { Text } from '../typography/Text';
12
- import { CarouselContext, useCarouselContext } from './CarouselContext';
13
+ import { CarouselAutoplayContext, CarouselContext, useCarouselAutoplayContext, useCarouselContext } from './CarouselContext';
13
14
  import { CarouselItem } from './CarouselItem';
14
15
  import { DefaultCarouselNavigation } from './DefaultCarouselNavigation';
15
16
  import { DefaultCarouselPagination } from './DefaultCarouselPagination';
@@ -26,7 +27,7 @@ const wrap = (min, max, value) => {
26
27
  const range = max - min;
27
28
  return min + ((value - min) % range + range) % range;
28
29
  };
29
- export { CarouselContext, useCarouselContext };
30
+ export { CarouselAutoplayContext, CarouselContext, useCarouselAutoplayContext, useCarouselContext };
30
31
  /**
31
32
  * Calculates the locations of each item in the carousel, offset from the first item.
32
33
  * @param itemRects - The items to get the offsets for.
@@ -250,8 +251,8 @@ const getVisibleItems = (itemRects, containerWidth, scrollOffset) => {
250
251
  return visibleItems;
251
252
  };
252
253
  const animationConfig = {
253
- tension: 200,
254
- friction: 25
254
+ stiffness: 900,
255
+ damping: 120
255
256
  };
256
257
  export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) => {
257
258
  let {
@@ -267,11 +268,16 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
267
268
  styles,
268
269
  nextPageAccessibilityLabel,
269
270
  previousPageAccessibilityLabel,
270
- goToPageAccessibilityLabel,
271
+ startAutoplayAccessibilityLabel,
272
+ stopAutoplayAccessibilityLabel,
273
+ paginationAccessibilityLabel,
271
274
  onChangePage,
272
275
  onDragStart,
273
276
  onDragEnd,
274
- loop
277
+ loop,
278
+ autoplay,
279
+ autoplayInterval = 3000,
280
+ paginationVariant
275
281
  } = _ref4,
276
282
  props = _objectWithoutPropertiesLoose(_ref4, _excluded);
277
283
  const carouselScrollX = useRef(0);
@@ -283,14 +289,11 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
283
289
  const [containerSize, onLayout] = useLayout();
284
290
  const [carouselItemRects, setCarouselItemRects] = useState({});
285
291
  const [visibleCarouselItems, setVisibleCarouselItems] = useState(new Set());
286
- const rootRef = useRef(null);
287
292
  const isDragEnabled = drag !== 'none';
288
293
  const updateActivePageIndex = useCallback(newPageIndexOrUpdater => {
289
294
  setActivePageIndex(prevIndex => {
290
295
  const newPageIndex = typeof newPageIndexOrUpdater === 'function' ? newPageIndexOrUpdater(prevIndex) : newPageIndexOrUpdater;
291
- if (prevIndex !== newPageIndex && onChangePage) {
292
- onChangePage(newPageIndex);
293
- }
296
+ if (prevIndex !== newPageIndex) onChangePage == null || onChangePage(newPageIndex);
294
297
  return newPageIndex;
295
298
  });
296
299
  }, [onChangePage]);
@@ -398,6 +401,22 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
398
401
  updateActivePageIndex(pageIndex => Math.min(pageIndex, pageOffsets.totalPages - 1));
399
402
  return pageOffsets;
400
403
  }, [hasCalculatedDimensions, carouselItemRects, snapMode, containerSize.width, maxScrollOffset, shouldLoop, updateActivePageIndex]);
404
+ const {
405
+ isPlaying,
406
+ isStopped,
407
+ isPaused,
408
+ start,
409
+ stop,
410
+ toggle,
411
+ reset,
412
+ pause,
413
+ resume,
414
+ getRemainingTime,
415
+ addCompletionListener
416
+ } = useCarouselAutoplay({
417
+ enabled: autoplay != null ? autoplay : false,
418
+ interval: autoplayInterval
419
+ });
401
420
  const goToPage = useCallback(page => {
402
421
  const newPage = Math.max(0, Math.min(totalPages - 1, page));
403
422
  updateActivePageIndex(newPage);
@@ -408,12 +427,22 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
408
427
  to: targetOffset,
409
428
  config: animationConfig
410
429
  });
411
- }, [isLoopingActive, loopLength, totalPages, pageOffsets, animationApi.x, updateVisibleCarouselItems, updateActivePageIndex]);
430
+ reset();
431
+ }, [totalPages, updateActivePageIndex, updateVisibleCarouselItems, pageOffsets, isLoopingActive, loopLength, animationApi.x, reset]);
412
432
  useImperativeHandle(ref, () => ({
413
433
  activePageIndex,
414
434
  totalPages,
415
435
  goToPage
416
436
  }), [activePageIndex, totalPages, goToPage]);
437
+ useEffect(() => {
438
+ if (!autoplay || totalPages === 0) return;
439
+ const unsubscribe = addCompletionListener(() => {
440
+ const nextPage = wrap(0, totalPages, activePageIndex + 1);
441
+ reset();
442
+ goToPage(nextPage);
443
+ });
444
+ return unsubscribe;
445
+ }, [autoplay, addCompletionListener, activePageIndex, totalPages, goToPage, reset]);
417
446
  const handleGoNext = useCallback(() => {
418
447
  const nextPage = shouldLoop ? wrap(0, totalPages, activePageIndex + 1) : activePageIndex + 1;
419
448
  goToPage(nextPage);
@@ -424,10 +453,12 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
424
453
  }, [shouldLoop, totalPages, activePageIndex, goToPage]);
425
454
  const handleDragStart = useCallback(() => {
426
455
  onDragStart == null || onDragStart();
427
- }, [onDragStart]);
456
+ pause();
457
+ }, [onDragStart, pause]);
428
458
  const handleDragEnd = useCallback(() => {
429
459
  onDragEnd == null || onDragEnd();
430
- }, [onDragEnd]);
460
+ resume();
461
+ }, [onDragEnd, resume]);
431
462
  const handleDragTransition = useCallback(targetOffsetScroll => {
432
463
  if (drag === 'none') return targetOffsetScroll;
433
464
  if (isLoopingActive) {
@@ -435,6 +466,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
435
466
  offset: nearestOffset,
436
467
  index: pageIndex
437
468
  } = findNearestLoopOffset(targetOffsetScroll, pageOffsets, loopLength);
469
+ if (pageIndex !== activePageIndex) reset();
438
470
  updateActivePageIndex(pageIndex);
439
471
  if (drag === 'snap') {
440
472
  updateVisibleCarouselItems(pageOffsets[pageIndex]);
@@ -448,6 +480,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
448
480
  // Non-looping logic with clamping
449
481
  const clampedScrollOffset = clampWithElasticResistance(targetOffsetScroll, maxScrollOffset, 0);
450
482
  const closestPageIndex = getNearestPageIndexFromOffset(clampedScrollOffset, pageOffsets);
483
+ if (closestPageIndex !== activePageIndex) reset();
451
484
  updateActivePageIndex(closestPageIndex);
452
485
  if (drag === 'snap') {
453
486
  const snapOffset = pageOffsets[closestPageIndex];
@@ -457,7 +490,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
457
490
  updateVisibleCarouselItems(clampedScrollOffset);
458
491
  return targetOffsetScroll;
459
492
  }
460
- }, [drag, isLoopingActive, loopLength, maxScrollOffset, pageOffsets, updateVisibleCarouselItems, updateActivePageIndex]);
493
+ }, [drag, isLoopingActive, loopLength, maxScrollOffset, pageOffsets, activePageIndex, updateVisibleCarouselItems, updateActivePageIndex, reset]);
461
494
  const panGesture = useMemo(() => Gesture.Pan()
462
495
  // Only activate when horizontal movement exceeds threshold
463
496
  .activeOffsetX([-10, 10])
@@ -595,49 +628,73 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
595
628
  unregisterItem,
596
629
  visibleCarouselItems
597
630
  }), [registerItem, unregisterItem, visibleCarouselItems]);
631
+ const autoplayContextValue = useMemo(() => {
632
+ return {
633
+ isEnabled: !!autoplay,
634
+ isStopped,
635
+ isPaused,
636
+ isPlaying,
637
+ interval: autoplayInterval,
638
+ getRemainingTime,
639
+ start,
640
+ stop,
641
+ toggle,
642
+ reset,
643
+ pause,
644
+ resume
645
+ };
646
+ }, [autoplay, isStopped, isPaused, isPlaying, autoplayInterval, getRemainingTime, start, stop, toggle, reset, pause, resume]);
598
647
  return /*#__PURE__*/_jsx(CarouselContext.Provider, {
599
648
  value: carouselContextValue,
600
- children: /*#__PURE__*/_jsxs(VStack, _extends({
601
- ref: rootRef,
602
- "aria-live": "polite",
603
- "aria-roledescription": "carousel",
604
- gap: 2,
605
- role: "group",
606
- style: containerStyle
607
- }, props, {
608
- children: [(title || !hideNavigation) && /*#__PURE__*/_jsxs(HStack, {
609
- alignItems: "center",
610
- justifyContent: title ? 'space-between' : 'flex-end',
611
- children: [typeof title === 'string' ? /*#__PURE__*/_jsx(Text, {
612
- font: "title3",
613
- style: styles == null ? void 0 : styles.title,
614
- children: title
615
- }) : title, !hideNavigation && /*#__PURE__*/_jsx(NavigationComponent, {
616
- disableGoNext: totalPages <= 1 || !shouldLoop && activePageIndex >= totalPages - 1,
617
- disableGoPrevious: totalPages <= 1 || !shouldLoop && activePageIndex <= 0,
618
- nextPageAccessibilityLabel: nextPageAccessibilityLabel,
619
- onGoNext: handleGoNext,
620
- onGoPrevious: handleGoPrevious,
621
- previousPageAccessibilityLabel: previousPageAccessibilityLabel,
622
- style: styles == null ? void 0 : styles.navigation
623
- })]
624
- }), /*#__PURE__*/_jsx(GestureDetector, {
625
- gesture: panGesture,
626
- children: /*#__PURE__*/_jsx(View, {
627
- onLayout: onLayout,
628
- style: scrollViewStyle,
629
- children: /*#__PURE__*/_jsx(animated.View, {
630
- style: [animatedStyle, animatedTransform],
631
- children: childrenWithClones
649
+ children: /*#__PURE__*/_jsx(CarouselAutoplayContext.Provider, {
650
+ value: autoplayContextValue,
651
+ children: /*#__PURE__*/_jsxs(VStack, _extends({
652
+ "aria-live": "polite",
653
+ "aria-roledescription": "carousel",
654
+ gap: 2,
655
+ role: "group",
656
+ style: containerStyle
657
+ }, props, {
658
+ children: [(title || !hideNavigation) && /*#__PURE__*/_jsxs(HStack, {
659
+ alignItems: "center",
660
+ justifyContent: title ? 'space-between' : 'flex-end',
661
+ children: [typeof title === 'string' ? /*#__PURE__*/_jsx(Text, {
662
+ font: "title3",
663
+ style: styles == null ? void 0 : styles.title,
664
+ children: title
665
+ }) : title, !hideNavigation && /*#__PURE__*/_jsx(NavigationComponent, {
666
+ autoplay: autoplay,
667
+ disableGoNext: totalPages <= 1 || !shouldLoop && activePageIndex >= totalPages - 1,
668
+ disableGoPrevious: totalPages <= 1 || !shouldLoop && activePageIndex <= 0,
669
+ isAutoplayStopped: isStopped,
670
+ nextPageAccessibilityLabel: nextPageAccessibilityLabel,
671
+ onGoNext: handleGoNext,
672
+ onGoPrevious: handleGoPrevious,
673
+ onToggleAutoplay: toggle,
674
+ previousPageAccessibilityLabel: previousPageAccessibilityLabel,
675
+ startAutoplayAccessibilityLabel: startAutoplayAccessibilityLabel,
676
+ stopAutoplayAccessibilityLabel: stopAutoplayAccessibilityLabel,
677
+ style: styles == null ? void 0 : styles.navigation
678
+ })]
679
+ }), /*#__PURE__*/_jsx(GestureDetector, {
680
+ gesture: panGesture,
681
+ children: /*#__PURE__*/_jsx(View, {
682
+ onLayout: onLayout,
683
+ style: scrollViewStyle,
684
+ children: /*#__PURE__*/_jsx(animated.View, {
685
+ style: [animatedStyle, animatedTransform],
686
+ children: childrenWithClones
687
+ })
632
688
  })
633
- })
634
- }), !hidePagination && /*#__PURE__*/_jsx(PaginationComponent, {
635
- activePageIndex: activePageIndex,
636
- onPressPage: goToPage,
637
- paginationAccessibilityLabel: goToPageAccessibilityLabel,
638
- style: styles == null ? void 0 : styles.pagination,
639
- totalPages: totalPages
640
- })]
641
- }))
689
+ }), !hidePagination && /*#__PURE__*/_jsx(PaginationComponent, {
690
+ activePageIndex: activePageIndex,
691
+ onPressPage: goToPage,
692
+ paginationAccessibilityLabel: paginationAccessibilityLabel,
693
+ style: styles == null ? void 0 : styles.pagination,
694
+ totalPages: totalPages,
695
+ variant: paginationVariant
696
+ })]
697
+ }))
698
+ })
642
699
  });
643
700
  }));
@@ -6,4 +6,12 @@ export const useCarouselContext = () => {
6
6
  throw new Error('useCarouselContext must be used within a Carousel component');
7
7
  }
8
8
  return context;
9
+ };
10
+ export const CarouselAutoplayContext = /*#__PURE__*/React.createContext(undefined);
11
+ export const useCarouselAutoplayContext = () => {
12
+ const context = useContext(CarouselAutoplayContext);
13
+ if (!context) {
14
+ throw new Error('useCarouselAutoplayContext must be used within a Carousel component');
15
+ }
16
+ return context;
9
17
  };
@@ -4,7 +4,7 @@ import { useTheme } from '../hooks/useTheme';
4
4
  import { HStack } from '../layout/HStack';
5
5
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  export const DefaultCarouselNavigation = /*#__PURE__*/memo(function DefaultCarouselNavigation(_ref) {
7
- var _testIDMap$previousBu, _testIDMap$nextButton;
7
+ var _testIDMap$autoplayBu, _testIDMap$previousBu, _testIDMap$nextButton;
8
8
  let {
9
9
  onGoPrevious,
10
10
  onGoNext,
@@ -12,10 +12,17 @@ export const DefaultCarouselNavigation = /*#__PURE__*/memo(function DefaultCarou
12
12
  disableGoNext,
13
13
  previousPageAccessibilityLabel = 'Previous page',
14
14
  nextPageAccessibilityLabel = 'Next page',
15
+ autoplay,
16
+ isAutoplayStopped,
17
+ onToggleAutoplay,
18
+ startAutoplayAccessibilityLabel = 'Play Carousel',
19
+ stopAutoplayAccessibilityLabel = 'Pause Carousel',
15
20
  variant = 'secondary',
16
21
  compact,
17
22
  previousIcon = 'caretLeft',
18
23
  nextIcon = 'caretRight',
24
+ startIcon = 'play',
25
+ stopIcon = 'pause',
19
26
  style,
20
27
  styles,
21
28
  testIDMap
@@ -29,7 +36,15 @@ export const DefaultCarouselNavigation = /*#__PURE__*/memo(function DefaultCarou
29
36
  return /*#__PURE__*/_jsxs(HStack, {
30
37
  gap: 1,
31
38
  style: rootStyles,
32
- children: [/*#__PURE__*/_jsx(IconButton, {
39
+ children: [autoplay && /*#__PURE__*/_jsx(IconButton, {
40
+ accessibilityLabel: isAutoplayStopped ? startAutoplayAccessibilityLabel : stopAutoplayAccessibilityLabel,
41
+ compact: compact,
42
+ name: isAutoplayStopped ? startIcon : stopIcon,
43
+ onPress: onToggleAutoplay,
44
+ style: styles == null ? void 0 : styles.autoplayButton,
45
+ testID: (_testIDMap$autoplayBu = testIDMap == null ? void 0 : testIDMap.autoplayButton) != null ? _testIDMap$autoplayBu : 'carousel-autoplay-button',
46
+ variant: variant
47
+ }), /*#__PURE__*/_jsx(IconButton, {
33
48
  accessibilityLabel: previousPageAccessibilityLabel,
34
49
  compact: compact,
35
50
  disabled: disableGoPrevious,
@@ -1,49 +1,184 @@
1
- import React, { memo, useMemo } from 'react';
1
+ import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { animated, useSpring } from '@react-spring/native';
2
3
  import { useTheme } from '../hooks/useTheme';
3
4
  import { HStack } from '../layout/HStack';
4
5
  import { Pressable } from '../system/Pressable';
6
+ import { useCarouselAutoplayContext } from './CarouselContext';
5
7
  import { jsx as _jsx } from "react/jsx-runtime";
6
- export const DefaultCarouselPagination = /*#__PURE__*/memo(function DefaultCarouselPagination(_ref) {
8
+ const INDICATOR_ACTIVE_WIDTH = 24;
9
+ const INDICATOR_INACTIVE_WIDTH = 4;
10
+ const INDICATOR_HEIGHT = 4;
11
+ const PaginationPill = /*#__PURE__*/memo(function PaginationPill(_ref) {
12
+ let {
13
+ index,
14
+ isActive,
15
+ onPress,
16
+ accessibilityLabel,
17
+ style
18
+ } = _ref;
19
+ return /*#__PURE__*/_jsx(Pressable, {
20
+ accessibilityLabel: accessibilityLabel,
21
+ background: isActive ? 'bgPrimary' : 'bgLine',
22
+ borderColor: "transparent",
23
+ borderRadius: 100,
24
+ height: INDICATOR_HEIGHT,
25
+ onPress: onPress,
26
+ style: style,
27
+ testID: "carousel-page-" + index,
28
+ width: INDICATOR_ACTIVE_WIDTH
29
+ });
30
+ });
31
+ const animationConfig = {
32
+ stiffness: 900,
33
+ damping: 120,
34
+ clamp: true
35
+ };
36
+ const PaginationDot = /*#__PURE__*/memo(function PaginationDot(_ref2) {
37
+ let {
38
+ index,
39
+ isActive,
40
+ onPress,
41
+ accessibilityLabel,
42
+ style
43
+ } = _ref2;
44
+ const theme = useTheme();
45
+ const autoplayContext = useCarouselAutoplayContext();
46
+ const {
47
+ isPlaying,
48
+ isEnabled,
49
+ interval,
50
+ getRemainingTime
51
+ } = autoplayContext;
52
+ const showProgress = isActive && isEnabled;
53
+ const springProps = useSpring({
54
+ width: isActive ? INDICATOR_ACTIVE_WIDTH : INDICATOR_INACTIVE_WIDTH,
55
+ backgroundColor: isActive && !showProgress ? theme.color.bgPrimary : theme.color.bgLine,
56
+ config: animationConfig
57
+ });
58
+
59
+ // Track progress animation state
60
+ const [progressState, setProgressState] = useState({
61
+ width: 0,
62
+ duration: 0
63
+ });
64
+
65
+ // Use a ref to track the last paused progress so we can resume from it
66
+ const lastProgressRef = useRef(0);
67
+ useEffect(() => {
68
+ if (!showProgress) {
69
+ setProgressState({
70
+ width: 0,
71
+ duration: 0
72
+ });
73
+ lastProgressRef.current = 0;
74
+ return;
75
+ }
76
+ const remainingTime = getRemainingTime();
77
+ if (!interval || interval <= 0) {
78
+ return;
79
+ }
80
+ const currentProgress = 1 - remainingTime / interval;
81
+ if (isPlaying) {
82
+ lastProgressRef.current = currentProgress;
83
+ setProgressState({
84
+ width: INDICATOR_ACTIVE_WIDTH,
85
+ duration: remainingTime
86
+ });
87
+ } else {
88
+ setProgressState({
89
+ width: currentProgress * INDICATOR_ACTIVE_WIDTH,
90
+ duration: 0
91
+ });
92
+ lastProgressRef.current = currentProgress;
93
+ }
94
+ }, [isPlaying, showProgress, interval, getRemainingTime]);
95
+
96
+ // Use spring with duration config for linear timed animation
97
+ // immediate: true when duration is 0 to force instant snap (not animated)
98
+ const progressSpring = useSpring({
99
+ width: progressState.width,
100
+ config: progressState.duration > 0 ? {
101
+ duration: progressState.duration
102
+ } : {
103
+ duration: 0
104
+ },
105
+ immediate: progressState.duration === 0
106
+ });
107
+ return /*#__PURE__*/_jsx(Pressable, {
108
+ accessibilityLabel: accessibilityLabel,
109
+ borderColor: "transparent",
110
+ borderRadius: 100,
111
+ borderWidth: 0,
112
+ onPress: onPress,
113
+ overflow: "hidden",
114
+ style: style,
115
+ testID: "carousel-page-" + index,
116
+ children: /*#__PURE__*/_jsx(animated.View, {
117
+ style: {
118
+ width: springProps.width,
119
+ height: INDICATOR_HEIGHT,
120
+ backgroundColor: springProps.backgroundColor,
121
+ borderRadius: theme.borderRadius[100],
122
+ overflow: 'hidden'
123
+ },
124
+ children: showProgress && /*#__PURE__*/_jsx(animated.View, {
125
+ style: {
126
+ width: progressSpring.width,
127
+ height: '100%',
128
+ backgroundColor: theme.color.bgPrimary,
129
+ borderRadius: theme.borderRadius[100]
130
+ }
131
+ })
132
+ })
133
+ });
134
+ });
135
+ const defaultPaginationAccessibilityLabel = pageIndex => "Go to page " + (pageIndex + 1);
136
+ export const DefaultCarouselPagination = /*#__PURE__*/memo(function DefaultCarouselPagination(_ref3) {
7
137
  let {
8
138
  totalPages,
9
139
  activePageIndex,
10
140
  onPressPage,
11
141
  style,
12
142
  styles,
13
- paginationAccessibilityLabel = 'Go to page'
14
- } = _ref;
143
+ paginationAccessibilityLabel = defaultPaginationAccessibilityLabel,
144
+ variant = 'pill'
145
+ } = _ref3;
15
146
  const theme = useTheme();
147
+ const isDot = variant === 'dot';
16
148
 
17
149
  // Using paddingVertical here instead of HStack prop so it can be overridden by custom styles
18
150
  const rootStyles = useMemo(() => [{
19
151
  paddingVertical: theme.space[0.5]
20
152
  }, style, styles == null ? void 0 : styles.root], [style, styles == null ? void 0 : styles.root, theme.space]);
153
+ const getAccessibilityLabel = index => typeof paginationAccessibilityLabel === 'function' ? paginationAccessibilityLabel(index) : paginationAccessibilityLabel;
21
154
  return /*#__PURE__*/_jsx(HStack, {
22
155
  gap: 0.5,
23
156
  justifyContent: "center",
24
157
  style: rootStyles,
25
158
  children: totalPages > 0 ? Array.from({
26
159
  length: totalPages
27
- }, (_, index) => /*#__PURE__*/_jsx(Pressable, {
28
- accessibilityLabel: typeof paginationAccessibilityLabel === 'function' ? paginationAccessibilityLabel(index) : paginationAccessibilityLabel + " " + (index + 1),
29
- background: index === activePageIndex ? 'bgPrimary' : 'bgLine',
30
- borderColor: "transparent",
31
- borderRadius: 100,
32
- height: 4,
160
+ }, (_, index) => isDot ? /*#__PURE__*/_jsx(PaginationDot, {
161
+ accessibilityLabel: getAccessibilityLabel(index),
162
+ index: index,
163
+ isActive: index === activePageIndex,
164
+ onPress: () => onPressPage(index),
165
+ style: styles == null ? void 0 : styles.dot
166
+ }, index) : /*#__PURE__*/_jsx(PaginationPill, {
167
+ accessibilityLabel: getAccessibilityLabel(index),
168
+ index: index,
169
+ isActive: index === activePageIndex,
33
170
  onPress: () => onPressPage(index),
34
- style: styles == null ? void 0 : styles.dot,
35
- testID: "carousel-page-" + index,
36
- width: 24
171
+ style: styles == null ? void 0 : styles.dot
37
172
  }, index)) : /*#__PURE__*/_jsx(Pressable, {
38
173
  disabled: true,
39
174
  background: "bgLine",
40
175
  borderColor: "transparent",
41
176
  borderRadius: 100,
42
- height: 4,
177
+ height: INDICATOR_HEIGHT,
43
178
  style: [{
44
179
  opacity: 0
45
180
  }, styles == null ? void 0 : styles.dot],
46
- width: 24
181
+ width: isDot ? INDICATOR_INACTIVE_WIDTH : INDICATOR_ACTIVE_WIDTH
47
182
  })
48
183
  });
49
184
  });
@@ -0,0 +1,15 @@
1
+ import { Carousel, CarouselItem } from '@coinbase/cds-mobile/carousel';
2
+ import figma from '@figma/code-connect/react';
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ figma.connect(Carousel, 'https://www.figma.com/design/k5CtyJccNQUGMI5bI4lJ2g/%E2%9C%A8-CDS-Components?node-id=48671-10433', {
5
+ imports: ["import { Carousel, CarouselItem } from '@coinbase/cds-mobile/carousel'"],
6
+ example: () => /*#__PURE__*/_jsxs(Carousel, {
7
+ children: [/*#__PURE__*/_jsx(CarouselItem, {
8
+ id: "1"
9
+ }), /*#__PURE__*/_jsx(CarouselItem, {
10
+ id: "2"
11
+ }), /*#__PURE__*/_jsx(CarouselItem, {
12
+ id: "3"
13
+ })]
14
+ })
15
+ });