109 lines
2.6 KiB
Vue
109 lines
2.6 KiB
Vue
|
|
<template>
|
||
|
|
<Teleport to="body">
|
||
|
|
<div
|
||
|
|
v-if="isOpen"
|
||
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/90"
|
||
|
|
data-testid="lightbox"
|
||
|
|
@click.self="$emit('close')"
|
||
|
|
@keydown.esc="$emit('close')"
|
||
|
|
tabindex="0"
|
||
|
|
ref="lightboxRef"
|
||
|
|
>
|
||
|
|
<!-- Close Button -->
|
||
|
|
<button
|
||
|
|
class="absolute top-4 right-4 text-text text-3xl leading-none p-2 hover:text-accent transition-colors duration-fast"
|
||
|
|
data-testid="lightbox-close"
|
||
|
|
aria-label="Schließen"
|
||
|
|
@click="$emit('close')"
|
||
|
|
>
|
||
|
|
×
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<!-- Prev Button -->
|
||
|
|
<button
|
||
|
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-text text-3xl leading-none p-3 hover:text-accent transition-colors duration-fast"
|
||
|
|
data-testid="lightbox-prev"
|
||
|
|
aria-label="Vorheriges Bild"
|
||
|
|
@click="$emit('prev')"
|
||
|
|
>
|
||
|
|
‹
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<!-- Image -->
|
||
|
|
<div class="max-w-4xl max-h-[80vh] px-16">
|
||
|
|
<img
|
||
|
|
:src="image.src"
|
||
|
|
:alt="image.alt"
|
||
|
|
class="max-w-full max-h-[80vh] object-contain rounded-lg"
|
||
|
|
/>
|
||
|
|
<p class="text-center text-text-muted text-sm mt-4">{{ image.caption }}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Next Button -->
|
||
|
|
<button
|
||
|
|
class="absolute right-4 top-1/2 -translate-y-1/2 text-text text-3xl leading-none p-3 hover:text-accent transition-colors duration-fast"
|
||
|
|
data-testid="lightbox-next"
|
||
|
|
aria-label="Nächstes Bild"
|
||
|
|
@click="$emit('next')"
|
||
|
|
>
|
||
|
|
›
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</Teleport>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, watch, onMounted, onUnmounted, nextTick } from "vue";
|
||
|
|
|
||
|
|
interface LightboxImage {
|
||
|
|
src: string;
|
||
|
|
alt: string;
|
||
|
|
caption: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
const props = defineProps<{
|
||
|
|
isOpen: boolean;
|
||
|
|
image: LightboxImage;
|
||
|
|
}>();
|
||
|
|
|
||
|
|
const lightboxRef = ref<HTMLElement | null>(null);
|
||
|
|
|
||
|
|
const emit = defineEmits<{
|
||
|
|
close: [];
|
||
|
|
prev: [];
|
||
|
|
next: [];
|
||
|
|
}>();
|
||
|
|
|
||
|
|
const handleKeydown = (e: KeyboardEvent) => {
|
||
|
|
if (!props.isOpen) return;
|
||
|
|
if (e.key === "Escape") {
|
||
|
|
emit("close");
|
||
|
|
} else if (e.key === "ArrowLeft") {
|
||
|
|
emit("prev");
|
||
|
|
} else if (e.key === "ArrowRight") {
|
||
|
|
emit("next");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
window.addEventListener("keydown", handleKeydown);
|
||
|
|
});
|
||
|
|
|
||
|
|
onUnmounted(() => {
|
||
|
|
window.removeEventListener("keydown", handleKeydown);
|
||
|
|
});
|
||
|
|
|
||
|
|
watch(
|
||
|
|
() => props.isOpen,
|
||
|
|
(open) => {
|
||
|
|
if (open) {
|
||
|
|
document.body.style.overflow = "hidden";
|
||
|
|
nextTick(() => {
|
||
|
|
lightboxRef.value?.focus();
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
document.body.style.overflow = "";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
</script>
|