@coinbase/cds-web 8.42.0 → 8.43.2

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 (31) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dts/carousel/Carousel.d.ts +77 -9
  3. package/dts/carousel/Carousel.d.ts.map +1 -1
  4. package/dts/carousel/CarouselContext.d.ts +18 -0
  5. package/dts/carousel/CarouselContext.d.ts.map +1 -1
  6. package/dts/carousel/CarouselItem.d.ts +2 -0
  7. package/dts/carousel/CarouselItem.d.ts.map +1 -1
  8. package/dts/carousel/DefaultCarouselNavigation.d.ts +20 -0
  9. package/dts/carousel/DefaultCarouselNavigation.d.ts.map +1 -1
  10. package/dts/carousel/DefaultCarouselPagination.d.ts.map +1 -1
  11. package/dts/carousel/__figma__/Carousel.figma.d.ts +2 -0
  12. package/dts/carousel/__figma__/Carousel.figma.d.ts.map +1 -0
  13. package/dts/chips/Chip.d.ts +2 -0
  14. package/dts/chips/Chip.d.ts.map +1 -1
  15. package/dts/navigation/NavigationBar.d.ts +35 -4
  16. package/dts/navigation/NavigationBar.d.ts.map +1 -1
  17. package/dts/types.d.ts +8 -0
  18. package/dts/types.d.ts.map +1 -1
  19. package/esm/cards/MediaCard/__figma__/MediaCard.figma.js +20 -16
  20. package/esm/cards/MessagingCard/__figma__/MessagingCard.figma.js +3 -10
  21. package/esm/carousel/Carousel.css +2 -1
  22. package/esm/carousel/Carousel.js +301 -91
  23. package/esm/carousel/CarouselContext.js +8 -0
  24. package/esm/carousel/CarouselItem.css +1 -0
  25. package/esm/carousel/CarouselItem.js +15 -8
  26. package/esm/carousel/DefaultCarouselNavigation.js +18 -2
  27. package/esm/carousel/DefaultCarouselPagination.css +3 -2
  28. package/esm/carousel/DefaultCarouselPagination.js +138 -86
  29. package/esm/carousel/__figma__/Carousel.figma.js +15 -0
  30. package/esm/navigation/NavigationBar.js +78 -45
  31. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- const _excluded = ["children", "title", "hideNavigation", "hidePagination", "drag", "snapMode", "NavigationComponent", "PaginationComponent", "className", "classNames", "style", "styles", "nextPageAccessibilityLabel", "previousPageAccessibilityLabel", "paginationAccessibilityLabel", "onChangePage", "onDragStart", "onDragEnd", "loop"];
1
+ const _excluded = ["children", "title", "hideNavigation", "hidePagination", "drag", "snapMode", "NavigationComponent", "PaginationComponent", "className", "classNames", "style", "styles", "nextPageAccessibilityLabel", "previousPageAccessibilityLabel", "paginationAccessibilityLabel", "startAutoplayAccessibilityLabel", "stopAutoplayAccessibilityLabel", "pageChangeAccessibilityLabel", "onChangePage", "onDragStart", "onDragEnd", "loop", "autoplay", "autoplayInterval", "paginationVariant"];
2
2
  function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
3
3
  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; }
4
4
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -7,6 +7,7 @@ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object
7
7
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
8
8
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
9
9
  import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
10
+ import { useCarouselAutoplay } from '@coinbase/cds-common/carousel/useCarouselAutoplay';
10
11
  import { useRefMap } from '@coinbase/cds-common/hooks/useRefMap';
11
12
  import { RefMapContext } from '@coinbase/cds-common/system/RefMapContext';
12
13
  import { animate, domMax, LazyMotion, m, useAnimation, useDragControls, useMotionValue, useTransform } from 'framer-motion';
@@ -14,13 +15,20 @@ import { cx } from '../cx';
14
15
  import { HStack } from '../layout/HStack';
15
16
  import { VStack } from '../layout/VStack';
16
17
  import { Text } from '../typography';
17
- import { CarouselContext, useCarouselContext } from './CarouselContext';
18
+ import { CarouselAutoplayContext, CarouselContext, useCarouselAutoplayContext, useCarouselContext } from './CarouselContext';
18
19
  import { CarouselItem } from './CarouselItem';
19
20
  import { DefaultCarouselNavigation } from './DefaultCarouselNavigation';
20
21
  import { DefaultCarouselPagination } from './DefaultCarouselPagination';
21
22
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
23
  const defaultCarouselCss = "defaultCarouselCss-d3is9f4";
