Skip to content

Commit 35054c9

Browse files
committed
refactor: enhance Polaroid gallery with improved image viewer and styling adjustments
1 parent b018aa5 commit 35054c9

File tree

3 files changed

+276
-90
lines changed

3 files changed

+276
-90
lines changed

app/globals.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@
9393
}
9494

9595
@utility shadow-polaroid {
96-
@apply shadow-[0px_0px_0px_1px_rgba(0,0,0,0.06),0px_1px_1px_-0.5px_rgba(0,0,0,0.06),0px_3px_3px_-1.5px_rgba(0,0,0,0.06),_0px_6px_6px_-3px_rgba(0,0,0,0.06),0px_12px_12px_-6px_rgba(0,0,0,0.06),0px_24px_24px_-12px_rgba(0,0,0,0.06)];
96+
@apply shadow-[0px_0px_0px_1px_rgba(0,0,0,0.08),0px_2px_2px_-1px_rgba(0,0,0,0.1),0px_4px_4px_-2px_rgba(0,0,0,0.08),0px_8px_8px_-4px_rgba(0,0,0,0.06),0px_16px_16px_-8px_rgba(0,0,0,0.04),0px_32px_32px_-16px_rgba(0,0,0,0.02)];
97+
}
98+
99+
@utility shadow-polaroid-hover {
100+
@apply shadow-[0px_0px_0px_1px_rgba(0,0,0,0.12),0px_4px_4px_-2px_rgba(0,0,0,0.15),0px_8px_8px_-4px_rgba(0,0,0,0.12),0px_16px_16px_-8px_rgba(0,0,0,0.08),0px_32px_32px_-16px_rgba(0,0,0,0.06),0px_64px_64px_-32px_rgba(0,0,0,0.04)];
97101
}
98102

