wgp-next-shop-ui-lib
Version:
Библиотека UI-компонентов для Next-Shop с поддержкой e-commerce функций
156 lines (141 loc) • 4.98 kB
JSX
'use client';
import React, { useState, useEffect, useRef } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
const Slider = ({
slides = [],
autoplay = false,
autoplayInterval = 3000,
showArrows = true,
showDots = true,
infinite = true,
customStyles = {},
renderSlide = null,
onSlideChange = null,
className = ''
}) => {
const [currentSlide, setCurrentSlide] = useState(0);
const [isAutoplay, setIsAutoplay] = useState(autoplay);
const intervalRef = useRef(null);
// Автопроигрывание
useEffect(() => {
if (isAutoplay && slides.length > 1) {
intervalRef.current = setInterval(() => {
setCurrentSlide(prev =>
infinite ? (prev + 1) % slides.length : Math.min(prev + 1, slides.length - 1)
);
}, autoplayInterval);
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [isAutoplay, slides.length, autoplayInterval, infinite]);
// Обработчики навигации
const goToSlide = (index) => {
setCurrentSlide(index);
onSlideChange?.(index);
};
const nextSlide = () => {
const newIndex = infinite
? (currentSlide + 1) % slides.length
: Math.min(currentSlide + 1, slides.length - 1);
goToSlide(newIndex);
};
const prevSlide = () => {
const newIndex = infinite
? currentSlide === 0 ? slides.length - 1 : currentSlide - 1
: Math.max(currentSlide - 1, 0);
goToSlide(newIndex);
};
// Остановка автопроигрывания при наведении
const handleMouseEnter = () => setIsAutoplay(false);
const handleMouseLeave = () => setIsAutoplay(autoplay);
if (!slides.length) return null;
const defaultStyles = {
container: "relative w-full overflow-hidden rounded-lg bg-gray-100",
slideContainer: "flex transition-transform duration-500 ease-in-out",
slide: "w-full flex-shrink-0",
arrow: "absolute top-1/2 -translate-y-1/2 z-10 bg-white/80 hover:bg-white text-gray-800 rounded-full p-2 shadow-lg transition-all duration-200 hover:scale-110",
leftArrow: "left-4",
rightArrow: "right-4",
dotsContainer: "absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-10",
dot: "w-3 h-3 rounded-full transition-all duration-200 cursor-pointer",
dotActive: "bg-white shadow-lg",
dotInactive: "bg-white/50 hover:bg-white/75"
};
const styles = { ...defaultStyles, ...customStyles };
return (
<div
className={`${styles.container} ${className}`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{/* Слайды */}
<div
className={styles.slideContainer}
style={{
transform: `translateX(-${currentSlide * 100}%)`
}}
>
{slides.map((slide, index) => (
<div key={index} className={styles.slide}>
{renderSlide ? renderSlide(slide, index) : (
<div className="w-full h-full flex items-center justify-center">
{slide.image && (
<img
src={slide.image}
alt={slide.title || `Slide ${index + 1}`}
className="w-full h-full object-cover"
/>
)}
{slide.content && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center text-white">
{slide.title && <h2 className="text-2xl md:text-4xl font-bold mb-2">{slide.title}</h2>}
{slide.description && <p className="text-lg md:text-xl">{slide.description}</p>}
</div>
</div>
)}
</div>
)}
</div>
))}
</div>
{/* Стрелки навигации */}
{showArrows && slides.length > 1 && (
<>
<button
onClick={prevSlide}
className={`${styles.arrow} ${styles.leftArrow}`}
disabled={!infinite && currentSlide === 0}
>
<ChevronLeft size={20} />
</button>
<button
onClick={nextSlide}
className={`${styles.arrow} ${styles.rightArrow}`}
disabled={!infinite && currentSlide === slides.length - 1}
>
<ChevronRight size={20} />
</button>
</>
)}
{/* Точки индикации */}
{showDots && slides.length > 1 && (
<div className={styles.dotsContainer}>
{slides.map((_, index) => (
<button
key={index}
onClick={() => goToSlide(index)}
className={`${styles.dot} ${
index === currentSlide ? styles.dotActive : styles.dotInactive
}`}
/>
))}
</div>
)}
</div>
);
};
export default Slider;