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
This commit is contained in:
118
packages/design-system/lib/components/Lightbox/index.tsx
Normal file
118
packages/design-system/lib/components/Lightbox/index.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user