diff --git a/src/components/carousel/carousel.css b/src/components/carousel/carousel.css new file mode 100644 index 0000000..ddc04f4 --- /dev/null +++ b/src/components/carousel/carousel.css @@ -0,0 +1,107 @@ +.carousel { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + border-radius: 15px; + position: relative; +} + +.carousel-wrapper { + overflow: hidden; + width: 100%; + position: relative; +} + +.carousel-slider { + display: flex; + gap: 25px; + position: relative; + padding-block: 1rem; + transition: translate 0.3s; +} + +.slide { + background: var(--white); + border-radius: 15px; + box-shadow: var(--shadow); +} +.slide-image { + display: block; + color: var(--darkGray); + font-size: 0.8rem; + transition: opacity 0.5s ease-in-out; + height: 15rem; + object-fit: contain; + border-radius: 15px; +} +.slide-content { + padding: 1rem; + border-bottom-left-radius: 15px; + border-bottom-right-radius: 15px; +} +.slide-title { + font-weight: 800; + font-size: 1rem; + margin-bottom: 0.5rem; +} +.slide-text { + font-size: 0.8rem; + color: var(--darkGray); + margin-bottom: 0.5rem; +} + +.carousel-button { + display: flex; + align-items: center; + justify-content: center; + background: var(--gray); + border: none; + width: 2em; + height: 2em; + font-size: 1em; + border-radius: 999px; + cursor: pointer; +} + +.carousel-image.fade-out { + opacity: 0; +} + +.carousel-image.fade-in { + opacity: 1; +} + +.carousel-buttons { + width: 100%; + display: flex; + justify-content: space-between; + transform: translateY(-50%); +} + +.carousel-buttons button { + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + padding: 1em; + cursor: pointer; +} + +.carousel-bar { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + width: 100%; +} +.carousel-dot { + width: 8px; + height: 8px; + border-radius: 999px; + background: var(--gray); + cursor: pointer; + border: none; + transition: width .3s, background .3s; + &.dot_active { width: 24px; } + &:hover { background: var(--darkGray); } +} \ No newline at end of file diff --git a/src/components/carousel/carousel.tsx b/src/components/carousel/carousel.tsx index 2c7ebe4..4d8a904 100644 --- a/src/components/carousel/carousel.tsx +++ b/src/components/carousel/carousel.tsx @@ -1,84 +1,92 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; + +import { NavArrowLeft, NavArrowRight } from "iconoir-react"; + +import "./carousel.css"; interface CarouselProps { - images: string[]; + carousel: CarouselImageProps[]; } -interface CarouselClickablesProps { - images: string[]; - links: string[]; +interface CarouselImageProps { + image: string; + title: string; + text: string; + link?: string; + alt?: string; } -export const Carousel: React.FC = ({ images }) => { - const [currentImage, setCurrentImage] = React.useState(0); +export const Carousel: React.FC = ({ carousel }) => { + const [currentIndex, setCurrentImage] = React.useState(0); + const [imagesSize, setImagesSize] = React.useState([]); + const carouselSlider = React.useRef(null); const nextImage = () => { - setCurrentImage((currentImage + 1) % images.length); + setCurrentImage((currentIndex + 1) % carousel.length); } const previousImage = () => { - setCurrentImage((currentImage - 1 + images.length) % images.length); + setCurrentImage((currentIndex - 1 + carousel.length) % carousel.length); + } + + React.useEffect(() => { + const interval = setTimeout(() => { + nextImage(); + }, 3000); + + if (carouselSlider.current) { + const translate = imagesSize.reduce((acc, size, index) => index < currentIndex ? acc + size : acc, 0); + const gap = 25; // TODO: Find a way to get this value (HARDCODED) + carouselSlider.current.style.translate = `-${translate+gap*currentIndex}px`; + } + + return () => clearInterval(interval); + }, [currentIndex, imagesSize, carouselSlider]); + + function handleImageLoad(i: number, event: React.SyntheticEvent) { + setImagesSize((old) => { + const newSizes = [...old]; + newSizes[i] = (event.target as HTMLImageElement).clientWidth; + return newSizes; + }); } return (
-
- {"carousel"}/ +
+
+ { + carousel.map((image, index) => ( +
+ {image.alt handleImageLoad(index, e)} + /> +
+

{image.title}

+

{image.text}

+
+
+ )) + } +
-
- - +
+ + { + carousel.map((_, index) => ( +
) -} - -export const CarouselClickables: React.FC = ({ images, links }) => { - const [currentIndex, setCurrentIndex] = useState(0); - const [fade, setFade] = useState("fade-in"); - - useEffect(() => { - const interval = setInterval(() => { - handleNextImage(); - }, 3000); - - return () => clearInterval(interval); - }, [images.length]); - - const handleNextImage = () => { - setFade("fade-out"); - setTimeout(() => { - setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length); - setFade("fade-in"); - }, 500); - }; - - const handlePreviousImage = () => { - setFade("fade-out"); - setTimeout(() => { - setCurrentIndex((prevIndex) => (prevIndex - 1 + images.length) % images.length); - setFade("fade-in"); - }, 500); - }; - - return ( -
-
- {images.map((image, index) => ( - - {`Slide - - ))} -
-
- - -
-
- ); }; \ No newline at end of file