@mantine/carousel
Version:
Embla based carousel
285 lines (284 loc) • 9.4 kB
JavaScript
"use client";
import { CarouselProvider } from "./Carousel.context.mjs";
import Carousel_module_default from "./Carousel.module.mjs";
import { CarouselSlide } from "./CarouselSlide/CarouselSlide.mjs";
import { CarouselContainerVariables, CarouselVariables } from "./CarouselVariables/CarouselVariables.mjs";
import { getChevronRotation } from "./get-chevron-rotation.mjs";
import { Children, createElement, useCallback, useEffect, useState } from "react";
import useEmblaCarousel from "embla-carousel-react";
import { AccordionChevron, Box, UnstyledButton, VisuallyHidden, createVarsResolver, factory, getSpacing, rem, useDirection, useProps, useRandomClassName, useStyles } from "@mantine/core";
import { clamp, useId } from "@mantine/hooks";
import { jsx, jsxs } from "react/jsx-runtime";
//#region packages/@mantine/carousel/src/Carousel.tsx
const defaultProps = {
controlSize: 26,
controlsOffset: "sm",
slideSize: "100%",
slideGap: 0,
orientation: "horizontal",
includeGapInSize: true,
initialSlide: 0,
withControls: true,
withIndicators: false,
withKeyboardEvents: true,
type: "media"
};
const defaultEmblaOptions = {
align: "center",
loop: false,
slidesToScroll: 1,
dragFree: false,
inViewThreshold: 0,
skipSnaps: false,
containScroll: "trimSnaps"
};
const varsResolver = createVarsResolver((_, { height, controlSize, controlsOffset }) => ({ root: {
"--carousel-height": rem(height),
"--carousel-control-size": rem(controlSize),
"--carousel-controls-offset": getSpacing(controlsOffset)
} }));
const Carousel = factory((_props) => {
const props = useProps("Carousel", defaultProps, _props);
const { classNames, className, style, styles, unstyled, vars, children, getEmblaApi, onNextSlide, onPreviousSlide, onSlideChange, nextControlProps, previousControlProps, controlSize, controlsOffset, slideSize, slideGap, orientation, height, includeGapInSize, draggable, initialSlide, withControls, withIndicators, plugins, nextControlIcon, previousControlIcon, withKeyboardEvents, mod, type, emblaOptions, attributes, getIndicatorProps, id, ...others } = props;
const getStyles = useStyles({
name: "Carousel",
classes: Carousel_module_default,
props,
className,
style,
classNames,
styles,
unstyled,
attributes,
vars,
varsResolver
});
const _id = useId(id);
const responsiveClassName = useRandomClassName();
const { dir } = useDirection();
const [emblaRefElement, embla] = useEmblaCarousel({
axis: orientation === "horizontal" ? "x" : "y",
direction: orientation === "horizontal" ? dir : void 0,
startIndex: initialSlide,
...defaultEmblaOptions,
...emblaOptions
}, plugins);
const [selected, setSelected] = useState(0);
const [slidesCount, setSlidesCount] = useState(0);
const handleScroll = useCallback((index) => embla && embla.scrollTo(index), [embla]);
const handleSelect = useCallback(() => {
if (!embla) return;
const slide = embla.selectedScrollSnap();
setSelected(slide);
slide !== selected && onSlideChange?.(slide);
}, [
embla,
setSelected,
onSlideChange,
selected
]);
const handlePrevious = useCallback(() => {
embla?.scrollPrev();
onPreviousSlide?.();
}, [embla]);
const handleNext = useCallback(() => {
embla?.scrollNext();
onNextSlide?.();
}, [embla]);
const handleKeydown = useCallback((event) => {
if (withKeyboardEvents) {
if (event.key === "ArrowRight") {
event.preventDefault();
handleNext();
}
if (event.key === "ArrowLeft") {
event.preventDefault();
handlePrevious();
}
if (event.key === "Home") {
event.preventDefault();
embla?.scrollTo(0);
}
if (event.key === "End") {
event.preventDefault();
embla?.scrollTo(embla.scrollSnapList().length - 1);
}
}
}, [
embla,
handleNext,
handlePrevious
]);
useEffect(() => {
if (embla) {
getEmblaApi?.(embla);
handleSelect();
setSlidesCount(embla.scrollSnapList().length);
embla.on("select", handleSelect);
return () => {
embla.off("select", handleSelect);
};
}
}, [
embla,
emblaOptions?.slidesToScroll,
handleSelect
]);
useEffect(() => {
if (embla) {
embla.reInit();
setSlidesCount(embla.scrollSnapList().length);
setSelected((currentSelected) => clamp(currentSelected, 0, Children.toArray(children).length - 1));
}
}, [Children.toArray(children).length, emblaOptions?.slidesToScroll]);
const canScrollPrev = embla?.canScrollPrev() || false;
const canScrollNext = embla?.canScrollNext() || false;
const handleIndicatorKeyDown = useCallback((event, index) => {
const isHorizontal = orientation === "horizontal";
const nextKey = isHorizontal ? "ArrowRight" : "ArrowDown";
const prevKey = isHorizontal ? "ArrowLeft" : "ArrowUp";
if (event.key === nextKey) {
event.preventDefault();
const nextIndex = index < slidesCount - 1 ? index + 1 : 0;
handleScroll(nextIndex);
(event.currentTarget.parentElement?.children[nextIndex])?.focus();
}
if (event.key === prevKey) {
event.preventDefault();
const prevIndex = index > 0 ? index - 1 : slidesCount - 1;
handleScroll(prevIndex);
(event.currentTarget.parentElement?.children[prevIndex])?.focus();
}
if (event.key === "Home") {
event.preventDefault();
handleScroll(0);
(event.currentTarget.parentElement?.children[0])?.focus();
}
if (event.key === "End") {
event.preventDefault();
handleScroll(slidesCount - 1);
(event.currentTarget.parentElement?.children[slidesCount - 1])?.focus();
}
}, [
orientation,
slidesCount,
handleScroll
]);
const indicators = Array(slidesCount).fill(0).map((_, index) => /* @__PURE__ */ createElement(UnstyledButton, {
...getStyles("indicator"),
key: index,
role: "tab",
"aria-label": `Go to slide ${index + 1}`,
"aria-selected": index === selected,
tabIndex: index === selected ? 0 : -1,
"data-active": index === selected || void 0,
onClick: () => handleScroll(index),
onKeyDown: (event) => handleIndicatorKeyDown(event, index),
"data-orientation": orientation,
onMouseDown: (event) => event.preventDefault(),
...getIndicatorProps?.(index)
}));
return /* @__PURE__ */ jsxs(CarouselProvider, {
value: {
getStyles,
orientation
},
children: [type === "container" ? /* @__PURE__ */ jsx(CarouselContainerVariables, {
...props,
selector: `.${responsiveClassName}`
}) : /* @__PURE__ */ jsx(CarouselVariables, {
...props,
selector: `.${responsiveClassName}`
}), /* @__PURE__ */ jsxs(Box, {
role: "region",
"aria-roledescription": "carousel",
...getStyles("root", { className: responsiveClassName }),
...others,
id: _id,
mod: [{
orientation,
"include-gap-in-size": includeGapInSize
}, mod],
onKeyDownCapture: handleKeydown,
children: [
/* @__PURE__ */ jsx(VisuallyHidden, {
role: "status",
"aria-live": "polite",
"aria-atomic": "true",
children: slidesCount > 0 && `Slide ${selected + 1} of ${slidesCount}`
}),
withControls && /* @__PURE__ */ jsxs("div", {
...getStyles("controls"),
"data-orientation": orientation,
children: [/* @__PURE__ */ jsx(UnstyledButton, {
"aria-controls": _id,
"aria-label": "Previous slide",
"aria-disabled": !canScrollPrev,
"data-inactive": !canScrollPrev || void 0,
"data-type": "previous",
tabIndex: canScrollPrev ? 0 : -1,
...previousControlProps,
...getStyles("control", {
className: previousControlProps?.className,
style: previousControlProps?.style
}),
onClick: (event) => {
handlePrevious();
previousControlProps?.onClick?.(event);
},
children: typeof previousControlIcon !== "undefined" ? previousControlIcon : /* @__PURE__ */ jsx(AccordionChevron, { style: { transform: `rotate(${getChevronRotation({
dir,
orientation,
direction: "previous"
})}deg)` } })
}), /* @__PURE__ */ jsx(UnstyledButton, {
"aria-controls": _id,
"aria-label": "Next slide",
"aria-disabled": !canScrollNext,
"data-inactive": !canScrollNext || void 0,
"data-type": "next",
tabIndex: canScrollNext ? 0 : -1,
...getStyles("control", {
className: nextControlProps?.className,
style: nextControlProps?.style
}),
...nextControlProps,
onClick: (event) => {
handleNext();
nextControlProps?.onClick?.(event);
},
children: typeof nextControlIcon !== "undefined" ? nextControlIcon : /* @__PURE__ */ jsx(AccordionChevron, { style: { transform: `rotate(${getChevronRotation({
dir,
orientation,
direction: "next"
})}deg)` } })
})]
}),
/* @__PURE__ */ jsx("div", {
...getStyles("viewport"),
ref: emblaRefElement,
"data-type": type,
children: /* @__PURE__ */ jsx("div", {
...getStyles("container", { className: responsiveClassName }),
"data-orientation": orientation,
children
})
}),
withIndicators && /* @__PURE__ */ jsx("div", {
...getStyles("indicators"),
role: "tablist",
"aria-label": "Slides",
"data-orientation": orientation,
children: indicators
})
]
})]
});
});
Carousel.classes = Carousel_module_default;
Carousel.varsResolver = varsResolver;
Carousel.displayName = "@mantine/carousel/Carousel";
Carousel.Slide = CarouselSlide;
//#endregion
export { Carousel };
//# sourceMappingURL=Carousel.mjs.map