lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
90 lines • 8.64 kB
JavaScript
;
"use client";
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const framer_motion_1 = require("framer-motion");
const lucide_react_1 = require("lucide-react");
const utils_1 = require("../lib/utils");
// ─────────────────────────────────────────────────────────────────────────────
// Component
// ─────────────────────────────────────────────────────────────────────────────
const StylishCarousel = ({ items = [], initialIndex = 0, slideSize = "clamp(140px, 75vmin, 320px)", rotationDegrees = 28, inactiveScale = 0.62, yOffsetPercent = 48, springBounce = 0.15, springDuration = 0.8, showArrows = true, showDots = true, clickToNavigate = true, autoPlay = 0, className, onIndexChange, borderRadius = "1rem", arrowClassName, }) => {
const clampedInitial = Math.max(0, Math.min(initialIndex, items.length - 1));
const [activeIndex, setActiveIndex] = (0, react_1.useState)(clampedInitial);
const autoPlayRef = (0, react_1.useRef)(null);
const containerRef = (0, react_1.useRef)(null);
// ── helpers ──────────────────────────────────────────────────────────────
const goTo = (0, react_1.useCallback)((index) => {
const clamped = Math.max(0, Math.min(index, items.length - 1));
setActiveIndex(clamped);
onIndexChange?.(clamped);
}, [items.length, onIndexChange]);
const toPrev = (0, react_1.useCallback)(() => goTo(activeIndex - 1), [activeIndex, goTo]);
const toNext = (0, react_1.useCallback)(() => goTo(activeIndex + 1), [activeIndex, goTo]);
// ── keyboard navigation ───────────────────────────────────────────────────
(0, react_1.useEffect)(() => {
const handler = (e) => {
if (e.key === "ArrowLeft")
toPrev();
if (e.key === "ArrowRight")
toNext();
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [toPrev, toNext]);
// ── touch / swipe ─────────────────────────────────────────────────────────
const touchStartX = (0, react_1.useRef)(null);
const handleTouchStart = (e) => {
touchStartX.current = e.touches[0].clientX;
};
const handleTouchEnd = (e) => {
if (touchStartX.current === null)
return;
const delta = touchStartX.current - e.changedTouches[0].clientX;
if (Math.abs(delta) > 40)
delta > 0 ? toNext() : toPrev();
touchStartX.current = null;
};
// ── auto-play ─────────────────────────────────────────────────────────────
(0, react_1.useEffect)(() => {
if (!autoPlay)
return;
autoPlayRef.current = setInterval(() => {
setActiveIndex((prev) => {
const next = prev + 1 >= items.length ? 0 : prev + 1;
onIndexChange?.(next);
return next;
});
}, autoPlay);
return () => {
if (autoPlayRef.current)
clearInterval(autoPlayRef.current);
};
}, [autoPlay, items.length, onIndexChange]);
// ── spring transition ──────────────────────────────────────────────────────
const spring = {
type: "spring",
bounce: springBounce,
duration: springDuration,
};
if (!items.length)
return null;
return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, className: (0, utils_1.cn)("relative flex flex-col items-center select-none", className), onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd, "aria-label": "Stylish Carousel", role: "region", children: [(0, jsx_runtime_1.jsx)("div", { style: { width: slideSize, aspectRatio: "1 / 1" }, className: "relative mt-6", children: (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { className: "flex w-fit", animate: { x: `${(-activeIndex * 100) / items.length}%` }, transition: spring, children: items.map((item, i) => {
const offset = i - activeIndex;
const isActive = offset === 0;
return ((0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { style: { width: slideSize, aspectRatio: "1 / 1" }, className: "flex-shrink-0 flex flex-col items-center gap-2 will-change-transform", animate: {
rotate: offset * rotationDegrees,
scale: isActive ? 1 : inactiveScale,
y: `${offset * yOffsetPercent}%`,
}, transition: spring, children: [(0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: item.title && ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.span, { initial: { opacity: 0, y: -4 }, animate: isActive ? { opacity: 1, y: 0 } : { opacity: 0, y: -4 }, transition: { duration: 0.3 }, className: "text-xs sm:text-sm font-semibold whitespace-nowrap text-foreground/80 tracking-wide", children: item.title }, `title-${i}`)) }), (0, jsx_runtime_1.jsxs)("div", { className: "relative w-full h-full overflow-hidden shadow-2xl", style: { borderRadius }, children: [(0, jsx_runtime_1.jsx)("img", { src: item.src, alt: item.alt ?? item.title ?? `Slide ${i + 1}`, draggable: false, onClick: () => clickToNavigate && goTo(i), className: (0, utils_1.cn)("w-full h-full object-cover transition-[filter] duration-300 will-change-transform", !isActive && "brightness-75", clickToNavigate && !isActive && "cursor-pointer"), loading: "lazy" }), isActive && ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { layoutId: "glow-ring", className: "absolute inset-0 rounded-[inherit] pointer-events-none", style: {
boxShadow: "0 0 0 3px hsl(var(--primary, 221 83% 53%) / 0.7)",
borderRadius,
}, transition: spring }))] })] }, i));
}) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-8 flex items-center gap-4 px-3 py-2 rounded-full bg-background/60 border border-border/60 backdrop-blur-md shadow-lg", children: [showArrows && ((0, jsx_runtime_1.jsx)("button", { "aria-label": "Previous slide", onClick: toPrev, disabled: activeIndex === 0, className: (0, utils_1.cn)("p-2 rounded-full transition-all hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed", arrowClassName), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { className: "w-4 h-4 text-foreground/80" }) })), showDots && ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1.5", children: items.map((_, i) => ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.button, { "aria-label": `Go to slide ${i + 1}`, onClick: () => goTo(i), animate: {
width: activeIndex === i ? 28 : 8,
opacity: activeIndex === i ? 1 : 0.35,
}, transition: { type: "spring", bounce: 0.3, duration: 0.5 }, className: "h-2 rounded-full bg-foreground cursor-pointer", style: { minWidth: 8 } }, i))) })), showArrows && ((0, jsx_runtime_1.jsx)("button", { "aria-label": "Next slide", onClick: toNext, disabled: activeIndex === items.length - 1, className: (0, utils_1.cn)("p-2 rounded-full transition-all hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed", arrowClassName), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "w-4 h-4 text-foreground/80" }) }))] }), (0, jsx_runtime_1.jsxs)("p", { className: "mt-3 text-xs text-muted-foreground font-medium tabular-nums", children: [activeIndex + 1, " / ", items.length] })] }));
};
exports.default = StylishCarousel;
//# sourceMappingURL=stylish-carousel.js.map