Files
web/packages/design-system/lib/components/Lightbox/FullView/index.tsx
Erik Tiekstra 4ec1e85d84 Feat/BOOK-293 button adjustments
* feat(BOOK-293): Adjusted padding of the buttons to match Figma design
* feat(BOOK-293): Updated variants for IconButton
* feat(BOOK-113): Updated focus indicators on buttons and added default focus ring color
* feat(BOOK-293): Replaced buttons inside booking widget

Approved-by: Christel Westerberg
2025-12-15 07:05:31 +00:00

157 lines
3.9 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 { LightboxImage } from '../index'
import styles from './fullView.module.css'
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) {
setAnimateLeft(false)
onPrev()
}
if (offset < -30) {
setAnimateLeft(true)
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,
}),
} as const
return (
<div className={styles.fullView}>
<IconButton
variant="Muted"
className={styles.closeButton}
onPress={onClose}
aria-label={intl.formatMessage({
id: 'common.close',
defaultMessage: 'Close',
})}
>
<MaterialIcon icon="close" color="CurrentColor" size={24} />
</IconButton>
<div className={styles.header}>
<Typography variant="Tag/sm">
<span className={styles.imageCount}>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{`${currentIndex + 1} / ${totalImages}`}
</span>
</Typography>
</div>
<div className={styles.content}>
<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.motionContainer}
drag="x"
onDragEnd={(_e, info) => handleSwipe(info.offset.x)}
>
<div className={styles.imageWrapper}>
<Image
alt={image.alt}
fill
sizes="90vw"
src={image.src}
className={styles.image}
/>
{image.caption && !hideLabel ? (
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.caption}>{image.caption}</p>
</Typography>
) : null}
</div>
</motion.div>
</AnimatePresence>
</div>
<IconButton
variant="Muted"
className={`${styles.navigationButton} ${styles.prev}`}
onPress={handlePrev}
>
<MaterialIcon icon="arrow_back" color="CurrentColor" />
</IconButton>
<IconButton
variant="Muted"
className={`${styles.navigationButton} ${styles.next}`}
onPress={handleNext}
>
<MaterialIcon icon="arrow_forward" color="CurrentColor" />
</IconButton>
</div>
)
}