Files
web/packages/design-system/lib/components/Lightbox/index.tsx
Anton Gunnarsson 29292fd157 Merged in feat/sw-3231-move-lightbox-to-design-system (pull request #2619)
feat(SW-3231): Move Lightbox to design-system

* Move Lightbox to design-system

* Fix self-referencing imports

* Fix broken import


Approved-by: Matilda Landström
2025-08-13 11:02:59 +00:00

119 lines
3.2 KiB
TypeScript

'use client'
import { AnimatePresence, motion } from 'motion/react'
import { useEffect, useState } from 'react'
import { Dialog, Modal, ModalOverlay } from 'react-aria-components'
import FullView from './FullView'
import Gallery from './Gallery'
import styles from './lightbox.module.css'
export type LightboxImage = {
src: string
alt: string
caption?: string | null
smallSrc?: string | null
}
type LightboxProps = {
images: LightboxImage[]
dialogTitle: string /* Accessible title for dialog screen readers */
onClose: () => void
activeIndex?: number
hideLabel?: boolean
}
export default function Lightbox({
images,
dialogTitle,
onClose,
activeIndex = 0,
hideLabel,
}: LightboxProps) {
const [selectedImageIndex, setSelectedImageIndex] = useState(activeIndex)
const [isFullView, setIsFullView] = useState(false)
useEffect(() => {
setSelectedImageIndex(activeIndex)
}, [activeIndex])
function handleClose() {
setSelectedImageIndex(0)
onClose()
}
function handleNext() {
setSelectedImageIndex((prevIndex) => (prevIndex + 1) % images.length)
}
function handlePrev() {
setSelectedImageIndex(
(prevIndex) => (prevIndex - 1 + images.length) % images.length
)
}
useEffect(() => {
function handlePopState() {
handleClose()
}
window.history.pushState(null, '', window.location.href)
window.addEventListener('popstate', handlePopState)
return () => {
window.removeEventListener('popstate', handlePopState)
}
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [])
return (
<ModalOverlay
isOpen={true}
onOpenChange={handleClose}
className={styles.overlay}
isDismissable
>
<Modal>
<AnimatePresence>
<Dialog aria-label={dialogTitle}>
<motion.div
className={`${styles.content} ${
isFullView ? styles.fullViewContent : styles.galleryContent
}`}
initial={{ opacity: 0, scale: 0.95, x: '-50%', y: '-50%' }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
>
{isFullView ? (
<FullView
image={images[selectedImageIndex]}
onClose={() => setIsFullView(false)}
onNext={handleNext}
onPrev={handlePrev}
currentIndex={selectedImageIndex}
totalImages={images.length}
hideLabel={hideLabel}
/>
) : (
<Gallery
images={images}
onClose={handleClose}
onSelectImage={(image) => {
setSelectedImageIndex(
images.findIndex((img) => img === image)
)
}}
onImageClick={() => setIsFullView(true)}
selectedImage={images[selectedImageIndex]}
hideLabel={hideLabel}
/>
)}
</motion.div>
</Dialog>
</AnimatePresence>
</Modal>
</ModalOverlay>
)
}