Fix/sw-1127 image fixes for lightbox * fix(SW-1127): move back to top button behind lightbox * fix(SW-1127): don't loop lightbox thumbnails * fix(SW-1127): nicer animation in the gallery This both fixes a bug in the gallery where the animation in the carousel didn't work so good and also animates the images different directions depending on if the user go left or right. Approved-by: Matilda Landström
181 lines
5.6 KiB
TypeScript
181 lines
5.6 KiB
TypeScript
"use client"
|
|
import { AnimatePresence, motion } from "framer-motion"
|
|
import { useState } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { ChevronLeftIcon } from "@/components/Icons"
|
|
import ArrowRightIcon from "@/components/Icons/ArrowRight"
|
|
import CloseIcon from "@/components/Icons/Close"
|
|
import Image from "@/components/Image"
|
|
import Button from "@/components/TempDesignSystem/Button"
|
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
|
|
import styles from "./Lightbox.module.css"
|
|
|
|
import type { GalleryProps } from "@/types/components/lightbox/lightbox"
|
|
|
|
export default function Gallery({
|
|
images,
|
|
onClose,
|
|
onSelectImage,
|
|
onImageClick,
|
|
selectedImage,
|
|
}: GalleryProps) {
|
|
const intl = useIntl()
|
|
const [animateLeft, setAnimateLeft] = useState(true)
|
|
const mainImage = selectedImage || images[0]
|
|
const mainImageIndex = images.findIndex((img) => img === mainImage)
|
|
|
|
function getThumbImages() {
|
|
const thumbs = []
|
|
for (let i = 1; i <= Math.min(5, images.length); i++) {
|
|
const index = (mainImageIndex + i) % images.length
|
|
thumbs.push(images[index])
|
|
}
|
|
return thumbs
|
|
}
|
|
|
|
function handleNext() {
|
|
setAnimateLeft(true)
|
|
const nextIndex = (mainImageIndex + 1) % images.length
|
|
onSelectImage(images[nextIndex])
|
|
}
|
|
|
|
function handlePrev() {
|
|
setAnimateLeft(false)
|
|
const prevIndex = (mainImageIndex - 1 + images.length) % images.length
|
|
onSelectImage(images[prevIndex])
|
|
}
|
|
|
|
const variants = {
|
|
initial: (animateLeft: boolean) => ({
|
|
opacity: 0,
|
|
x: animateLeft ? 300 : -300,
|
|
}),
|
|
animate: { opacity: 1, x: 0 },
|
|
exit: (animateLeft: boolean) => ({
|
|
opacity: 0,
|
|
x: animateLeft ? -300 : 300,
|
|
}),
|
|
}
|
|
|
|
return (
|
|
<div className={styles.galleryContainer}>
|
|
<Button
|
|
intent="text"
|
|
size="small"
|
|
className={styles.closeButton}
|
|
onClick={onClose}
|
|
aria-label={intl.formatMessage({ id: "Close" })}
|
|
>
|
|
<ChevronLeftIcon
|
|
color="black"
|
|
width={32}
|
|
height={32}
|
|
className={styles.mobileCloseIcon}
|
|
/>
|
|
<CloseIcon width={32} height={32} className={styles.desktopCloseIcon} />
|
|
</Button>
|
|
{/* Desktop Gallery */}
|
|
<div className={styles.desktopGallery}>
|
|
<div className={styles.galleryHeader}>
|
|
{mainImage.metaData.title && (
|
|
<div className={styles.imageCaption}>
|
|
<Caption color="textMediumContrast">
|
|
{mainImage.metaData.title}
|
|
</Caption>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className={styles.mainImageWrapper}>
|
|
<AnimatePresence initial={false} custom={animateLeft}>
|
|
<motion.div
|
|
key={mainImage.imageSizes.medium}
|
|
className={styles.mainImageContainer}
|
|
custom={animateLeft}
|
|
variants={variants}
|
|
initial="initial"
|
|
animate="animate"
|
|
exit="exit"
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<Image
|
|
src={mainImage.imageSizes.medium}
|
|
alt={mainImage.metaData.altText}
|
|
fill
|
|
className={styles.image}
|
|
onClick={onImageClick}
|
|
/>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
<motion.button
|
|
className={`${styles.navigationButton} ${styles.galleryPrevButton}`}
|
|
onClick={handlePrev}
|
|
>
|
|
<ArrowRightIcon
|
|
color="burgundy"
|
|
className={styles.leftTransformIcon}
|
|
/>
|
|
</motion.button>
|
|
<motion.button
|
|
className={`${styles.navigationButton} ${styles.galleryNextButton}`}
|
|
onClick={handleNext}
|
|
>
|
|
<ArrowRightIcon color="burgundy" />
|
|
</motion.button>
|
|
</div>
|
|
<div className={styles.desktopThumbnailGrid}>
|
|
<AnimatePresence initial={false}>
|
|
{getThumbImages().map((image, index) => (
|
|
<motion.div
|
|
key={image.imageSizes.tiny}
|
|
className={styles.thumbnailContainer}
|
|
onClick={() => onSelectImage(image)}
|
|
initial={{ opacity: 0, x: 50 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -50 }}
|
|
transition={{ duration: 0.2, delay: index * 0.05 }}
|
|
>
|
|
<Image
|
|
src={image.imageSizes.tiny}
|
|
alt={image.metaData.altText}
|
|
fill
|
|
className={styles.image}
|
|
/>
|
|
</motion.div>
|
|
))}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile Gallery */}
|
|
<div className={styles.mobileGallery}>
|
|
<div className={styles.mobileGalleryContent}>
|
|
<div className={styles.thumbnailGrid}>
|
|
{images.map((image, index) => (
|
|
<motion.div
|
|
key={image.imageSizes.small}
|
|
className={`${styles.thumbnailContainer} ${index % 3 === 0 ? styles.fullWidthImage : ""}`}
|
|
onClick={() => {
|
|
onSelectImage(image)
|
|
onImageClick()
|
|
}}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: index * 0.05 }}
|
|
>
|
|
<Image
|
|
src={image.imageSizes.small}
|
|
alt={image.metaData.altText}
|
|
fill
|
|
className={styles.image}
|
|
/>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|