Skip to content

Commit 049b74c

Browse files
committed
feat: add fullscreen slider
1 parent c6f80fc commit 049b74c

2 files changed

Lines changed: 151 additions & 31 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import Swiper from 'swiper';
3+
import { Navigation, A11y } from 'swiper/modules';
4+
import 'swiper/css';
5+
import 'swiper/css/navigation';
6+
7+
interface Props {
8+
images: string[];
9+
}
10+
11+
export default function FullscreenSliderModal({ images }: Props) {
12+
const [isFullscreen, setIsFullscreen] = useState(false);
13+
const previewSwiperRef = useRef<HTMLDivElement>(null);
14+
const fullscreenSwiperRef = useRef<HTMLDivElement>(null);
15+
const nextButtonRef = useRef<HTMLDivElement>(null);
16+
const prevButtonRef = useRef<HTMLDivElement>(null);
17+
const fullNextButtonRef = useRef<HTMLDivElement>(null);
18+
const fullPrevButtonRef = useRef<HTMLDivElement>(null);
19+
20+
const [previewSwiper, setPreviewSwiper] = useState<Swiper | null>(null);
21+
const [fullscreenSwiper, setFullscreenSwiper] = useState<Swiper | null>(null);
22+
23+
// Initialize preview slider
24+
useEffect(() => {
25+
if (previewSwiperRef.current) {
26+
const swiper = new Swiper(previewSwiperRef.current, {
27+
modules: [Navigation, A11y],
28+
slidesPerView: 1,
29+
spaceBetween: 20,
30+
navigation: {
31+
nextEl: nextButtonRef.current,
32+
prevEl: prevButtonRef.current,
33+
},
34+
loop: true,
35+
a11y: {
36+
prevSlideMessage: 'Previous slide',
37+
nextSlideMessage: 'Next slide',
38+
},
39+
});
40+
setPreviewSwiper(swiper);
41+
return () => swiper.destroy();
42+
}
43+
}, []);
44+
45+
// Initialize fullscreen slider when opened
46+
useEffect(() => {
47+
if (isFullscreen && fullscreenSwiperRef.current) {
48+
const swiper = new Swiper(fullscreenSwiperRef.current, {
49+
modules: [Navigation, A11y],
50+
slidesPerView: 1,
51+
navigation: {
52+
nextEl: fullNextButtonRef.current,
53+
prevEl: fullPrevButtonRef.current,
54+
},
55+
loop: true,
56+
a11y: {
57+
prevSlideMessage: 'Previous slide',
58+
nextSlideMessage: 'Next slide',
59+
},
60+
});
61+
62+
// Sync with preview slider if it exists
63+
if (previewSwiper) {
64+
swiper.slideTo(previewSwiper.realIndex);
65+
}
66+
67+
setFullscreenSwiper(swiper);
68+
document.body.style.overflow = 'hidden';
69+
70+
return () => {
71+
swiper.destroy();
72+
document.body.style.overflow = 'auto';
73+
};
74+
}
75+
}, [isFullscreen, previewSwiper]);
76+
77+
const openFullscreen = () => {
78+
setIsFullscreen(true);
79+
};
80+
81+
const closeFullscreen = () => {
82+
// Sync back to preview slider before closing
83+
if (previewSwiper && fullscreenSwiper) {
84+
previewSwiper.slideTo(fullscreenSwiper.realIndex);
85+
}
86+
setIsFullscreen(false); };
87+
88+
return (
89+
<>
90+
{/* Preview Slider */}
91+
<div className="relative mt-8">
92+
<button
93+
onClick={openFullscreen}
94+
className="absolute top-4 right-4 bg-black/70 text-white px-3 py-1 rounded text-sm flex items-center gap-1 z-10"
95+
aria-label="View in fullscreen"
96+
>
97+
Fullscreen
98+
</button>
99+
<div className="swiper" ref={previewSwiperRef}>
100+
<div className="swiper-wrapper">
101+
{images.map((image, index) => (
102+
<div key={`preview-${index}`} className="swiper-slide">
103+
<img
104+
src={image}
105+
alt={`Preview ${index + 1}`}
106+
className="w-full h-auto object-contain rounded-lg cursor-zoom-in"
107+
loading="lazy"
108+
onClick={openFullscreen}
109+
/>
110+
</div>
111+
))}
112+
</div>
113+
<div ref={prevButtonRef} className="swiper-button-prev"></div>
114+
<div ref={nextButtonRef} className="swiper-button-next"></div>
115+
</div>
116+
</div>
117+
118+
{/* Fullscreen Modal */}
119+
{isFullscreen && (
120+
<div className="fixed inset-0 bg-black/70 z-50 p-4 w-full h-full">
121+
<button
122+
className="absolute top-4 right-4 text-center text-white z-10 hover:cursor-pointer text-2xl rounded-full"
123+
onClick={closeFullscreen}
124+
aria-label="Close fullscreen"
125+
>
126+
&times;
127+
</button>
128+
129+
<div className="flex justify-center items-center swiper w-full h-full" ref={fullscreenSwiperRef}>
130+
<div className="swiper-wrapper" onClick={closeFullscreen}>
131+
{images.map((image, index) => (
132+
<div key={`full-${index}`} className="swiper-slide">
133+
<img
134+
src={image}
135+
alt={`Slide ${index + 1}`}
136+
className="w-full h-full object-contain"
137+
loading="lazy"
138+
/>
139+
</div>
140+
))}
141+
</div>
142+
<div ref={fullPrevButtonRef} className="swiper-button-prev text-white"></div>
143+
<div ref={fullNextButtonRef} className="swiper-button-next text-white"></div>
144+
</div>
145+
</div>
146+
)}
147+
</>
148+
);
149+
}

src/layouts/BlogPost.astro

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import BaseHead from '../components/BaseHead.astro';
55
import Header from '../components/Header.astro';
66
import Footer from '../components/Footer.astro';
77
import FormattedDate from '../components/FormattedDate.astro';
8-
import { Image } from 'astro:assets'
8+
import FullscreenSliderModal from '../components/FullscreenSliderModal.tsx';
99
1010
type Props = CollectionEntry<'blog'>['data'];
1111
@@ -47,40 +47,11 @@ const { title, pubDate, updatedDate, carouselImages, tags } = Astro.props;
4747
</div>
4848
<slot />
4949
{carouselImages && (
50-
<div class="relative">
51-
<div class="swiper mt-8 rounded-lg overflow-hidden">
52-
<div class="swiper-wrapper">
53-
{carouselImages?.map((image) => (
54-
<div class="swiper-slide">
55-
<Image src={image} alt="Carousel Image Content" height={480} width={960} class="w-full h-auto" />
56-
</div>
57-
))}
58-
</div>
59-
<div class="swiper-button-prev text-fuchsia-700"></div>
60-
<div class="swiper-button-next text-fuchsia-700"></div>
61-
</div>
62-
</div>
50+
<FullscreenSliderModal images={carouselImages} client:only="react" />
6351
)}
6452
</div>
6553
</article>
6654
</main>
6755
<Footer />
6856
</body>
69-
<script>
70-
import Swiper from 'swiper';
71-
import { Navigation, A11y } from 'swiper/modules';
72-
import 'swiper/css';
73-
import 'swiper/css/bundle';
74-
75-
const swiper = new Swiper('.swiper', {
76-
modules: [Navigation, A11y],
77-
slidesPerView: 1,
78-
zoom: true,
79-
navigation: {
80-
prevEl: '.swiper-button-prev',
81-
nextEl: '.swiper-button-next',
82-
},
83-
});
84-
</script>
85-
8657
</html>

0 commit comments

Comments
 (0)