104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
import { createPortal } from 'react-dom'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { FaChevronLeft, FaChevronRight, FaTimes } from 'react-icons/fa'
|
|
import { Button } from '@/components/ui'
|
|
import type { MouseEvent } from 'react'
|
|
|
|
interface ImageLightboxProps {
|
|
images: string[]
|
|
activeIndex: number
|
|
isOpen: boolean
|
|
altPrefix: string
|
|
getSrc: (img: string) => string
|
|
onClose: () => void
|
|
onChangeIndex: (index: number) => void
|
|
}
|
|
|
|
const ImageLightbox = ({
|
|
images,
|
|
activeIndex,
|
|
isOpen,
|
|
altPrefix,
|
|
getSrc,
|
|
onClose,
|
|
onChangeIndex,
|
|
}: ImageLightboxProps) => {
|
|
const changeBy = (delta: number) => (event: MouseEvent) => {
|
|
event.stopPropagation()
|
|
onChangeIndex((activeIndex + delta + images.length) % images.length)
|
|
}
|
|
|
|
return createPortal(
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<>
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 bg-black/95 z-[1100]"
|
|
onClick={onClose}
|
|
/>
|
|
<div className="fixed inset-0 z-[1110] flex items-center justify-center">
|
|
<Button
|
|
onClick={onClose}
|
|
variant="plain"
|
|
shape="circle"
|
|
icon={<FaTimes className="h-8 w-8" />}
|
|
className="absolute top-4 right-4 z-10 !h-12 !w-12 !px-0 text-white transition-colors hover:text-gray-300"
|
|
/>
|
|
{images.length > 1 && (
|
|
<>
|
|
<Button
|
|
onClick={changeBy(-1)}
|
|
variant="plain"
|
|
shape="circle"
|
|
icon={<FaChevronLeft className="h-6 w-6" />}
|
|
className="absolute left-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white transition-colors hover:!bg-black/70"
|
|
/>
|
|
<Button
|
|
onClick={changeBy(1)}
|
|
variant="plain"
|
|
shape="circle"
|
|
icon={<FaChevronRight className="h-6 w-6" />}
|
|
className="absolute right-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white transition-colors hover:!bg-black/70"
|
|
/>
|
|
</>
|
|
)}
|
|
<motion.img
|
|
key={activeIndex}
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
src={getSrc(images[activeIndex])}
|
|
alt={`${altPrefix} ${activeIndex + 1}`}
|
|
className="max-w-[90vw] max-h-[90vh] object-contain rounded-lg"
|
|
onClick={(event) => event.stopPropagation()}
|
|
/>
|
|
{images.length > 1 && (
|
|
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
|
|
{images.map((_, index) => (
|
|
<Button
|
|
key={index}
|
|
onClick={(event) => {
|
|
event.stopPropagation()
|
|
onChangeIndex(index)
|
|
}}
|
|
variant="plain"
|
|
shape="circle"
|
|
className={`!h-2.5 !w-2.5 !min-w-2.5 !px-0 transition-all ${
|
|
index === activeIndex ? 'bg-white' : 'bg-white/40 hover:bg-white/70'
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>,
|
|
document.body,
|
|
)
|
|
}
|
|
|
|
export default ImageLightbox
|