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