123 lines
3.3 KiB
Vue
123 lines
3.3 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue';
|
|
import MediaPreviewDialog from '@/components/media/MediaPreviewDialog.vue';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import type { MediaItem } from '@/types/media';
|
|
|
|
const props = defineProps<{
|
|
items: MediaItem[];
|
|
maxVisible?: number;
|
|
}>();
|
|
|
|
const previewOpen = ref(false);
|
|
const previewUrl = ref<string | null>(null);
|
|
const galleryOpen = ref(false);
|
|
const cameFromGallery = ref(false);
|
|
|
|
const visibleItems = computed(() => props.items.slice(0, props.maxVisible ?? 3));
|
|
const hiddenCount = computed(() => Math.max(props.items.length - visibleItems.value.length, 0));
|
|
|
|
const galleryWidthClass = computed(() => {
|
|
const count = props.items.length;
|
|
|
|
if (count <= 1) {
|
|
return 'sm:max-w-xs';
|
|
}
|
|
|
|
if (count <= 2) {
|
|
return 'sm:max-w-sm';
|
|
}
|
|
|
|
if (count <= 4) {
|
|
return 'sm:max-w-md';
|
|
}
|
|
|
|
if (count <= 8) {
|
|
return 'sm:max-w-lg';
|
|
}
|
|
|
|
return 'sm:max-w-xl';
|
|
});
|
|
|
|
const galleryGridClass = computed(() => {
|
|
const count = props.items.length;
|
|
|
|
if (count === 1) {
|
|
return 'grid-cols-1';
|
|
}
|
|
|
|
if (count === 2) {
|
|
return 'grid-cols-2';
|
|
}
|
|
|
|
if (count === 3) {
|
|
return 'grid-cols-3';
|
|
}
|
|
|
|
return 'grid-cols-4';
|
|
});
|
|
|
|
function openPreview(url: string) {
|
|
previewUrl.value = url;
|
|
previewOpen.value = true;
|
|
}
|
|
|
|
function openPreviewFromGallery(url: string) {
|
|
cameFromGallery.value = true;
|
|
galleryOpen.value = false;
|
|
setTimeout(() => {
|
|
previewUrl.value = url;
|
|
previewOpen.value = true;
|
|
}, 200);
|
|
}
|
|
|
|
function onPreviewClose() {
|
|
if (cameFromGallery.value) {
|
|
cameFromGallery.value = false;
|
|
setTimeout(() => {
|
|
galleryOpen.value = true;
|
|
}, 200);
|
|
}
|
|
}
|
|
|
|
function openGallery() {
|
|
galleryOpen.value = true;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="items.length" class="flex items-center gap-1">
|
|
<button v-for="item in visibleItems" :key="item.id" type="button"
|
|
class="size-8 overflow-hidden rounded border bg-muted/30" @click="openPreview(item.url)">
|
|
<img :src="item.thumb_url" alt="Foto" class="size-full object-cover">
|
|
</button>
|
|
<button v-if="hiddenCount > 0" type="button"
|
|
class="flex size-8 items-center justify-center rounded border bg-muted/30 text-muted-foreground text-xs font-medium hover:bg-muted/50 transition-colors"
|
|
@click="openGallery">
|
|
+{{ hiddenCount }}
|
|
</button>
|
|
</div>
|
|
|
|
<Dialog v-model:open="galleryOpen">
|
|
<DialogContent :class="galleryWidthClass">
|
|
<DialogHeader>
|
|
<DialogTitle>Semua Foto ({{ items.length }})</DialogTitle>
|
|
</DialogHeader>
|
|
<div class="grid gap-2 max-h-[60vh] overflow-y-auto p-1" :class="galleryGridClass">
|
|
<button v-for="item in items" :key="item.id" type="button"
|
|
class="aspect-square overflow-hidden rounded border bg-muted/30"
|
|
@click="openPreviewFromGallery(item.url)">
|
|
<img :src="item.thumb_url" alt="Foto" class="size-full object-cover">
|
|
</button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<MediaPreviewDialog v-model:open="previewOpen" :url="previewUrl" @close="onPreviewClose" />
|
|
</template>
|