Skip to content

Commit 4f23e85

Browse files
committed
Add lightbox
1 parent 2bf1856 commit 4f23e85

File tree

1 file changed

+102
-4
lines changed

1 file changed

+102
-4
lines changed

resources/views/components/showcase-card.blade.php

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
<article
44
x-data="{
55
currentSlide: 0,
6+
lightboxSlide: 0,
7+
lightboxOpen: false,
68
screenshots: {{ json_encode($showcase->screenshots ?? []) }},
9+
screenshotUrls: {{ json_encode(collect($showcase->screenshots ?? [])->map(fn($s) => Storage::disk('public')->url($s))->values()) }},
710
get hasScreenshots() {
811
return this.screenshots && this.screenshots.length > 0
912
},
@@ -19,8 +22,31 @@
1922
if (this.currentSlide > 0) {
2023
this.currentSlide--
2124
}
25+
},
26+
nextLightboxSlide() {
27+
this.lightboxSlide = (this.lightboxSlide + 1) % this.totalSlides
28+
},
29+
prevLightboxSlide() {
30+
this.lightboxSlide = (this.lightboxSlide - 1 + this.totalSlides) % this.totalSlides
31+
},
32+
openLightbox(index) {
33+
this.lightboxSlide = index
34+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
35+
document.body.style.overflow = 'hidden'
36+
document.body.style.paddingRight = scrollbarWidth + 'px'
37+
this.lightboxOpen = true
38+
},
39+
closeLightbox() {
40+
this.lightboxOpen = false
41+
setTimeout(() => {
42+
document.body.style.overflow = ''
43+
document.body.style.paddingRight = ''
44+
}, 200)
2245
}
2346
}"
47+
@keydown.escape.window="closeLightbox()"
48+
@keydown.arrow-right.window="if (lightboxOpen) nextLightboxSlide()"
49+
@keydown.arrow-left.window="if (lightboxOpen) prevLightboxSlide()"
2450
class="group relative overflow-hidden rounded-2xl border border-gray-200 bg-white transition-all hover:shadow-xl dark:border-gray-700 dark:bg-gray-800"
2551
>
2652
{{-- NEW Badge --}}
@@ -37,15 +63,21 @@ class="group relative overflow-hidden rounded-2xl border border-gray-200 bg-whit
3763
@if($showcase->screenshots && count($showcase->screenshots) > 0)
3864
<div class="relative h-full">
3965
@foreach($showcase->screenshots as $index => $screenshot)
40-
<img
66+
<button
67+
type="button"
4168
x-show="currentSlide === {{ $index }}"
4269
x-transition:enter="transition ease-out duration-300"
4370
x-transition:enter-start="opacity-0"
4471
x-transition:enter-end="opacity-100"
45-
src="{{ Storage::disk('public')->url($screenshot) }}"
46-
alt="{{ $showcase->title }} screenshot {{ $index + 1 }}"
47-
class="absolute inset-0 w-full h-full object-contain"
72+
@click="openLightbox({{ $index }})"
73+
class="absolute inset-0 w-full h-full cursor-zoom-in focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-inset"
4874
>
75+
<img
76+
src="{{ Storage::disk('public')->url($screenshot) }}"
77+
alt="{{ $showcase->title }} screenshot {{ $index + 1 }}"
78+
class="w-full h-full object-contain"
79+
>
80+
</button>
4981
@endforeach
5082

5183
{{-- Navigation Arrows --}}
@@ -213,4 +245,70 @@ class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gra
213245
@endif
214246
</div>
215247
</div>
248+
249+
{{-- Lightbox Modal --}}
250+
<template x-teleport="body">
251+
<div
252+
x-show="lightboxOpen"
253+
x-transition:enter="transition ease-out duration-300"
254+
x-transition:enter-start="opacity-0"
255+
x-transition:enter-end="opacity-100"
256+
x-transition:leave="transition ease-in duration-200"
257+
x-transition:leave-start="opacity-100"
258+
x-transition:leave-end="opacity-0"
259+
class="fixed inset-0 z-50 flex items-center justify-center bg-black/90 p-4 sm:p-8"
260+
@click.self="closeLightbox()"
261+
>
262+
{{-- Close Button --}}
263+
<button
264+
@click="closeLightbox()"
265+
class="absolute top-4 right-4 z-10 p-2 rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors"
266+
aria-label="Close lightbox"
267+
>
268+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
269+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
270+
</svg>
271+
</button>
272+
273+
{{-- Image Counter --}}
274+
<div
275+
x-show="totalSlides > 1"
276+
class="absolute top-4 left-4 px-3 py-1.5 rounded-full bg-white/10 text-white text-sm font-medium"
277+
>
278+
<span x-text="lightboxSlide + 1"></span> / <span x-text="totalSlides"></span>
279+
</div>
280+
281+
{{-- Previous Button --}}
282+
<button
283+
x-show="totalSlides > 1"
284+
@click.stop="prevLightboxSlide()"
285+
class="absolute left-4 top-1/2 -translate-y-1/2 z-10 p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors"
286+
aria-label="Previous screenshot"
287+
>
288+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
289+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
290+
</svg>
291+
</button>
292+
293+
{{-- Next Button --}}
294+
<button
295+
x-show="totalSlides > 1"
296+
@click.stop="nextLightboxSlide()"
297+
class="absolute right-4 top-1/2 -translate-y-1/2 z-10 p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors"
298+
aria-label="Next screenshot"
299+
>
300+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
301+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
302+
</svg>
303+
</button>
304+
305+
{{-- Main Image --}}
306+
<img
307+
:src="screenshotUrls[lightboxSlide]"
308+
:alt="'{{ $showcase->title }} screenshot ' + (lightboxSlide + 1)"
309+
class="max-h-full max-w-full object-contain"
310+
@click.stop
311+
>
312+
</div>
313+
</template>
216314
</article>

0 commit comments

Comments
 (0)