99103
@utility faded-border {

components/blocks/polaroid-gallery.tsx

Lines changed: 215 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
11
/* eslint-disable @next/next/no-img-element */
22
"use client";
33
import Polaroid, { type polaroidVariants } from "@/components/blocks/polaroid";
4-
import {
5-
Carousel,
6-
CarouselContent,
7-
CarouselItem,
8-
CarouselNext,
9-
CarouselPrevious,
10-
} from "@/components/ui/carousel";
11-
import {
12-
Dialog,
13-
DialogContent,
14-
DialogDescription,
15-
DialogHeader,
16-
DialogTitle,
17-
DialogTrigger,
18-
} from "@/components/ui/dialog";
19-
import { useInView } from "motion/react";
4+
import { AnimatePresence, motion, useInView } from "motion/react";
205
import Image from "next/image";
21-
import { useEffect, useRef, useState } from "react";
6+
import { useCallback, useEffect, useRef, useState } from "react";
227

238
type TImage = {
249
src: string;
@@ -37,64 +22,228 @@ const PolaroidGallery = ({
3722
const ref = useRef(null);
3823
const isInView = useInView(ref);
3924
const [isVisible, setIsVisible] = useState(false);
40-
const [startIndex, setStartIndex] = useState(0);
25+
const [currentIndex, setCurrentIndex] = useState(0);
26+
const [isViewerOpen, setIsViewerOpen] = useState(false);
4127

4228
useEffect(() => {
4329
if (isInView && !isVisible) {
4430
setIsVisible(true);
4531
}
4632
}, [isInView, isVisible]);
4733

34+
const openViewer = useCallback((index: number) => {
35+
setCurrentIndex(index);
36+
setIsViewerOpen(true);
37+
}, []);
38+
39+
const closeViewer = useCallback(() => {
40+
setIsViewerOpen(false);
41+
}, []);
42+
43+
const nextImage = useCallback(() => {
44+
setCurrentIndex((prev) => (prev + 1) % images.length);
45+
}, [images.length]);
46+
47+
const prevImage = useCallback(() => {
48+
setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
49+
}, [images.length]);
50+
51+
const handleKeyDown = useCallback(
52+
(e: KeyboardEvent) => {
53+
if (!isViewerOpen) return;
54+
55+
if (e.key === "Escape") closeViewer();
56+
if (e.key === "ArrowRight") nextImage();
57+
if (e.key === "ArrowLeft") prevImage();
58+
},
59+
[isViewerOpen, nextImage, prevImage, closeViewer],
60+
);
61+
62+
// Global keyboard listener
63+
useEffect(() => {
64+
if (isViewerOpen) {
65+
document.addEventListener("keydown", handleKeyDown);
66+
return () => document.removeEventListener("keydown", handleKeyDown);
67+
}
68+
}, [isViewerOpen, handleKeyDown]);
69+
4870
return (
49-
<Carousel
50-
opts={{
51-
startIndex,
52-
}}
53-
>
54-
<Dialog>
55-
<DialogTrigger asChild>
56-
<div
57-
ref={ref}
58-
className="grid grid-cols-12 items-center -gap-10 mt-3"
71+
<>
72+
<div
73+
ref={ref}
74+
className="relative mb-4 mt-2 p-2"
75+
style={{ height: "160px" }}
76+
>
77+
{images.map((image, index) => (
78+
<Polaroid
79+
isVisible={isVisible}
80+
index={index}
81+
total={images.length}
82+
key={image.src}
83+
variant={image.variant}
84+
onClick={() => openViewer(index)}
85+
src={image.src}
86+
/>
87+
))}
88+
</div>
89+
90+
{/* Amazing Full-Screen Image Viewer */}
91+
<AnimatePresence>
92+
{isViewerOpen && (
93+
<motion.div
94+
className="fixed inset-0 z-50 flex items-center justify-center"
95+
initial={{ opacity: 0 }}
96+
animate={{ opacity: 1 }}
97+
exit={{ opacity: 0 }}
98+
transition={{ duration: 0.3, ease: "easeOut" }}
5999
>
60-
{images.map((image, index) => (
61-
<Polaroid
62-
isVisible={isVisible}
63-
index={index}
64-
total={images.length}
65-
key={image.src}
66-
variant={image.variant}
67-
onClick={() => setStartIndex(index)}
68-
src={image.src}
69-
/>
70-
))}
71-
</div>
72-
</DialogTrigger>
73-
<DialogContent>
74-
<DialogHeader>
75-
<DialogTitle>{event}</DialogTitle>
76-
<DialogDescription>{title}</DialogDescription>
77-
</DialogHeader>
78-
<div>
79-
<CarouselContent>
80-
{images.map((image) => (
81-
<CarouselItem key={image.src}>
82-
<Image
83-
alt=""
84-
src={image.src}
85-
width={image.variant === "1x1" ? 640 : 480}
86-
height={image.variant === "1x1" ? 640 : 960}
87-
// className="object-contain"
88-
/>
89-
</CarouselItem>
90-
))}
91-
</CarouselContent>
92-
<CarouselPrevious />
93-
<CarouselNext />
94-
</div>
95-
</DialogContent>
96-
</Dialog>
97-
</Carousel>
100+
{/* Immersive Background */}
101+
<motion.div
102+
className="absolute inset-0 bg-black/90 backdrop-blur-md"
103+
initial={{ backdropFilter: "blur(0px)" }}
104+
animate={{ backdropFilter: "blur(12px)" }}
105+
exit={{ backdropFilter: "blur(0px)" }}
106+
onClick={closeViewer}
107+
/>
108+
109+
{/* Main Content Container */}
110+
<div className="relative z-10 w-full h-full flex flex-col">
111+
{/* Header */}
112+
<motion.div
113+
className="flex items-center justify-between p-6 text-white"
114+
initial={{ y: -50, opacity: 0 }}
115+
animate={{ y: 0, opacity: 1 }}
116+
transition={{ delay: 0.1, duration: 0.4 }}
117+
>
118+
<div>
119+
<h2 className="text-2xl font-bold">{event}</h2>
120+
{title && <p className="text-gray-300 mt-1">{title}</p>}
121+
</div>
122+
<div className="flex items-center gap-4">
123+
<span className="text-sm text-gray-400">
124+
{currentIndex + 1} / {images.length}
125+
</span>
126+
<button
127+
onClick={closeViewer}
128+
className="p-2 hover:bg-white/10 rounded-full transition-colors"
129+
>
130+
<svg
131+
className="w-6 h-6"
132+
fill="none"
133+
stroke="currentColor"
134+
viewBox="0 0 24 24"
135+
>
136+
<path
137+
strokeLinecap="round"
138+
strokeLinejoin="round"
139+
strokeWidth={2}
140+
d="M6 18L18 6M6 6l12 12"
141+
/>
142+
</svg>
143+
</button>
144+
</div>
145+
</motion.div>
146+
147+
{/* Image Container */}
148+
<div className="flex-1 flex items-center justify-center p-6">
149+
<motion.div
150+
key={currentIndex}
151+
className="relative w-full h-[80vh] max-w-4xl mx-auto"
152+
initial={{ scale: 0.8, opacity: 0, rotateY: 15 }}
153+
animate={{ scale: 1, opacity: 1, rotateY: 0 }}
154+
exit={{ scale: 0.8, opacity: 0, rotateY: -15 }}
155+
transition={{
156+
duration: 0.6,
157+
ease: [0.25, 0.46, 0.45, 0.94],
158+
type: "spring",
159+
stiffness: 100,
160+
damping: 20,
161+
}}
162+
>
163+
<div className="relative w-full h-full rounded-xl overflow-hidden flex items-center justify-center">
164+
<Image
165+
src={images[currentIndex].src}
166+
alt=""
167+
width={800}
168+
height={600}
169+
className="object-contain max-w-full max-h-full rounded-xl"
170+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 70vw"
171+
priority
172+
/>
173+
174+
{/* Subtle gradient overlay */}
175+
<div className="absolute inset-0 pointer-events-none" />
176+
</div>
177+
</motion.div>
178+
</div>
179+
180+
{/* Navigation Controls */}
181+
<motion.div
182+
className="flex items-center justify-center gap-8 p-6"
183+
initial={{ y: 50, opacity: 0 }}
184+
animate={{ y: 0, opacity: 1 }}
185+
transition={{ delay: 0.2, duration: 0.4 }}
186+
>
187+
<button
188+
onClick={prevImage}
189+
className="p-3 bg-white/10 hover:bg-white/20 rounded-full transition-all duration-200 backdrop-blur-sm"
190+
disabled={images.length <= 1}
191+
>
192+
<svg
193+
className="w-6 h-6 text-white"
194+
fill="none"
195+
stroke="currentColor"
196+
viewBox="0 0 24 24"
197+
>
198+
<path
199+
strokeLinecap="round"
200+
strokeLinejoin="round"
201+
strokeWidth={2}
202+
d="M15 19l-7-7 7-7"
203+
/>
204+
</svg>
205+
</button>
206+
207+
{/* Progress Dots */}
208+
<div className="flex gap-2">
209+
{images.map((_, index) => (
210+
<button
211+
key={index}
212+
onClick={() => setCurrentIndex(index)}
213+
className={`w-2 h-2 rounded-full transition-all duration-200 ${
214+
index === currentIndex
215+
? "bg-white scale-125"
216+
: "bg-white/40 hover:bg-white/60"
217+
}`}
218+
/>
219+
))}
220+
</div>
221+
222+
<button
223+
onClick={nextImage}
224+
className="p-3 bg-white/10 hover:bg-white/20 rounded-full transition-all duration-200 backdrop-blur-sm"
225+
disabled={images.length <= 1}
226+
>
227+
<svg
228+
className="w-6 h-6 text-white"
229+
fill="none"
230+
stroke="currentColor"
231+
viewBox="0 0 24 24"
232+
>
233+
<path
234+
strokeLinecap="round"
235+
strokeLinejoin="round"
236+
strokeWidth={2}
237+
d="M9 5l7 7-7 7"
238+
/>
239+
</svg>
240+
</button>
241+
</motion.div>
242+
</div>
243+
</motion.div>
244+
)}
245+
</AnimatePresence>
246+
</>
98247
);
99248
};
100249

0 commit comments

Comments
 (0)