Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
at main 114 lines 3.6 kB view raw
1import { Dialog, DialogPanel } from "@headlessui/react"; 2import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/solid"; 3import { memo, useEffect, useMemo, useState } from "react"; 4import { useHotkeys } from "react-hotkeys-hook"; 5import { Spinner } from "@/components/Shared/UI"; 6import cn from "@/helpers/cn"; 7 8interface LightBoxProps { 9 show: boolean; 10 onClose: () => void; 11 images: string[]; 12 initialIndex?: number; 13} 14 15const LightBox = ({ 16 show, 17 onClose, 18 images, 19 initialIndex = 0 20}: LightBoxProps) => { 21 const [currentIndex, setCurrentIndex] = useState(initialIndex); 22 const [isLoading, setIsLoading] = useState(true); 23 24 const currentImage = useMemo( 25 () => images[currentIndex], 26 [images, currentIndex] 27 ); 28 29 useEffect(() => { 30 if (show) { 31 setCurrentIndex(initialIndex); 32 setIsLoading(true); 33 } 34 }, [show, initialIndex]); 35 36 const handleNext = () => { 37 setCurrentIndex((prev) => { 38 const next = Math.min(prev + 1, images.length - 1); 39 if (next !== prev) setIsLoading(true); 40 return next; 41 }); 42 }; 43 44 const handlePrevious = () => { 45 setCurrentIndex((prev) => { 46 const prevIndex = Math.max(prev - 1, 0); 47 if (prevIndex !== prev) setIsLoading(true); 48 return prevIndex; 49 }); 50 }; 51 52 useHotkeys("escape", onClose, { enabled: show }); 53 useHotkeys("arrowright", handleNext, { enabled: show }); 54 useHotkeys("arrowleft", handlePrevious, { enabled: show }); 55 56 return ( 57 <Dialog className="relative z-50" onClose={onClose} open={show}> 58 <div 59 aria-hidden="true" 60 className="fixed inset-0 bg-gray-500/75 backdrop-blur-sm dark:bg-gray-900/80" 61 /> 62 <div className="fixed inset-0 flex items-center justify-center"> 63 <DialogPanel> 64 {isLoading && ( 65 <div className="absolute inset-0 flex items-center justify-center"> 66 <Spinner className="text-white" size="md" /> 67 </div> 68 )} 69 {images.length > 1 && ( 70 <> 71 <button 72 className={cn( 73 "fixed top-1/2 left-4 rounded-full bg-black/50 p-2 text-white md:left-6 md:p-3", 74 { "cursor-not-allowed opacity-50": currentIndex === 0 } 75 )} 76 disabled={currentIndex === 0} 77 onClick={handlePrevious} 78 type="button" 79 > 80 <ArrowLeftIcon className="size-6" /> 81 </button> 82 <button 83 className={cn( 84 "fixed top-1/2 right-4 rounded-full bg-black/50 p-2 text-white md:right-6 md:p-3", 85 { 86 "cursor-not-allowed opacity-50": 87 currentIndex === images.length - 1 88 } 89 )} 90 disabled={currentIndex === images.length - 1} 91 onClick={handleNext} 92 type="button" 93 > 94 <ArrowRightIcon className="size-6" /> 95 </button> 96 </> 97 )} 98 <img 99 alt={`${currentIndex + 1} of ${images.length}`} 100 className="max-h-[90vh] w-auto max-w-full cursor-zoom-in touch-manipulation select-none object-contain" 101 draggable={false} 102 loading="lazy" 103 onClick={() => window.open(currentImage, "_blank", "noopener")} 104 onError={() => setIsLoading(false)} 105 onLoad={() => setIsLoading(false)} 106 src={currentImage} 107 /> 108 </DialogPanel> 109 </div> 110 </Dialog> 111 ); 112}; 113 114export default memo(LightBox);