@mantine/carousel
Version:
Embla based carousel
267 lines (264 loc) • 8.49 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { useState, useCallback, useEffect, Children, createElement } from 'react';
import useEmblaCarousel from 'embla-carousel-react';
import { createVarsResolver, getSpacing, rem, factory, useProps, useStyles, useRandomClassName, useDirection, UnstyledButton, Box, AccordionChevron } from '@mantine/core';
import { clamp } from '@mantine/hooks';
import { CarouselProvider } from './Carousel.context.mjs';
import classes from './Carousel.module.css.mjs';
import { CarouselSlide } from './CarouselSlide/CarouselSlide.mjs';
import { CarouselContainerVariables, CarouselVariables } from './CarouselVariables/CarouselVariables.mjs';
import { getChevronRotation } from './get-chevron-rotation.mjs';
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, ref) => {
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,
...others
} = props;
const getStyles = useStyles({
name: "Carousel",
classes,
props,
className,
style,
classNames,
styles,
unstyled,
attributes,
vars,
varsResolver
});
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();
}
}
},
[embla]
);
useEffect(() => {
if (embla) {
getEmblaApi?.(embla);
handleSelect();
setSlidesCount(embla.scrollSnapList().length);
embla.on("select", handleSelect);
return () => {
embla.off("select", handleSelect);
};
}
return void 0;
}, [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 indicators = Array(slidesCount).fill(0).map((_, index) => /* @__PURE__ */ createElement(
UnstyledButton,
{
...getStyles("indicator"),
key: index,
"data-active": index === selected || void 0,
"aria-hidden": true,
tabIndex: -1,
onClick: () => handleScroll(index),
"data-orientation": orientation,
onMouseDown: (event) => event.preventDefault()
}
));
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,
{
ref,
...getStyles("root", { className: "responsiveClassName" }),
...others,
mod: [{ orientation, "include-gap-in-size": includeGapInSize }, mod],
onKeyDownCapture: handleKeydown,
children: [
withControls && /* @__PURE__ */ jsxs("div", { ...getStyles("controls"), "data-orientation": orientation, children: [
/* @__PURE__ */ jsx(
UnstyledButton,
{
...previousControlProps,
...getStyles("control", {
className: previousControlProps?.className,
style: previousControlProps?.style
}),
onClick: (event) => {
handlePrevious();
previousControlProps?.onClick?.(event);
},
"data-inactive": !canScrollPrev || void 0,
"data-type": "previous",
tabIndex: canScrollPrev ? 0 : -1,
children: typeof previousControlIcon !== "undefined" ? previousControlIcon : /* @__PURE__ */ jsx(
AccordionChevron,
{
style: {
transform: `rotate(${getChevronRotation({
dir,
orientation,
direction: "previous"
})}deg)`
}
}
)
}
),
/* @__PURE__ */ jsx(
UnstyledButton,
{
...getStyles("control", {
className: nextControlProps?.className,
style: nextControlProps?.style
}),
...nextControlProps,
onClick: (event) => {
handleNext();
nextControlProps?.onClick?.(event);
},
"data-inactive": !canScrollNext || void 0,
"data-type": "next",
tabIndex: canScrollNext ? 0 : -1,
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"), "data-orientation": orientation, children: indicators })
]
}
)
] });
});
Carousel.classes = classes;
Carousel.displayName = "@mantine/carousel/Carousel";
Carousel.Slide = CarouselSlide;
export { Carousel };
//# sourceMappingURL=Carousel.mjs.map