<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
This Pen doesn't use any external CSS resources.