@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.
- package/CHANGELOG.md +17 -0
- package/dts/carousel/Carousel.d.ts +77 -9
- 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/CarouselItem.d.ts +2 -0
- package/dts/carousel/CarouselItem.d.ts.map +1 -1
- package/dts/carousel/DefaultCarouselNavigation.d.ts +20 -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/chips/Chip.d.ts +2 -0
- package/dts/chips/Chip.d.ts.map +1 -1
- package/dts/navigation/NavigationBar.d.ts +35 -4
- package/dts/navigation/NavigationBar.d.ts.map +1 -1
- package/dts/types.d.ts +8 -0
- package/dts/types.d.ts.map +1 -1
- package/esm/cards/MediaCard/__figma__/MediaCard.figma.js +20 -16
- package/esm/cards/MessagingCard/__figma__/MessagingCard.figma.js +3 -10
- package/esm/carousel/Carousel.css +2 -1
- package/esm/carousel/Carousel.js +301 -91
- package/esm/carousel/CarouselContext.js +8 -0
- package/esm/carousel/CarouselItem.css +1 -0
- package/esm/carousel/CarouselItem.js +15 -8
- package/esm/carousel/DefaultCarouselNavigation.js +18 -2
- package/esm/carousel/DefaultCarouselPagination.css +3 -2
- package/esm/carousel/DefaultCarouselPagination.js +138 -86
- package/esm/carousel/__figma__/Carousel.figma.js +15 -0
- package/esm/navigation/NavigationBar.js +78 -45
- package/package.json +3 -3
package/esm/carousel/Carousel.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
501
|
-
|
|
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
|
-
|
|
662
|
+
pause();
|
|
663
|
+
}, [onDragStart, pause]);
|
|
551
664
|
const handleDragEnd = useCallback(() => {
|
|
552
665
|
onDragEnd === null || onDragEnd === void 0 || onDragEnd();
|
|
553
|
-
|
|
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__*/
|
|
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: '
|
|
762
|
+
overflow: 'clip'
|
|
570
763
|
}, style), styles === null || styles === void 0 ? void 0 : styles.root),
|
|
571
764
|
width: "100%"
|
|
572
765
|
}, props), {}, {
|
|
573
|
-
children:
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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:
|
|
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:
|
|
2
|
-
.
|
|
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;}}
|