Skip to content

Commit

Permalink
Merge pull request #485 from pinkary-project/feat/image-lightbox
Browse files Browse the repository at this point in the history
Lightbox implementation for Post Images
  • Loading branch information
nunomaduro authored Aug 5, 2024
2 parents b1596bd + 39d2b6e commit 63e66e5
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 13 deletions.
3 changes: 3 additions & 0 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ import { autocomplete, usesAutocomplete } from "./autocomplete.js";
Alpine.data('dynamicAutocomplete', autocomplete);
Alpine.data('usesDynamicAutocomplete', usesAutocomplete);

import {lightBox} from './light-box.js';
Alpine.data('lightBox',lightBox);

Livewire.start()
72 changes: 72 additions & 0 deletions resources/js/light-box.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const lightBox = () => ({
open: false,
imgSrc: '',
currentIndex: 0,
images: [],
init() {
let self = this;

let hasLightboxImageElements = document.querySelectorAll('[data-has-lightbox-images]');

hasLightboxImageElements.forEach((lightboxImageElement) => {
let images = lightboxImageElement.querySelectorAll('img');

images.forEach((img, index) => {
img.classList.add('cursor-pointer');
img.dataset.navigateIgnore = true;
img.addEventListener('click', function (e) {
self.currentIndex = index;
self.images = images;
self.updateImageSrc();
self.$dispatch('open-modal', 'image-lightbox');
self.attachKeyboardEvents();
});
});
});

window.addEventListener('modal-opened', (e) => {
if (e.detail === 'image-lightbox') {
this.open = true;
}
});

window.addEventListener('modal-closed', (e) => {
if (e.detail === 'image-lightbox') {
this.open = false;
}
});
},
nextImage() {
this.currentIndex = (this.currentIndex + 1) % this.images.length;
this.updateImageSrc()
},
prevImage() {
this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
this.updateImageSrc()
},
updateImageSrc() {
this.imgSrc = this.images[this.currentIndex].src;
},
shouldShowNextButton() {
return this.canScrollImages() && this.images.length - 1 !== this.currentIndex;
},
shouldShowPrevButton() {
return this.canScrollImages() && this.currentIndex !== 0;
},
canScrollImages() {
return this.images.length > 1;
},
attachKeyboardEvents() {
document.addEventListener('keydown', (e) => {
if (this.open && this.canScrollImages()) {
if (e.key === 'ArrowRight' && this.shouldShowNextButton()) {
this.nextImage();
} else if (e.key === 'ArrowLeft' && this.shouldShowPrevButton()) {
this.prevImage();
}
}
});
}
});

export {lightBox}
8 changes: 8 additions & 0 deletions resources/views/components/image-lightbox.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- Lightbox Modal -->
<x-modal name="image-lightbox" close-button-outside-modal should-center-modal-content>
<div x-data="lightBox" class="relative md:flex md:items-center">
<img :src="imgSrc" alt="image" class="max-w-full rounded-lg"/>
<button x-show="shouldShowPrevButton" class="absolute left-0 md:-ml-8 text-white cursor-pointer text-2xl" @click="prevImage">&larr;</button>
<button x-show="shouldShowNextButton" class="absolute right-0 md:-mr-8 text-white cursor-pointer text-2xl" @click="nextImage">&rarr;</button>
</div>
</x-modal>
40 changes: 28 additions & 12 deletions resources/views/components/modal.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
'show' => false,
'maxWidth' => '2xl',
'showCloseButton' => true,
'closeButtonOutsideModal' => false,
'shouldCenterModalContent' => false
])

@php
Expand All @@ -13,6 +15,10 @@
'xl' => 'sm:max-w-xl',
'2xl' => 'sm:max-w-2xl',
][$maxWidth];
$closeButtonPosition = $closeButtonOutsideModal ? 'right-0 -top-10' : 'right-2 top-2';
$contentOverflowStyle = ($closeButtonOutsideModal && !$shouldCenterModalContent) ? 'mt-10' : '';
$modalContentPosition = $shouldCenterModalContent ? 'flex justify-center items-center' : '';
@endphp

<div
Expand All @@ -32,6 +38,14 @@
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
open(name) {
this.show = true;
this.$dispatch('modal-opened', name);
},
close(name) {
this.show = false;
this.$dispatch('modal-closed', name);
}
}"
x-init="
$watch('show', (value) => {
Expand All @@ -43,14 +57,14 @@
}
})
"
x-on:open-modal.window="$event.detail == '{{ $name }}' ? (show = true) : null"
x-on:close-modal.window="$event.detail == '{{ $name }}' ? (show = false) : null"
x-on:open-modal.window="$event.detail == '{{ $name }}' ? open('{{ $name }}') : null"
x-on:close-modal.window="$event.detail == '{{ $name }}' ? close('{{ $name }}') : null"
x-on:close.stop="show = false"
x-on:keydown.escape.window="show = false"
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
x-show="show"
class="fixed inset-0 z-50 overflow-y-auto bg-clip-padding px-4 py-6 backdrop-blur-sm backdrop-filter sm:px-0"
class="fixed inset-0 z-50 overflow-y-auto {{$modalContentPosition}} bg-clip-padding px-4 py-6 backdrop-blur-sm backdrop-filter sm:px-0"
style="display: {{ $show ? 'block' : 'none' }}"
>
<div
Expand All @@ -68,22 +82,24 @@ class="fixed inset-0 transform transition-all"
</div>
<div
x-show="show"
class="{{ $maxWidth }} mb-6 transform overflow-hidden rounded-lg bg-slate-950 shadow-xl transition-all sm:mx-auto sm:w-full"
class="{{ $maxWidth }} {{$contentOverflowStyle}} mb-6 transform rounded-lg bg-slate-950 shadow-xl transition-all sm:mx-auto sm:w-full"
x-transition:enter="duration-300 ease-out"
x-transition:enter-start="translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="translate-y-0 opacity-100 sm:scale-100"
x-transition:leave="duration-200 ease-in"
x-transition:leave-start="translate-y-0 opacity-100 sm:scale-100"
x-transition:leave-end="translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95"
>
<button
x-show="showCloseButton == true"
x-on:click="show = false"
class="absolute right-2 top-2 text-xl text-slate-500 focus:outline-none"
>
<x-heroicon-o-x-mark class="h-6 w-6" />
</button>
<div>
<button
x-show="showCloseButton == true"
x-on:click="show = false"
class="absolute text-xl focus:outline-none z-50 {{$closeButtonPosition}}"
>
<x-heroicon-o-x-mark class="h-6 w-6" />
</button>

{{ $slot }}
{{ $slot }}
</div>
</div>
</div>
1 change: 1 addition & 0 deletions resources/views/layouts/app.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class="bg-slate-950 bg-center bg-repeat font-sans text-slate-50 antialiased"
<main class="mt-16">
{{ $slot }}
</main>
<x-image-lightbox />
</div>

@persist('footer')
Expand Down
2 changes: 1 addition & 1 deletion resources/views/livewire/questions/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class="mt-3 break-words text-slate-200 overflow-hidden answer"
wire:ignore.self
x-ref="parentDiv"
>
<p>
<p data-has-lightbox-images>
{!! $question->answer !!}
</p>
</div>
Expand Down

0 comments on commit 63e66e5

Please sign in to comment.