23
- export { CarouselContext, useCarouselContext };
24
+ const screenReaderOnlyCss = "screenReaderOnlyCss-shdwk7s";
25
+ const animationConfig = {
26
+ type: 'spring',
27
+ stiffness: 900,
28
+ damping: 120,
29
+ mass: 4
30
+ };
31
+ export { CarouselAutoplayContext, CarouselContext, useCarouselAutoplayContext, useCarouselContext };
24
32
  /**
25
33
  * Wraps a value within a range (min, max) for circular indexing.
26
34
  * @param min - The minimum value of the range.
@@ -253,6 +261,81 @@ const getVisibleItems = (itemRects, containerWidth, scrollOffset) => {
253
261
  });
254
262
  return visibleItems;
255
263
  };
264
+
265
+ /**
266
+ * Finds the carousel item element and its rect from a focus event target.
267
+ * Returns null if the target is not within a carousel item or is a clone.
268
+ * @param target - The focused element.
269
+ * @param carouselItemRects - The item rects to search.
270
+ * @returns The item ID and rect, or null if not found.
271
+ */
272
+ const getFocusedCarouselItemInfo = (target, carouselItemRects) => {
273
+ const carouselItemElement = target.closest('[data-carousel-item-id]');
274
+ if (!carouselItemElement) return null;
275
+ const itemId = carouselItemElement.dataset.carouselItemId;
276
+ if (!itemId || itemId.startsWith('clone-')) return null;
277
+ const itemRect = carouselItemRects[itemId];
278
+ if (!itemRect) return null;
279
+ return {
280
+ itemId,
281
+ itemRect
282
+ };
283
+ };
284
+
285
+ /**
286
+ * Checks if an item is fully visible within the current viewport.
287
+ * @param itemRect - The item rect to check.
288
+ * @param scrollOffset - The current scroll offset (positive value).
289
+ * @param containerWidth - The width of the container viewport.
290
+ * @param isLoopingActive - Whether looping is active.
291
+ * @param loopLength - The total length of one loop cycle.
292
+ * @returns Whether the item is fully visible.
293
+ */
294
+ const isItemFullyVisible = (itemRect, scrollOffset, containerWidth, isLoopingActive, loopLength) => {
295
+ const adjustedOffset = isLoopingActive ? (scrollOffset % loopLength + loopLength) % loopLength : scrollOffset;
296
+ const viewportLeft = adjustedOffset;
297
+ const viewportRight = adjustedOffset + containerWidth;
298
+ const itemLeft = itemRect.x;
299
+ const itemRight = itemRect.x + itemRect.width;
300
+ return itemLeft >= viewportLeft && itemRight <= viewportRight;
301
+ };
302
+
303
+ /**
304
+ * Finds the first focusable element within the first visible carousel item.
305
+ * @param visibleCarouselItems - Set of visible item IDs.
306
+ * @param carouselItemRects - The item rects for sorting by position.
307
+ * @param containerElement - The container element to search within.
308
+ * @returns The first focusable element, or null if not found.
309
+ */
310
+ const findFirstVisibleItem = (visibleCarouselItems, carouselItemRects, containerElement) => {
311
+ const visibleItemIds = Array.from(visibleCarouselItems).filter(id => !id.startsWith('clone-'));
312
+ if (visibleItemIds.length === 0 || !containerElement) return null;
313
+ const sortedVisibleIds = visibleItemIds.sort((a, b) => {
314
+ var _rectA$x, _rectB$x;
315
+ const rectA = carouselItemRects[a];
316
+ const rectB = carouselItemRects[b];
317
+ return ((_rectA$x = rectA === null || rectA === void 0 ? void 0 : rectA.x) !== null && _rectA$x !== void 0 ? _rectA$x : 0) - ((_rectB$x = rectB === null || rectB === void 0 ? void 0 : rectB.x) !== null && _rectB$x !== void 0 ? _rectB$x : 0);
318
+ });
319
+ const firstVisibleElement = containerElement.querySelector("[data-carousel-item-id=\"".concat(sortedVisibleIds[0], "\"]"));
320
+ if (!firstVisibleElement) return null;
321
+ return firstVisibleElement.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
322
+ };
323
+
324
+ /**
325
+ * Finds the page index that best displays the given item.
326
+ * @param itemRect - The item rect to find the page for.
327
+ * @param pageOffsets - The page offsets to search.
328
+ * @returns The page index that shows the item.
329
+ */
330
+ const findPageIndexForItem = (itemRect, pageOffsets) => {
331
+ for (let i = pageOffsets.length - 1; i >= 0; i--) {
332
+ if (pageOffsets[i] <= itemRect.x) {
333
+ return i;
334
+ }
335
+ }
336
+ return 0;
337
+ };
338
+ const defaultPageChangeAccessibilityLabel = (activePageIndex, totalPages) => "Page ".concat(activePageIndex + 1, " of ").concat(totalPages);
256
339
  export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) => {
257
340
  let {
258
341
  children,
@@ -270,10 +353,16 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
270
353
  nextPageAccessibilityLabel,
271
354
  previousPageAccessibilityLabel,
272
355
  paginationAccessibilityLabel,
356
+ startAutoplayAccessibilityLabel,
357
+ stopAutoplayAccessibilityLabel,
358
+ pageChangeAccessibilityLabel = defaultPageChangeAccessibilityLabel,
273
359
  onChangePage,
274
360
  onDragStart,
275
361
  onDragEnd,
276
- loop
362
+ loop,
363
+ autoplay,
364
+ autoplayInterval = 3000,
365
+ paginationVariant
277
366
  } = _ref4,
278
367
  props = _objectWithoutProperties(_ref4, _excluded);
279
368
  const animationApi = useAnimation();
@@ -281,7 +370,6 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
281
370
  const dragControls = useDragControls();
282
371
  const [activePageIndex, setActivePageIndex] = useState(0);
283
372
  const containerRef = useRef(null);
284
- const rootRef = useRef(null);
285
373
  const [containerWidth, setContainerWidth] = useState(0);
286
374
  const carouselItemRefMap = useRefMap();
287
375
  const [carouselItemRects, setCarouselItemRects] = useState({});
@@ -333,7 +421,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
333
421
  if (!shouldLoop) return 0;
334
422
  return contentWidth + gap;
335
423
  }, [shouldLoop, contentWidth, gap]);
