Browse Source
🛠️ Fixed a bug in GallerySlider component
🛠️ Fixed a bug in GallerySlider component
🚧 Work in progress on GallerySlider 📸 Added new images to the gallery ✨ Implemented swipe functionality 🎨 Improved styling in GallerySlider componentmain
John Doe
1 year ago
1 changed files with 146 additions and 0 deletions
@ -0,0 +1,146 @@ |
|||
"use client"; |
|||
|
|||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; |
|||
import { AnimatePresence, motion, MotionConfig } from "framer-motion"; |
|||
import Image, { StaticImageData } from "next/image"; |
|||
import { useState } from "react"; |
|||
import { useSwipeable } from "react-swipeable"; |
|||
import { variants } from "@/utils/animationVariants"; |
|||
import Link from "next/link"; |
|||
import { Route } from "@/routers/types"; |
|||
|
|||
export interface GallerySliderProps { |
|||
className?: string; |
|||
galleryImgs: (StaticImageData | string)[]; |
|||
ratioClass?: string; |
|||
uniqueID: string; |
|||
href?: Route<string>; |
|||
imageClass?: string; |
|||
galleryClass?: string; |
|||
navigation?: boolean; |
|||
} |
|||
|
|||
export default function GallerySlider({ |
|||
className = "", |
|||
galleryImgs, |
|||
ratioClass = "aspect-w-4 aspect-h-3", |
|||
imageClass = "", |
|||
uniqueID = "uniqueID", |
|||
galleryClass = "rounded-xl", |
|||
href = "/listing-stay-detail", |
|||
navigation = true, |
|||
}: GallerySliderProps) { |
|||
const [loaded, setLoaded] = useState(false); |
|||
const [index, setIndex] = useState(0); |
|||
const [direction, setDirection] = useState(0); |
|||
const images = galleryImgs; |
|||
|
|||
function changePhotoId(newVal: number) { |
|||
if (newVal > index) { |
|||
setDirection(1); |
|||
} else { |
|||
setDirection(-1); |
|||
} |
|||
setIndex(newVal); |
|||
} |
|||
|
|||
const handlers = useSwipeable({ |
|||
onSwipedLeft: () => { |
|||
if (index < images?.length - 1) { |
|||
changePhotoId(index + 1); |
|||
} |
|||
}, |
|||
onSwipedRight: () => { |
|||
if (index > 0) { |
|||
changePhotoId(index - 1); |
|||
} |
|||
}, |
|||
trackMouse: true, |
|||
}); |
|||
|
|||
let currentImage = images[index]; |
|||
|
|||
return ( |
|||
<MotionConfig |
|||
transition={{ |
|||
x: { type: "spring", stiffness: 300, damping: 30 }, |
|||
opacity: { duration: 0.2 }, |
|||
}} |
|||
> |
|||
<div |
|||
className={`relative group group/cardGallerySlider ${className}`} |
|||
{...handlers} |
|||
> |
|||
{/* Main image */} |
|||
<div className={`w-full overflow-hidden ${galleryClass}`}> |
|||
<Link |
|||
href={href} |
|||
className={`relative flex items-center justify-center ${ratioClass}`} |
|||
> |
|||
<AnimatePresence initial={false} custom={direction}> |
|||
<motion.div |
|||
key={index} |
|||
custom={direction} |
|||
variants={variants(340, 1)} |
|||
initial="enter" |
|||
animate="center" |
|||
exit="exit" |
|||
className="absolute inset-0" |
|||
> |
|||
<Image |
|||
src={currentImage || ""} |
|||
fill |
|||
alt="listing card gallery" |
|||
className={`object-cover ${imageClass}`} |
|||
onLoadingComplete={() => setLoaded(true)} |
|||
sizes="(max-width: 1025px) 100vw, 300px" |
|||
/> |
|||
</motion.div> |
|||
</AnimatePresence> |
|||
</Link> |
|||
</div> |
|||
|
|||
{/* Buttons + bottom nav bar */} |
|||
<> |
|||
{/* Buttons */} |
|||
{loaded && navigation && ( |
|||
<div className="opacity-0 group-hover/cardGallerySlider:opacity-100 transition-opacity "> |
|||
{index > 0 && ( |
|||
<button |
|||
className="absolute w-8 h-8 left-3 top-[calc(50%-16px)] bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-6000 dark:hover:border-neutral-500 rounded-full flex items-center justify-center hover:border-neutral-300 focus:outline-none" |
|||
style={{ transform: "translate3d(0, 0, 0)" }} |
|||
onClick={() => changePhotoId(index - 1)} |
|||
> |
|||
<ChevronLeftIcon className="h-4 w-4" /> |
|||
</button> |
|||
)} |
|||
{index + 1 < images.length && ( |
|||
<button |
|||
className="absolute w-8 h-8 right-3 top-[calc(50%-16px)] bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-6000 dark:hover:border-neutral-500 rounded-full flex items-center justify-center hover:border-neutral-300 focus:outline-none" |
|||
style={{ transform: "translate3d(0, 0, 0)" }} |
|||
onClick={() => changePhotoId(index + 1)} |
|||
> |
|||
<ChevronRightIcon className="h-4 w-4" /> |
|||
</button> |
|||
)} |
|||
</div> |
|||
)} |
|||
|
|||
{/* Bottom Nav bar */} |
|||
<div className="absolute bottom-0 inset-x-0 h-10 bg-gradient-to-t from-neutral-900 opacity-50 rounded-b-lg"></div> |
|||
<div className="flex items-center justify-center absolute bottom-2 left-1/2 transform -translate-x-1/2 space-x-1.5"> |
|||
{images.map((_, i) => ( |
|||
<button |
|||
className={`w-1.5 h-1.5 rounded-full ${ |
|||
i === index ? "bg-white" : "bg-white/60 " |
|||
}`}
|
|||
onClick={() => changePhotoId(i)} |
|||
key={i} |
|||
/> |
|||
))} |
|||
</div> |
|||
</> |
|||
</div> |
|||
</MotionConfig> |
|||
); |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue