<div id="app"></div>
#app {
  font-family: 'Roboto', sans-serif;
}

.slider-container {
  max-width: 900px;
  height: 500px;
  position: relative;
  overflow: hidden;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2)
}

.slide {
  width: 100%;
  height: 100%;
  position: absolute;
  opacity: 0;
}

.active-anim {
  opacity: 1;
  transition: opacity ease-in-out 0.8s;
}

.active-anim.fade-in {
  animation: fade-in 0.8s ease-in-out;
}

.active-anim.slide-left {
  animation: slide-left 0.8s ease-in-out;
}

.active-anim.slide-right {
  animation: slide-right 0.8s ease-in-out;
}

@keyframes fade-in {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

@keyframes slide-left {
  0% {
    transform: translateX(-100%);
  }

  100% {
    transform: translateX(0);
  }
}

@keyframes slide-right {
  0% {
    transform: translateX(100%);
  }

  100% {
    transform: translateX(0);
  }
}

.slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.active-anim {
  opacity: 1;
}

.slide-button {
  width: 60px;
  height: 60px;
  background: rgba(255, 255, 255, 0.2);
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
}

.next {
  top: 50%;
  right: 20px;
  transform: translateY(-60%);
}

.prev {
  top: 50%;
  left: 20px;
  transform: translateY(-60%);
}

.directional-arrow {
  height: 30px;
  width: 30px;
}

.dots-container {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  cursor: pointer;
}

.dot {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.3);
  margin: 0 5px;
  background: rgba(255, 255, 255, 0.2);
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);  
}

.dot-active {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.3);
  margin: 0 5px;
  background: #f1f1f1;
}

.slider-options {
  max-width: 900px;
  display: flex;
  justify-content: space-evenly;
}

.selector-container {
  margin-top: 10px;
}

label {
  font-size: 1em;  
}

select {
  font-size: 1em;  
  margin: 0 0 0 4px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.2);
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);  
}

select:hover {
  background-color: #eee;
  cursor: pointer;
}
/*
* https://frontendeval.com/questions/image-carousel
*
* Build an auto-playing image carousel
*/

const { useState, useEffect, useRef } = React;
import { FaChevronLeft, FaChevronRight } from "https://esm.sh/react-icons/fa6";

const SliderButton = ({ direction, moveSlide }) => (
  <button 
    className={
      direction === "next"
        ? "slide-button next"
        : "slide-button prev"
    }
    onClick={moveSlide}
  >
   {direction === "next" ? (
      <FaChevronRight className="directional-arrow" alt="Right Arrow" />
    ) : (
      <FaChevronLeft className="directional-arrow" alt="Left Arrow" />
    )} 
  </button>
)

const Slider = ({ queryParam, transitionTheme }) => {
  const [slideIndex, setSlideIndex] = useState(1);
  const [carouselImages, setCarouselImages] = useState([]);
  const interval = useRef(null);
    
  const getImagesForCarousel = async () => {
    let url = `https://www.reddit.com/r/aww/top/.json?t=${queryParam}`;
    await fetch(url)
      .then((res) => {
        if (res.ok) return res.json();
        throw new Error("Network response was not ok.");
    })
      .then((res) => {
        let imageUrls = res.data.children
          .filter((img) => img.data.url_overridden_by_dest.includes("jpg"))
          .map((img) => ({ url: img.data.url_overridden_by_dest, title: img.data.title }));
        
        setCarouselImages(imageUrls);
    })
      .catch((err) => console.error(err));
  };
  
  useEffect(() => {
    getImagesForCarousel();
  }, [queryParam, transitionTheme]);
  
  const nextSlide = () => {
    if (slideIndex !== carouselImages.length) {
      setSlideIndex(slideIndex + 1);
    } else if (slideIndex === carouselImages.length) {
      setSlideIndex(1);
    }
    
    clearTimeout(interval.current);
  };
  
  const prevSlide = () => {
    if (slideIndex !== 1) {
      setSlideIndex(slideIndex - 1);
    } else if (slideIndex === 1) {
      setSlideIndex(carouselImages.length);
    }
    
    clearTimeout(interval.current);
  };
  
  const moveDot = (index) => {
    setSlideIndex(index);
  };
  
  const kebabCasedString = (stringToKebab) => (
    stringToKebab.split(" ").join("-")
  )
  const cssFormattedTheme = kebabCasedString(transitionTheme)

  setTimeout(nextSlide, 3000);
  
  return (
    <div className="slider-container">
      {carouselImages.map((image, index) => (
        <div
          key={image.url}
          className={
            slideIndex === index + 1
              ? `slide active-anim ${cssFormattedTheme}`
              : "slide"
          }
          >
            <img src={image.url} alt={image.title} />
          </div>
      ))}
      <SliderButton dirction="prev" moveSlide={prevSlide} />
      <SliderButton direction="next" moveSlide={nextSlide} />
      <div className="dots-container">
        {Array.from({ length: carouselImages.length }).map((dot, index) => (
          <div
            key={String(index)}
            className={
              slideIndex === index + 1 ? "dot-active" : "dot"
            }
            onClick={() => moveDot(index + 1)}/>
        ))}
      </div>
    </div>
  )
}

const TopImagesSelector = ({ selectTopImage }) => {
  const TOP_IMAGE_OPTIONS = ["all", "week", "month", "year"]
  
  const capitalizeString = (string) => (
    string.charAt(0).toUpperCase() + string.slice(1)
  )
  
  return (
    <div className="selector-container">
      <label htmlFor="top-images">Top Images</label>
        <select id="top-images" onChange={selectTopImage}>
          {TOP_IMAGE_OPTIONS.map((option) => (
            <option value={option}>{capitalizeString(option)}</option>
          ))}
        </select>
    </div>
  )
}

const TransitionSelect = ({ setTheme }) => {
  const THEME_OPTIONS = ["fade in", "slide left", "slide right"]
   
  return (
    <div className="selector-container">
      <label htmlFor="theme-select">Transition Theme</label>
        <select id="theme-select" onChange={setTheme}>
          {THEME_OPTIONS.map((option) => (
            <option value={option}>{option}</option>
          ))}
        </select>
    </div>
  )
}

const App = () => {
  const [topImage, setTopImage] = useState("all");
  const [transitionTheme, setTransitionTheme] = useState("fade in")
  
  const selectTopImage = (event) => {
    setTopImage(event.target.value);
  }
  
  const setTheme = (event) => {
    setTransitionTheme(event.target.value);
  }
    
  return (
    <>
      <Slider queryParam={topImage} transitionTheme={transitionTheme} />
       <div className="slider-options">
        <TopImagesSelector selectTopImage={selectTopImage} />
        <TransitionSelect setTheme={setTheme} />        
      </div>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById('app'));
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js