feat(T02): Static pages – home, referenzen, kontakt, legal (impressum/datenschutz/agb)
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user