@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.
- package/CHANGELOG.md +17 -0
- package/dts/alpha/data-card/DataCard.d.ts +27 -0
- package/dts/alpha/data-card/DataCard.d.ts.map +1 -0
- package/dts/alpha/data-card/DataCardLayout.d.ts +38 -0
- package/dts/alpha/data-card/DataCardLayout.d.ts.map +1 -0
- package/dts/alpha/data-card/__figma__/DataCard.figma.d.ts +2 -0
- package/dts/alpha/data-card/__figma__/DataCard.figma.d.ts.map +1 -0
- package/dts/alpha/data-card/index.d.ts +3 -0
- package/dts/alpha/data-card/index.d.ts.map +1 -0
- package/dts/alpha/index.d.ts +1 -0
- package/dts/alpha/index.d.ts.map +1 -1
- package/dts/cards/AnnouncementCard.d.ts +2 -2
- package/dts/cards/AnnouncementCard.d.ts.map +1 -1
- package/dts/cards/CardMedia.d.ts +1 -2
- package/dts/cards/CardMedia.d.ts.map +1 -1
- package/dts/cards/CardRoot.d.ts +32 -0
- package/dts/cards/CardRoot.d.ts.map +1 -0
- package/dts/cards/ContainedAssetCard.d.ts +27 -0
- package/dts/cards/ContainedAssetCard.d.ts.map +1 -1
- package/dts/cards/ContentCard/ContentCard.d.ts.map +1 -1
- package/dts/cards/ContentCard/ContentCardBody.d.ts +82 -10
- package/dts/cards/ContentCard/ContentCardBody.d.ts.map +1 -1
- package/dts/cards/ContentCard/ContentCardFooter.d.ts.map +1 -1
- package/dts/cards/ContentCard/ContentCardHeader.d.ts +50 -12
- package/dts/cards/ContentCard/ContentCardHeader.d.ts.map +1 -1
- package/dts/cards/DataCard.d.ts.map +1 -1
- package/dts/cards/FeatureEntryCard.d.ts +2 -2
- package/dts/cards/FeatureEntryCard.d.ts.map +1 -1
- package/dts/cards/FeedCard.d.ts +3 -0
- package/dts/cards/FeedCard.d.ts.map +1 -1
- package/dts/cards/FloatingAssetCard.d.ts +25 -0
- package/dts/cards/FloatingAssetCard.d.ts.map +1 -1
- package/dts/cards/MediaCard/MediaCardLayout.d.ts +40 -0
- package/dts/cards/MediaCard/MediaCardLayout.d.ts.map +1 -0
- package/dts/cards/MediaCard/__figma__/MediaCard.figma.d.ts +2 -0
- package/dts/cards/MediaCard/__figma__/MediaCard.figma.d.ts.map +1 -0
- package/dts/cards/MediaCard/index.d.ts +28 -0
- package/dts/cards/MediaCard/index.d.ts.map +1 -0
- package/dts/cards/MessagingCard/MessagingCardLayout.d.ts +62 -0
- package/dts/cards/MessagingCard/MessagingCardLayout.d.ts.map +1 -0
- package/dts/cards/MessagingCard/__figma__/MessagingCard.figma.d.ts +2 -0
- package/dts/cards/MessagingCard/__figma__/MessagingCard.figma.d.ts.map +1 -0
- package/dts/cards/MessagingCard/index.d.ts +21 -0
- package/dts/cards/MessagingCard/index.d.ts.map +1 -0
- package/dts/cards/NudgeCard.d.ts +27 -0
- package/dts/cards/NudgeCard.d.ts.map +1 -1
- package/dts/cards/UpsellCard.d.ts +27 -0
- package/dts/cards/UpsellCard.d.ts.map +1 -1
- package/dts/cards/index.d.ts +4 -0
- package/dts/cards/index.d.ts.map +1 -1
- package/dts/carousel/Carousel.d.ts +88 -14
- package/dts/carousel/Carousel.d.ts.map +1 -1
- package/dts/carousel/CarouselContext.d.ts +18 -0
- package/dts/carousel/CarouselContext.d.ts.map +1 -1
- package/dts/carousel/DefaultCarouselNavigation.d.ts +16 -0
- package/dts/carousel/DefaultCarouselNavigation.d.ts.map +1 -1
- package/dts/carousel/DefaultCarouselPagination.d.ts.map +1 -1
- package/dts/carousel/__figma__/Carousel.figma.d.ts +2 -0
- package/dts/carousel/__figma__/Carousel.figma.d.ts.map +1 -0
- package/dts/examples/ExampleScreen.d.ts.map +1 -1
- package/esm/alpha/data-card/DataCard.js +44 -0
- package/esm/alpha/data-card/DataCardLayout.js +80 -0
- package/esm/alpha/data-card/__figma__/DataCard.figma.js +29 -0
- package/esm/alpha/data-card/__stories__/DataCard.stories.js +367 -0
- package/esm/alpha/data-card/index.js +1 -0
- package/esm/alpha/index.js +1 -0
- package/esm/buttons/__figma__/SlideButton.figma.js +1 -1
- package/esm/cards/AnnouncementCard.js +2 -2
- package/esm/cards/CardMedia.js +5 -4
- package/esm/cards/CardRoot.js +27 -0
- package/esm/cards/ContainedAssetCard.js +27 -0
- package/esm/cards/ContentCard/ContentCard.js +7 -6
- package/esm/cards/ContentCard/ContentCardBody.js +74 -19
- package/esm/cards/ContentCard/ContentCardFooter.js +4 -5
- package/esm/cards/ContentCard/ContentCardHeader.js +60 -23
- package/esm/cards/ContentCard/__figma__/ContentCard.figma.js +9 -6
- package/esm/cards/DataCard.js +35 -0
- package/esm/cards/FeatureEntryCard.js +2 -2
- package/esm/cards/FeedCard.js +5 -0
- package/esm/cards/FloatingAssetCard.js +25 -0
- package/esm/cards/MediaCard/MediaCardLayout.js +68 -0
- package/esm/cards/MediaCard/__figma__/MediaCard.figma.js +46 -0
- package/esm/cards/MediaCard/index.js +45 -0
- package/esm/cards/MessagingCard/MessagingCardLayout.js +175 -0
- package/esm/cards/MessagingCard/__figma__/MessagingCard.figma.js +38 -0
- package/esm/cards/MessagingCard/index.js +58 -0
- package/esm/cards/NudgeCard.js +27 -0
- package/esm/cards/UpsellCard.js +27 -0
- package/esm/cards/__stories__/ContentCard.stories.js +375 -47
- package/esm/cards/__stories__/MediaCard.stories.js +189 -0
- package/esm/cards/__stories__/MessagingCard.stories.js +535 -0
- package/esm/cards/__stories__/NudgeCard.stories.js +2 -2
- package/esm/cards/__stories__/UpsellCard.stories.js +2 -2
- package/esm/cards/index.js +8 -1
- package/esm/carousel/Carousel.js +114 -57
- package/esm/carousel/CarouselContext.js +8 -0
- package/esm/carousel/DefaultCarouselNavigation.js +17 -2
- package/esm/carousel/DefaultCarouselPagination.js +150 -15
- package/esm/carousel/__figma__/Carousel.figma.js +15 -0
- package/esm/carousel/__stories__/Carousel.stories.js +60 -11
- package/esm/examples/ExampleScreen.js +2 -2
- package/package.json +3 -3
- package/dts/cards/CardRemoteImage.d.ts +0 -5
- package/dts/cards/CardRemoteImage.d.ts.map +0 -1
- package/dts/cards/ContentCard/__figma__/ContentCardBody.figma.d.ts +0 -2
- package/dts/cards/ContentCard/__figma__/ContentCardBody.figma.d.ts.map +0 -1
- package/dts/cards/ContentCard/__figma__/ContentCardFooter.figma.d.ts +0 -2
- package/dts/cards/ContentCard/__figma__/ContentCardFooter.figma.d.ts.map +0 -1
- package/dts/cards/ContentCard/__figma__/ContentCardHeader.figma.d.ts +0 -2
- package/dts/cards/ContentCard/__figma__/ContentCardHeader.figma.d.ts.map +0 -1
- package/esm/cards/CardRemoteImage.js +0 -18
- package/esm/cards/ContentCard/__figma__/ContentCardBody.figma.js +0 -39
- package/esm/cards/ContentCard/__figma__/ContentCardFooter.figma.js +0 -142
- package/esm/cards/ContentCard/__figma__/ContentCardHeader.figma.js +0 -25
package/esm/carousel/Carousel.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
const _excluded = ["children", "title", "hideNavigation", "hidePagination", "drag", "snapMode", "NavigationComponent", "PaginationComponent", "style", "styles", "nextPageAccessibilityLabel", "previousPageAccessibilityLabel", "
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
456
|
+
pause();
|
|
457
|
+
}, [onDragStart, pause]);
|
|
428
458
|
const handleDragEnd = useCallback(() => {
|
|
429
459
|
onDragEnd == null || onDragEnd();
|
|
430
|
-
|
|
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__*/
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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 =
|
|
14
|
-
|
|
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(
|
|
28
|
-
accessibilityLabel:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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:
|
|
177
|
+
height: INDICATOR_HEIGHT,
|
|
43
178
|
style: [{
|
|
44
179
|
opacity: 0
|
|
45
180
|
}, styles == null ? void 0 : styles.dot],
|
|
46
|
-
width:
|
|
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
|
+
});
|