Files
web/packages/design-system/lib/components/Lightbox/FullView/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

150 lines
3.7 KiB
TypeScript

'use client'
import { AnimatePresence, motion } from 'motion/react'
import { useEffect, useState } from 'react'
import { useIntl } from 'react-intl'
import Image from '../../Image'
import { IconButton } from '../../IconButton'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography'
import styles from './fullView.module.css'
import { LightboxImage } from '../index'
type FullViewProps = {
image: LightboxImage
onClose: () => void
onNext: () => void
onPrev: () => void
currentIndex: number
totalImages: number
hideLabel?: boolean
}
export default function FullView({
image,
onClose,
onNext,
onPrev,
currentIndex,
totalImages,
hideLabel,
}: FullViewProps) {
const intl = useIntl()
const [animateLeft, setAnimateLeft] = useState(true)
function handleSwipe(offset: number) {
if (offset > 30) onPrev()
if (offset < -30) onNext()
}
function handleNext() {
setAnimateLeft(true)
onNext()
}
function handlePrev() {
setAnimateLeft(false)
onPrev()
}
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'ArrowLeft') {
handlePrev()
} else if (e.key === 'ArrowRight') {
handleNext()
}
}
useEffect(() => {
window.addEventListener('keydown', handleKeyDown)
return () => {
window.removeEventListener('keydown', handleKeyDown)
}
})
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.fullViewContainer}>
<IconButton
theme="Inverted"
style="Muted"
className={styles.closeButton}
onPress={onClose}
aria-label={intl.formatMessage({
defaultMessage: 'Close',
})}
>
<MaterialIcon icon="close" color="CurrentColor" size={24} />
</IconButton>
<div className={styles.header}>
<Typography variant="Tag/sm">
<span className={styles.imageCount}>
{`${currentIndex + 1} / ${totalImages}`}
</span>
</Typography>
</div>
<div className={styles.imageContainer}>
<AnimatePresence initial={false} custom={animateLeft}>
<motion.div
key={image.src}
custom={animateLeft}
variants={variants}
initial="initial"
animate="animate"
exit="exit"
transition={{ duration: 0.3 }}
className={styles.imageWrapper}
drag="x"
onDragEnd={(_e, info) => handleSwipe(info.offset.x)}
>
{image.caption && !hideLabel ? (
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.imageCaption}>{image.caption}</p>
</Typography>
) : null}
<Image
alt={image.alt}
fill
sizes="(min-width: 1500px) 1500px, 100vw"
src={image.src}
className={styles.image}
/>
</motion.div>
</AnimatePresence>
</div>
<motion.button
className={`${styles.navigationButton} ${styles.fullViewPrevButton}`}
onClick={handlePrev}
>
<MaterialIcon
icon="arrow_back"
color="CurrentColor"
className={styles.leftTransformIcon}
/>
</motion.button>
<motion.button
className={`${styles.navigationButton} ${styles.fullViewNextButton}`}
onClick={handleNext}
>
<MaterialIcon icon="arrow_forward" color="CurrentColor" />
</motion.button>
</div>
)
}