src/components/newsletter-swiper/index.tsx (108 lines of code) (raw):

import { Pagination } from 'swiper'; import React, { useCallback, useRef, useState } from 'react'; import usePhone from '../../hooks/use-phone'; import { Swiper, SwiperClass, SwiperSlide } from 'swiper/react'; import { NEWSLETTER_DATA } from '../../constant/newsletter.data'; import ReadMore from '../../components/ReadMore'; import { useAnimationFrame } from '../../hooks/use-animation-frame'; import './index.scss'; export function NewsLetterSwiper() { const { isPhone } = usePhone(); const [swiperRef, setSwiperRef] = useState<SwiperClass>(); const [progressCount, setProgressCount] = useState<number>(0); const [stop, setStop] = useState<boolean>(false); // Using ref to track progress internally const progressRef = useRef(progressCount); const handlePrevious = useCallback(() => { swiperRef?.slidePrev(); }, [swiperRef]); const handleNext = useCallback(() => { swiperRef?.slideNext(); }, [swiperRef]); useAnimationFrame(deltaTime => { if (!stop) { const newProgress = progressRef.current + deltaTime * 0.02; if (newProgress >= 100) { setProgressCount(0); // Reset state after reaching 100% progressRef.current = 0; setTimeout(handleNext, 0); // Delay to avoid direct state change within render } else { progressRef.current = newProgress; setProgressCount(newProgress); // Update UI state } } }, stop); const pagination = { clickable: true, renderBullet: function (index, className) { return '<span class="' + className + '"></span>'; }, }; return ( <div className="container pt-14" onMouseMove={() => setStop(true)} onMouseLeave={() => setStop(false)}> <div style={{ position: 'relative', '--progress-count': `${progressCount}%` } as any}> {!isPhone && ( <div onClick={handlePrevious} className="swiper-button-prev invisible group-hover:visible" style={{ position: 'absolute', top: 'calc(50% - 2rem)', left: '-3rem', zIndex: 99 }} ></div> )} <Swiper pagination={pagination} spaceBetween={50} slidesPerView={1} navigation={false} modules={[Pagination]} loop={true} className="firstPageSwiper" // style={{ minHeight: 480 }} onSlideChange={() => { progressRef.current = 0; setProgressCount(0); }} onSwiper={setSwiperRef} > {NEWSLETTER_DATA.map((newsletter, index) => { return ( <SwiperSlide key={`${newsletter.title}-${index}`}> <div className=" row flex justify-center xl:justify-start flex-start pb-8 lg:pb-16"> <div className="w-full lg:w-auto flex justify-center ml-4"> <img width={424} src={`${require(`@site/static/images/${newsletter.image}`).default}`} alt={newsletter.title} /> </div> <div className="lg:w-[48rem] px-6 lg:px-0 lg:ml-12 mt-4 lg:mt-0 flex flex-col "> <div className="flex gap-1 mb-1"> {newsletter.tags.map((value, idx) => ( <div key={'tag-' + idx} className="color-[#4c576c] font-medium text-xs leading-5" > {value} </div> ))} </div> <h3 className="leading-[38px] text-2xl font-semibold line-clamp-1 "> {newsletter.title} </h3> <p className="pt-3 line-clamp-2 text-lg leading-8">{newsletter.content}</p> <ReadMore to={newsletter.to} className="pt-6" /> </div> </div> </SwiperSlide> ); })} </Swiper> {!isPhone && ( <div onClick={handleNext} className="swiper-button-next invisible group-hover:visible" style={{ position: 'absolute', top: 'calc(50% - 2rem)', right: '-3rem', zIndex: 99 }} ></div> )} </div> </div> ); }