336
- const isLoopingActive = shouldLoop && loopLength > 0;
424
+ const isLoopingActive = Boolean(shouldLoop && loopLength > 0);
337
425
 
338
426
  // Derived transform: physics (carouselScrollX) can go to ±∞, visuals (wrappedX) stay bounded
339
427
  const wrappedX = useTransform(carouselScrollX, value => {
@@ -344,9 +432,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
344
432
  const updateActivePageIndex = useCallback(newPageIndexOrUpdater => {
345
433
  setActivePageIndex(prevIndex => {
346
434
  const newPageIndex = typeof newPageIndexOrUpdater === 'function' ? newPageIndexOrUpdater(prevIndex) : newPageIndexOrUpdater;
347
- if (prevIndex !== newPageIndex && onChangePage) {
348
- onChangePage(newPageIndex);
349
- }
435
+ if (prevIndex !== newPageIndex) onChangePage === null || onChangePage === void 0 || onChangePage(newPageIndex);
350
436
  return newPageIndex;
351
437
  });
352
438
  }, [onChangePage]);
@@ -439,6 +525,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
439
525
  const cloneId = "clone-backward-".concat(child.props.id);
440
526
  result.push(/*#__PURE__*/_jsx(CarouselItem, {
441
527
  "aria-hidden": true,
528
+ isClone: true,
442
529
  id: cloneId,
443
530
  style: _objectSpread({
444
531
  position: 'absolute',
@@ -460,6 +547,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
460
547
  const cloneId = "clone-forward-".concat(child.props.id);
461
548
  result.push(/*#__PURE__*/_jsx(CarouselItem, {
462
549
  "aria-hidden": true,
550
+ isClone: true,
463
551
  id: cloneId,
464
552
  style: _objectSpread({
465
553
  width: itemData === null || itemData === void 0 ? void 0 : itemData.width,
@@ -491,21 +579,43 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
491
579
  updateActivePageIndex(pageIndex => Math.min(pageIndex, pageOffsets.totalPages - 1));
492
580
  return pageOffsets;
493
581
  }, [hasDimensions, carouselItemRects, snapMode, containerWidth, maxScrollOffset, shouldLoop, updateActivePageIndex]);
582
+ const {
583
+ isPlaying,
584
+ isStopped,
585
+ isPaused,
586
+ start,
587
+ stop,
588
+ toggle,
589
+ reset,
590
+ pause,
591
+ resume,
592
+ getRemainingTime,
593
+ addCompletionListener
594
+ } = useCarouselAutoplay({
595
+ enabled: autoplay !== null && autoplay !== void 0 ? autoplay : false,
596
+ interval: autoplayInterval
597
+ });
494
598
  const goToPage = useCallback(page => {
495
599
  const newPage = Math.max(0, Math.min(totalPages - 1, page));
496
600
  updateActivePageIndex(newPage);
497
601
  updateVisibleCarouselItems(pageOffsets[newPage]);
498
602
  const targetOffset = isLoopingActive ? findNearestLoopOffset(-carouselScrollX.get(), [pageOffsets[newPage]], loopLength).offset : pageOffsets[newPage];
499
- animate(carouselScrollX, -targetOffset, {
500
- type: 'tween',
501
- duration: 0.25
502
- });
503
- }, [totalPages, updateActivePageIndex, updateVisibleCarouselItems, pageOffsets, isLoopingActive, carouselScrollX, loopLength]);
603
+ animate(carouselScrollX, -targetOffset, animationConfig);
604
+ reset();
605
+ }, [totalPages, updateActivePageIndex, updateVisibleCarouselItems, pageOffsets, isLoopingActive, carouselScrollX, loopLength, reset]);
504
606
  useImperativeHandle(ref, () => ({
505
607
  activePageIndex,
506
608
  totalPages,
507
609
  goToPage
508
610
  }), [activePageIndex, totalPages, goToPage]);
611
+ useEffect(() => {
612
+ if (!autoplay || totalPages === 0) return;
613
+ const unsubscribe = addCompletionListener(() => {
614
+ const nextPage = wrap(0, totalPages, activePageIndex + 1);
615
+ goToPage(nextPage);
616
+ });
617
+ return unsubscribe;
618
+ }, [autoplay, addCompletionListener, activePageIndex, totalPages, goToPage]);
509
619
  const handleGoNext = useCallback(() => {
510
620
  const nextPage = shouldLoop ? wrap(0, totalPages, activePageIndex + 1) : activePageIndex + 1;
511
621
  goToPage(nextPage);
@@ -522,6 +632,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
522
632
  offset: nearestOffset,
523
633
  index: pageIndex
524
634
  } = findNearestLoopOffset(targetOffset, pageOffsets, loopLength);
635
+ if (pageIndex !== activePageIndex) reset();
525
636
  updateActivePageIndex(pageIndex);
526
637
  if (drag === 'snap') {
527
638
  updateVisibleCarouselItems(pageOffsets[pageIndex]);
@@ -535,6 +646,7 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
535
646
  // Non-looping logic with clamping
536
647
  const clampedScrollOffset = clampWithElasticResistance(targetOffset, maxScrollOffset, 0);
537
648
  const closestPageIndex = getNearestPageIndexFromOffset(clampedScrollOffset, pageOffsets);
649
+ if (closestPageIndex !== activePageIndex) reset();
538
650
  updateActivePageIndex(closestPageIndex);
539
651
  if (drag === 'snap') {
540
652
  const snapOffset = pageOffsets[closestPageIndex];
@@ -544,103 +656,201 @@ export const Carousel = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref4, ref) =
544
656
  updateVisibleCarouselItems(clampedScrollOffset);
545
657
  return targetOffsetScroll;
546
658
  }
547
- }, [drag, isLoopingActive, pageOffsets, loopLength, updateActivePageIndex, updateVisibleCarouselItems, maxScrollOffset]);
659
+ }, [drag, isLoopingActive, pageOffsets, loopLength, activePageIndex, updateActivePageIndex, updateVisibleCarouselItems, maxScrollOffset, reset]);
548
660
  const handleDragStart = useCallback(() => {
549
661
  onDragStart === null || onDragStart === void 0 || onDragStart();
550
- }, [onDragStart]);
662
+ pause();
663
+ }, [onDragStart, pause]);
551
664
  const handleDragEnd = useCallback(() => {
552
665
  onDragEnd === null || onDragEnd === void 0 || onDragEnd();
553
- }, [onDragEnd]);
666
+ resume();
667
+ }, [onDragEnd, resume]);
668
+ const handlePointerEnter = useCallback(() => {
669
+ pause();
670
+ }, [pause]);
671
+ const handlePointerLeave = useCallback(() => {
672
+ resume();
673
+ }, [resume]);
674
+
675
+ // Resume autoplay when focus leaves the carousel items container
676
+ const handleBlur = useCallback(event => {
677
+ var _containerRef$current;
678
+ const relatedTarget = event.relatedTarget;
679
+ // Only resume if we know focus is going outside the container.
680
+ // If relatedTarget is null (e.g., focus leaving window), also resume.
681
+ const isLeavingContainer = !relatedTarget || !((_containerRef$current = containerRef.current) !== null && _containerRef$current !== void 0 && _containerRef$current.contains(relatedTarget));
682
+ if (isLeavingContainer) {
683
+ resume();
684
+ }
685
+ }, [resume]);
686
+
687
+ // Handle focus moving to an element inside the carousel items container.
688
+ // Pauses autoplay when focus enters from outside (keyboard navigation a11y).
689
+ // Scrolls to show focused items that are not fully visible.
690
+ const handleFocusIn = useCallback(event => {
691
+ var _containerRef$current2;
692
+ const relatedTarget = event.relatedTarget;
693
+ // Check if focus is entering from outside the carousel items container
694
+ const isEnteringFromOutside = !relatedTarget || !((_containerRef$current2 = containerRef.current) !== null && _containerRef$current2 !== void 0 && _containerRef$current2.contains(relatedTarget));
695
+
696
+ // Pause autoplay when focus enters the container from outside.
697
+ // Only pause if we positively know focus came from outside (relatedTarget exists).
698
+ // This avoids false pauses during render, programmatic focus, or test environments.
699
+ if (relatedTarget && isEnteringFromOutside) {
700
+ pause();
701
+ }
702
+ if (pageOffsets.length === 0 || Object.keys(carouselItemRects).length === 0) return;
703
+ const target = event.target;
704
+ const focusedItem = getFocusedCarouselItemInfo(target, carouselItemRects);
705
+ if (!focusedItem) return;
706
+ const {
707
+ itemRect
708
+ } = focusedItem;
709
+ const currentOffset = Math.abs(carouselScrollX.get());
710
+
711
+ // Item is already visible - no action needed
712
+ if (isItemFullyVisible(itemRect, currentOffset, containerWidth, isLoopingActive, loopLength)) {
713
+ return;
714
+ }
715
+ if (isEnteringFromOutside) {
716
+ // Redirect focus to first focusable element on current page
717
+ const focusable = findFirstVisibleItem(visibleCarouselItems, carouselItemRects, containerRef.current);
718
+ if (focusable && focusable !== target) {
719
+ focusable.focus({
720
+ preventScroll: true
721
+ });
722
+ return;
723
+ }
724
+ }
725
+
726
+ // Navigate to show the focused item and reset autoplay progress
727
+ const targetPageIndex = findPageIndexForItem(itemRect, pageOffsets);
728
+ if (targetPageIndex !== activePageIndex) {
729
+ reset();
730
+ goToPage(targetPageIndex);
731
+ }
732
+ }, [pause, reset, pageOffsets, carouselItemRects, carouselScrollX, isLoopingActive, loopLength, containerWidth, visibleCarouselItems, activePageIndex, goToPage]);
554
733
  const carouselContextValue = useMemo(() => ({
555
734
  visibleCarouselItems
556
735
  }), [visibleCarouselItems]);
736
+ const autoplayContextValue = useMemo(() => ({
737
+ isEnabled: !!autoplay,
738
+ isStopped,
739
+ isPaused,
740
+ isPlaying,
741
+ interval: autoplayInterval,
742
+ getRemainingTime,
743
+ start,
744
+ stop,
745
+ toggle,
746
+ reset,
747
+ pause,
748
+ resume
749
+ }), [autoplay, isStopped, isPaused, isPlaying, autoplayInterval, getRemainingTime, start, stop, toggle, reset, pause, resume]);
557
750
  return /*#__PURE__*/_jsx(LazyMotion, {
558
751
  features: domMax,
559
752
  children: /*#__PURE__*/_jsx(RefMapContext.Provider, {
560
753
  value: carouselItemRefMap,
561
- children: /*#__PURE__*/_jsxs(VStack, _objectSpread(_objectSpread({
562
- ref: rootRef,
563
- "aria-live": "polite",
754
+ children: /*#__PURE__*/_jsx(VStack, _objectSpread(_objectSpread({
564
755
  "aria-roledescription": "carousel",
565
756
  className: cx(className, classNames === null || classNames === void 0 ? void 0 : classNames.root),
566
757
  gap: 2,
758
+ onPointerEnter: handlePointerEnter,
759
+ onPointerLeave: handlePointerLeave,
567
760
  role: "group",
568
761
  style: _objectSpread(_objectSpread({
569
- overflow: 'hidden'
762
+ overflow: 'clip'
570
763
  }, style), styles === null || styles === void 0 ? void 0 : styles.root),
571
764
  width: "100%"
572
765
  }, props), {}, {
573
- children: [(title || !hideNavigation) && /*#__PURE__*/_jsxs(HStack, {
574
- alignItems: "center",
575
- justifyContent: title ? 'space-between' : 'flex-end',
576
- children: [typeof title === 'string' ? /*#__PURE__*/_jsx(Text, {
577
- className: classNames === null || classNames === void 0 ? void 0 : classNames.title,
578
- font: "title3",
579
- style: styles === null || styles === void 0 ? void 0 : styles.title,
580
- children: title
581
- }) : title, !hideNavigation && /*#__PURE__*/_jsx(NavigationComponent, {
582
- className: classNames === null || classNames === void 0 ? void 0 : classNames.navigation,
583
- disableGoNext: totalPages <= 1 || !shouldLoop && activePageIndex >= totalPages - 1,
584
- disableGoPrevious: totalPages <= 1 || !shouldLoop && activePageIndex <= 0,
585
- nextPageAccessibilityLabel: nextPageAccessibilityLabel,
586
- onGoNext: handleGoNext,
587
- onGoPrevious: handleGoPrevious,
588
- previousPageAccessibilityLabel: previousPageAccessibilityLabel,
589
- style: styles === null || styles === void 0 ? void 0 : styles.navigation
590
- })]
591
- }), /*#__PURE__*/_jsx("div", {
592
- ref: containerRef,
593
- className: classNames === null || classNames === void 0 ? void 0 : classNames.carouselContainer,
594
- onPointerDown: e => {
595
- if (isDragEnabled) {
596
- // Allows us to grab between items where child wouldn't be selected
597
- dragControls.start(e);
598
- handleDragStart();
599
- }
600
- },
601
- style: _objectSpread({
602
- width: '100%',
603
- position: 'relative'
604
- }, styles === null || styles === void 0 ? void 0 : styles.carouselContainer),
605
- children: /*#__PURE__*/_jsx(CarouselContext.Provider, {
606
- value: carouselContextValue,
607
- children: /*#__PURE__*/_jsx(m.div, {
608
- _dragX: carouselScrollX,
609
- animate: animationApi,
610
- className: cx(classNames === null || classNames === void 0 ? void 0 : classNames.carousel, defaultCarouselCss),
611
- drag: isDragEnabled ? 'x' : false,
612
- dragConstraints: shouldLoop ? undefined : {
613
- left: -maxScrollOffset,
614
- right: 0
615
- },
616
- dragControls: dragControls,
617
- dragTransition: {
618
- // How much inertia affects the target
619
- power: drag === 'free' ? 0.5 : 0.125,
620
- timeConstant: drag !== 'free' ? 125 : undefined,
621
- modifyTarget: handleDragTransition
622
- },
623
- initial: false,
624
- onDragEnd: handleDragEnd,
625
- style: _objectSpread({
626
- display: 'flex',
627
- position: 'relative',
628
- x: shouldLoop ? wrappedX : carouselScrollX
629
- }, styles === null || styles === void 0 ? void 0 : styles.carousel),
630
- whileDrag: {
631
- pointerEvents: 'none'
632
- },
633
- children: childrenWithClones
766
+ children: /*#__PURE__*/_jsxs(CarouselAutoplayContext.Provider, {
767
+ value: autoplayContextValue,
768
+ children: [(title || !hideNavigation) && /*#__PURE__*/_jsxs(HStack, {
769
+ alignItems: "center",
770
+ justifyContent: title ? 'space-between' : 'flex-end',
771
+ children: [typeof title === 'string' ? /*#__PURE__*/_jsx(Text, {
772
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.title,
773
+ font: "title3",
774
+ style: styles === null || styles === void 0 ? void 0 : styles.title,
775
+ children: title
776
+ }) : title, !hideNavigation && /*#__PURE__*/_jsx(NavigationComponent, {
777
+ autoplay: autoplay,
778
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.navigation,
779
+ disableGoNext: totalPages <= 1 || !shouldLoop && activePageIndex >= totalPages - 1,
780
+ disableGoPrevious: totalPages <= 1 || !shouldLoop && activePageIndex <= 0,
781
+ isAutoplayStopped: isStopped,
782
+ nextPageAccessibilityLabel: nextPageAccessibilityLabel,
783
+ onGoNext: handleGoNext,
784
+ onGoPrevious: handleGoPrevious,
785
+ onToggleAutoplay: toggle,
786
+ previousPageAccessibilityLabel: previousPageAccessibilityLabel,
787
+ startAutoplayAccessibilityLabel: startAutoplayAccessibilityLabel,
788
+ stopAutoplayAccessibilityLabel: stopAutoplayAccessibilityLabel,
789
+ style: styles === null || styles === void 0 ? void 0 : styles.navigation
790
+ })]
791
+ }), /*#__PURE__*/_jsx("div", {
792
+ ref: containerRef,
793
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.carouselContainer,
794
+ onBlur: handleBlur,
795
+ onFocus: handleFocusIn,
796
+ onPointerDown: e => {
797
+ if (isDragEnabled) {
798
+ // Allows us to grab between items where child wouldn't be selected
799
+ dragControls.start(e);
800
+ handleDragStart();
801
+ }
802
+ },
803
+ style: _objectSpread({
804
+ width: '100%',
805
+ position: 'relative'
806
+ }, styles === null || styles === void 0 ? void 0 : styles.carouselContainer),
807
+ children: /*#__PURE__*/_jsxs(CarouselContext.Provider, {
808
+ value: carouselContextValue,
809
+ children: [totalPages > 0 && /*#__PURE__*/_jsx("div", {
810
+ "aria-atomic": "true",
811
+ "aria-live": isPlaying ? 'off' : 'polite',
812
+ className: screenReaderOnlyCss,
813
+ role: "status",
814
+ children: pageChangeAccessibilityLabel(activePageIndex, totalPages)
815
+ }), /*#__PURE__*/_jsx(m.div, {
816
+ _dragX: carouselScrollX,
817
+ animate: animationApi,
818
+ className: cx(classNames === null || classNames === void 0 ? void 0 : classNames.carousel, defaultCarouselCss),
819
+ drag: isDragEnabled ? 'x' : false,
820
+ dragConstraints: shouldLoop ? undefined : {
821
+ left: -maxScrollOffset,
822
+ right: 0
823
+ },
824
+ dragControls: dragControls,
825
+ dragTransition: {
826
+ // How much inertia affects the target
827
+ power: drag === 'free' ? 0.5 : 0.125,
828
+ timeConstant: drag !== 'free' ? 125 : undefined,
829
+ modifyTarget: handleDragTransition
830
+ },
831
+ initial: false,
832
+ onDragEnd: handleDragEnd,
833
+ style: _objectSpread({
834
+ display: 'flex',
835
+ position: 'relative',
836
+ x: shouldLoop ? wrappedX : carouselScrollX
837
+ }, styles === null || styles === void 0 ? void 0 : styles.carousel),
838
+ whileDrag: {
839
+ pointerEvents: 'none'
840
+ },
841
+ children: childrenWithClones
842
+ })]
634
843
  })
635
- })
636
- }), !hidePagination && /*#__PURE__*/_jsx(PaginationComponent, {
637
- activePageIndex: activePageIndex,
638
- className: classNames === null || classNames === void 0 ? void 0 : classNames.pagination,
639
- onClickPage: goToPage,
640
- paginationAccessibilityLabel: paginationAccessibilityLabel,
641
- style: styles === null || styles === void 0 ? void 0 : styles.pagination,
642
- totalPages: totalPages
643
- })]
844
+ }), !hidePagination && /*#__PURE__*/_jsx(PaginationComponent, {
845
+ activePageIndex: activePageIndex,
846
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.pagination,
847
+ onClickPage: goToPage,
848
+ paginationAccessibilityLabel: paginationAccessibilityLabel,
849
+ style: styles === null || styles === void 0 ? void 0 : styles.pagination,
850
+ totalPages: totalPages,
851
+ variant: paginationVariant
852
+ })]
853
+ })
644
854
  }))
645
855
  })
646
856
  });
@@ -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
  };
@@ -0,0 +1 @@
1
+ @layer cds{.carouselItemCss-c1rrqdlt{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;}}
@@ -1,4 +1,4 @@
1
- const _excluded = ["id", "children", "testID", "style"];
1
+ const _excluded = ["id", "children", "testID", "style", "className", "isClone"];
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -8,19 +8,23 @@ function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i
8
8
  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; }
9
9
  import React, { memo, useCallback } from 'react';
10
10
  import { useRefMapContext } from '@coinbase/cds-common/system/RefMapContext';
11
+ import { cx } from '../cx';
11
12
  import { Box } from '../layout/Box';
12
13
  import { useCarouselContext } from './CarouselContext';
14
+ import { jsx as _jsx } from "react/jsx-runtime";
15
+ const carouselItemCss = "carouselItemCss-c1rrqdlt";
13
16
 
14
17
  /**
15
18
  * Individual carousel item component that registers itself with the carousel via RefMapContext.
16
19
  */
17
- import { jsx as _jsx } from "react/jsx-runtime";
18
20
  export const CarouselItem = /*#__PURE__*/memo(_ref => {
19
21
  let {
20
22
  id,
21
23
  children,
22
24
  testID,
23
- style
25
+ style,
26
+ className,
27
+ isClone
24
28
  } = _ref,
25
29
  props = _objectWithoutProperties(_ref, _excluded);
26
30
  const {
@@ -37,16 +41,19 @@ export const CarouselItem = /*#__PURE__*/memo(_ref => {
37
41
  ref: refCallback,
38
42
  "aria-hidden": !isVisible,
39
43
  "aria-roledescription": "carousel item",
44
+ className: cx(carouselItemCss, className),
45
+ "data-carousel-item-id": id
46
+ // @ts-expect-error - inert is a valid HTML attribute but not yet in React types, used to prevent navigation to clones
47
+ ,
48
+ inert: isClone ? '' : undefined,
40
49
  maxWidth: "100%",
41
50
  role: "group",
42
- style: _objectSpread({
43
- flexShrink: 0
44
- }, style),
45
- tabIndex: isVisible ? undefined : -1,
51
+ style: style,
46
52
  testID: testID !== null && testID !== void 0 ? testID : "carousel-item-".concat(id)
47
53
  }, props), {}, {
48
54
  children: typeof children === 'function' ? children({
49
55
  isVisible
50
56
  }) : children
51
57
  }));
52
- });
58
+ });
59
+ import "./CarouselItem.css";
@@ -10,7 +10,7 @@ import { HStack } from '../layout/HStack';
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  const navigationCss = "navigationCss-n15h1fxo";
12
12
  export const DefaultCarouselNavigation = /*#__PURE__*/memo(function DefaultCarouselNavigation(_ref) {
13
- var _testIDMap$previousBu, _testIDMap$nextButton;
13
+ var _testIDMap$autoplayBu, _testIDMap$previousBu, _testIDMap$nextButton;
14
14
  let {
15
15
  onGoPrevious,
16
16
  onGoNext,
@@ -18,8 +18,15 @@ export const DefaultCarouselNavigation = /*#__PURE__*/memo(function DefaultCarou
18
18
  disableGoNext,
19
19
  nextPageAccessibilityLabel = 'Next page',
20
20
  previousPageAccessibilityLabel = 'Previous page',
21
+ autoplay,
22
+ isAutoplayStopped,
23
+ onToggleAutoplay,
24
+ startAutoplayAccessibilityLabel = 'Play Carousel',
25
+ stopAutoplayAccessibilityLabel = 'Pause Carousel',
21
26
  previousIcon = 'caretLeft',
22
27
  nextIcon = 'caretRight',
28
+ startIcon = 'play',
29
+ stopIcon = 'pause',
23
30
  variant = 'secondary',
24
31
  compact,
25
32
  className,
@@ -34,7 +41,16 @@ export const DefaultCarouselNavigation = /*#__PURE__*/memo(function DefaultCarou
34
41
  "data-hiddenunlessfocused": hideUnlessFocused,
35
42
  gap: 1,
36
43
  style: _objectSpread(_objectSpread({}, style), styles === null || styles === void 0 ? void 0 : styles.root),
37
- children: [/*#__PURE__*/_jsx(IconButton, {
44
+ children: [autoplay && /*#__PURE__*/_jsx(IconButton, {
45
+ accessibilityLabel: isAutoplayStopped ? startAutoplayAccessibilityLabel : stopAutoplayAccessibilityLabel,
46
+ className: classNames === null || classNames === void 0 ? void 0 : classNames.autoplayButton,
47
+ compact: compact,
48
+ name: isAutoplayStopped ? startIcon : stopIcon,
49
+ onClick: onToggleAutoplay,
50
+ style: styles === null || styles === void 0 ? void 0 : styles.autoplayButton,
51
+ testID: (_testIDMap$autoplayBu = testIDMap === null || testIDMap === void 0 ? void 0 : testIDMap.autoplayButton) !== null && _testIDMap$autoplayBu !== void 0 ? _testIDMap$autoplayBu : 'carousel-autoplay-button',
52
+ variant: variant
53
+ }), /*#__PURE__*/_jsx(IconButton, {
38
54
  accessibilityLabel: previousPageAccessibilityLabel,
39
55
  className: classNames === null || classNames === void 0 ? void 0 : classNames.previousButton,
40
56
  compact: compact,
@@ -1,2 +1,3 @@
1
- @layer cds{.defaultPaginationCss-dyqwmgi{padding:var(--space-0_5) 0;}
2
- .dotCss-dea8kmz{width:var(--space-3);height:var(--space-0_5);border-radius:var(--borderRadius-100);}}
1
+ @layer cds{.defaultPaginationCss-dyqwmgi{padding:4px 0;}
2
+ .pillCss-pea8kmz{width:24px;height:4px;border-radius:var(--borderRadius-100);}
3
+ .dotCss-d1eoinf6{height:4px;border-radius:var(--borderRadius-100);overflow:hidden;}}