UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

157 lines (156 loc) • 5.33 kB
"use client"; import ActionIcon from "../ActionIcon/ActionIcon.mjs"; import { useEventCallback } from "../../hooks/useEventCallback.mjs"; import { styles } from "./style.mjs"; import { Children, isValidElement, memo, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"; import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime"; import { cx } from "antd-style"; import useMergeState from "use-merge-value"; import { ChevronLeft, ChevronRight } from "lucide-react"; //#region src/base-ui/Carousel/Carousel.tsx const DEFAULT_INTERVAL = 5e3; const SWIPE_THRESHOLD = 40; const ARROW_SIZE = { blockSize: 28, borderRadius: "50%", size: 16 }; const Carousel = memo(({ adaptiveHeight = false, arrows = false, autoplay = false, children, className, defaultIndex = 0, dots = true, index, loop = true, onIndexChange, onPointerEnter, onPointerLeave, pauseOnHover = true, ref, ...rest }) => { const slides = Children.toArray(children); const count = slides.length; const keys = slides.map((slide, slideIndex) => isValidElement(slide) && slide.key !== null ? slide.key : slideIndex); const [mergedIndex, setMergedIndex] = useMergeState(defaultIndex, { onChange: onIndexChange, value: index }); const current = Math.min(Math.max(mergedIndex, 0), Math.max(count - 1, 0)); const [hovered, setHovered] = useState(false); const [height, setHeight] = useState(); const slidesRef = useRef([]); const swipeStartXRef = useRef(null); const goTo = useEventCallback((target) => { if (count === 0) return; const next = loop ? (target % count + count) % count : Math.min(Math.max(target, 0), count - 1); if (next !== current) setMergedIndex(next); }); const next = useEventCallback(() => goTo(current + 1)); const prev = useEventCallback(() => goTo(current - 1)); useImperativeHandle(ref, () => ({ goTo, next, prev }), [ goTo, next, prev ]); const interval = autoplay === true ? DEFAULT_INTERVAL : autoplay || 0; const paused = pauseOnHover && hovered; const atEnd = !loop && current >= count - 1; useEffect(() => { if (!interval || paused || count < 2 || atEnd) return; const timer = setTimeout(next, interval); return () => clearTimeout(timer); }, [ interval, paused, count, atEnd, current, next ]); useLayoutEffect(() => { const slide = slidesRef.current[current]; if (!adaptiveHeight || !slide) return; setHeight(slide.offsetHeight); if (typeof ResizeObserver === "undefined") return; const observer = new ResizeObserver(() => setHeight(slide.offsetHeight)); observer.observe(slide); return () => observer.disconnect(); }, [ adaptiveHeight, current, count ]); return /* @__PURE__ */ jsxs("div", { "aria-roledescription": "carousel", className: cx(styles.root, className), role: "region", onPointerEnter: (event) => { onPointerEnter?.(event); setHovered(true); }, onPointerLeave: (event) => { onPointerLeave?.(event); setHovered(false); }, ...rest, children: [/* @__PURE__ */ jsxs("div", { className: styles.viewport, style: adaptiveHeight && height !== void 0 ? { height } : void 0, onPointerCancel: () => { swipeStartXRef.current = null; }, onPointerDown: (event) => { if (event.pointerType !== "mouse") swipeStartXRef.current = event.clientX; }, onPointerUp: (event) => { if (swipeStartXRef.current === null) return; const delta = event.clientX - swipeStartXRef.current; swipeStartXRef.current = null; if (Math.abs(delta) < SWIPE_THRESHOLD) return; if (delta < 0) next(); else prev(); }, children: [/* @__PURE__ */ jsx("div", { "aria-live": interval && !paused ? "off" : "polite", className: cx(styles.track, adaptiveHeight && styles.trackAdaptive), style: { transform: `translateX(-${current * 100}%)` }, children: slides.map((slide, slideIndex) => { const active = slideIndex === current; return /* @__PURE__ */ jsx("div", { "aria-hidden": !active || void 0, "aria-label": `${slideIndex + 1} of ${count}`, "aria-roledescription": "slide", className: styles.slide, inert: !active, role: "group", ref: (node) => { slidesRef.current[slideIndex] = node; }, children: slide }, keys[slideIndex]); }) }), arrows && count > 1 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ActionIcon, { "aria-label": "Previous slide", className: styles.arrow, disabled: !loop && current === 0, icon: ChevronLeft, size: ARROW_SIZE, style: { insetInlineStart: 8 }, onClick: prev }), /* @__PURE__ */ jsx(ActionIcon, { "aria-label": "Next slide", className: styles.arrow, disabled: atEnd, icon: ChevronRight, size: ARROW_SIZE, style: { insetInlineEnd: 8 }, onClick: next })] })] }), dots && count > 1 && /* @__PURE__ */ jsx("div", { className: styles.dots, children: slides.map((_, dotIndex) => /* @__PURE__ */ jsx("button", { "aria-current": dotIndex === current || void 0, "aria-label": `Go to slide ${dotIndex + 1}`, className: styles.dot, type: "button", onClick: () => goTo(dotIndex) }, keys[dotIndex])) })] }); }); Carousel.displayName = "Carousel"; //#endregion export { Carousel as default }; //# sourceMappingURL=Carousel.mjs.map