From e4258288514ba7c1fa4e5d212c71ca87b4259481 Mon Sep 17 00:00:00 2001 From: Aleksandar Ristic Date: Mon, 1 Apr 2024 15:27:56 +0200 Subject: [PATCH] feat: add new Slider component --- src/core/Slider.tsx | 178 ++++++++++++++--------------- src/core/Slider/Slider.stories.tsx | 86 ++++++++------ src/core/Slider/component.css | 12 +- src/core/Slider/component.js | 107 ----------------- 4 files changed, 148 insertions(+), 235 deletions(-) diff --git a/src/core/Slider.tsx b/src/core/Slider.tsx index 968a3f69..f75c77fe 100644 --- a/src/core/Slider.tsx +++ b/src/core/Slider.tsx @@ -1,110 +1,108 @@ -import React, { CSSProperties, ReactNode, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, ReactNode } from "react"; +import Icon from "../core/Icon.tsx"; +import "./component.css"; -import Icon from "./Icon"; -import SliderScripts from "./Slider/component.js"; -import "./Slider/component.css"; +interface SliderProps { + children: ReactNode[]; + interval?: number; +} -type SliderProps = { - slides?: ReactNode[]; - classes?: string; - slideClasses?: string; - slideMinWidth?: string; - slideMaxWidth?: string; - mqEnableThreshold?: () => boolean; +interface SliderIndicatorProps { + numSlides: number; + activeIndex: number; + interval: number; +} - container?: HTMLDivElement | null; +const SlideIndicator = ({ + numSlides, + activeIndex, + interval, +}: SliderIndicatorProps) => { + return ( +
+ {Array.from({ length: numSlides }, (_, i) => ( +
+ {i === activeIndex && ( +
+ )} +
+ ))} +
+ ); }; -const Slider = ({ - slides = [], - classes = "", - slideClasses = "", - slideMinWidth = "16.875rem", - slideMaxWidth = "1fr", - mqEnableThreshold = () => true, - ...props -}: SliderProps) => { - const containerRef = useRef(null); +const Slider = ({ children, interval = 15000 }: SliderProps) => { + const [activeIndex, setActiveIndex] = useState(0); + const timerRef = useRef(null); - useEffect(() => { - SliderScripts({ - container: containerRef.current, - mqEnableThreshold, - }); - }, []); + const next = () => { + setActiveIndex((prevIndex) => (prevIndex + 1) % children.length); + resetInterval(); + }; + + const prev = () => { + setActiveIndex((prevIndex) => + prevIndex > 0 ? prevIndex - 1 : children.length - 1 + ); + resetInterval(); + }; - if (slides.length === 0) return; + const resetInterval = () => { + if (timerRef.current) clearInterval(timerRef.current); + timerRef.current = setInterval(next, interval); + }; + + useEffect(() => { + resetInterval(); + return () => { + if (timerRef.current) clearInterval(timerRef.current); + }; + }, [children.length, interval]); return ( -
-
    - {slides.map((slide, i) => ( -
  1. - {slide} -
  2. - ))} -
+
+
+
+ {children.map((child, index) => ( +
+ {child} +
+ ))} +
+
-
+
- -
    - {slides.map((_, i) => ( -
  • - - ⬤ - {" "} - {/* ⬤ */} -
  • - ))} -
-
+ +
); }; diff --git a/src/core/Slider/Slider.stories.tsx b/src/core/Slider/Slider.stories.tsx index 34aeca1b..7975c7bc 100644 --- a/src/core/Slider/Slider.stories.tsx +++ b/src/core/Slider/Slider.stories.tsx @@ -1,45 +1,65 @@ -import React, { ReactNode } from "react"; -import Slider from "../Slider"; +import React from "react"; +import Slider from "../Slider.tsx"; +import Icon from "../Icon.tsx"; -export default { - title: "Components/Slider", - component: Slider, - parameters: { - layout: "fullscreen", - }, - tags: ["autodocs"], -}; +const Slide = ({ name }: { name: string }) => ( +
+
+
+

+ “Ably seamlessly absorbs sudden bursts in load during unexpected + client events. The integration was easy and we were live in under a + month.” +

+
+
+
+ {/* */} +
+
+

{name}

+

+ Co-Founder & Technical Leader +

+
+
+ +
+
+ {/* */} +

Mentimeter

+
+
+ + Read case study + + +
+
-const Slide = ({ children }: { children: ReactNode }) => ( -
-

{children}

+
+
); const slides = [ - - Powers live chat, updates, analytics, and composition for millions of users. - , - - Powers virtual venues for millions of event attendees around the world. - , - - Provides 5 million daily users with live financial commentary and stock - tickers. - , - Monitors live car performance data across the USA., + , + , + , + , ]; -export const SliderOnAllBreakpoints = { +export default { + title: "Components/Slider", + component: Slider, args: { - slides, + children: slides, + interval: 15000, }, }; -export const SliderOnSmallBreakpointOnly = { - args: { - slides, - classes: `sm:grid-cols-${slides.length / 2} md:grid-cols-${slides.length}`, - mqEnableThreshold: () => !window.matchMedia("(min-width: 48rem)").matches, - }, -}; +export const Default = {}; diff --git a/src/core/Slider/component.css b/src/core/Slider/component.css index 48ea9f48..79bed4c6 100644 --- a/src/core/Slider/component.css +++ b/src/core/Slider/component.css @@ -1,6 +1,8 @@ -.ui-slider-marker { - font-size: 0.5rem; - top: -1px; - - @apply leading-none px-4 relative; +@keyframes fillAnimation { + 0% { + width: 0%; + } + 100% { + width: 100%; + } } diff --git a/src/core/Slider/component.js b/src/core/Slider/component.js index 473eb5f2..e69de29b 100644 --- a/src/core/Slider/component.js +++ b/src/core/Slider/component.js @@ -1,107 +0,0 @@ -import "./component.css"; - -import throttle from "lodash.throttle"; - -import { queryId, queryIdAll } from "../dom-query"; - -const mdBreakpoint = () => window.matchMedia("(min-width: 48rem)").matches; -const DRAG_BUFFER = 20; - -const init = (slidesContainer) => { - const transformContainer = queryId("slider-strip", slidesContainer); - const slides = Array.from(queryIdAll("slider-slide", slidesContainer)); - const slideLeftChevron = queryId("slider-previous", slidesContainer); - const slideRightChevron = queryId("slider-next", slidesContainer); - const slideMarkers = Array.from(queryIdAll("slider-marker", slidesContainer)); - const sliderControls = queryId("slider-controls", slidesContainer); - - sliderControls.classList.replace("hidden", "flex"); - const slidesLength = slides.length; - - const slidesWidth = slidesContainer.getBoundingClientRect().width; - const { width: slideWidth, left: slideLeftDistance } = - slides[0].getBoundingClientRect(); - const { left: slideLeftDistanceSecond } = slides[1].getBoundingClientRect(); - const slideGap = slideLeftDistanceSecond - slideLeftDistance - slideWidth; - const adjustment = (slidesWidth - slideWidth) / 2; - - let currentIndex = 0; - let touchStartX = 0; - - const calculateTransform = (index) => - index * -slideWidth + adjustment + index * -slideGap; - - const updateSlide = (index) => - (transformContainer.style.transform = `translateX(${calculateTransform( - index - )}px)`); - - const updateMarkers = (index) => { - slideMarkers.forEach((marker) => - marker.classList.remove("text-active-orange") - ); - slideMarkers[index].classList.remove("text-cool-black"); - slideMarkers[index].classList.add("text-active-orange"); - }; - - const slideLeft = () => { - currentIndex = currentIndex - 1 <= 0 ? 0 : currentIndex - 1; - updateSlide(currentIndex); - updateMarkers(currentIndex); - }; - - const slideRight = () => { - currentIndex = - currentIndex + 1 >= slidesLength ? currentIndex : currentIndex + 1; - updateSlide(currentIndex); - updateMarkers(currentIndex); - }; - - updateSlide(0); - updateMarkers(0); - - slideLeftChevron.addEventListener("click", slideLeft); - - transformContainer.addEventListener("touchstart", (e) => { - touchStartX = e.touches[0]?.clientX; - }); - - transformContainer.addEventListener("touchend", (e) => { - const distance = e.changedTouches[0]?.clientX - touchStartX; - - // Prevent sliding on clicks - if (Math.abs(distance) < DRAG_BUFFER) return; - - const direction = distance > 0 ? slideLeft : slideRight; - direction(); - }); - - slideRightChevron.addEventListener("click", slideRight); - - return () => { - transformContainer.style.transform = null; - sliderControls.classList.replace("flex", "hidden"); - }; -}; - -const Slider = ({ container, mqEnableThreshold }) => { - if (!container) return; - - const breakpointCheck = mqEnableThreshold || (() => !mdBreakpoint()); - - let unmount = () => {}; - if (breakpointCheck()) unmount = init(container); - - window.addEventListener( - "resize", - throttle(() => { - if (breakpointCheck()) { - unmount = init(container); - } else { - unmount(); - } - }, 100) - ); -}; - -export default Slider;