60 lines
1.6 KiB
TypeScript
Executable File
60 lines
1.6 KiB
TypeScript
Executable File
import { defineComponent, ref, computed, type Ref } from 'vue';
|
|
|
|
export type GalleryImage = {
|
|
src: string;
|
|
alt?: string;
|
|
caption?: string;
|
|
};
|
|
|
|
export default defineComponent({
|
|
name: 'CMyImageGallery',
|
|
props: {
|
|
images: { type: Array as () => GalleryImage[], required: true },
|
|
layout: { type: String as () => 'grid' | 'carousel', default: 'grid' },
|
|
cols: { type: Number, default: 3 },
|
|
gap: { type: Number, default: 12 },
|
|
ratio: { type: Number, default: 1 },
|
|
lightbox: { type: Boolean, default: false }
|
|
},
|
|
emits: ['imageClick'],
|
|
setup(props, { emit }) {
|
|
const slide: Ref<number> = ref(0);
|
|
const lightboxOpen = ref(false);
|
|
const currentIndex = ref(0);
|
|
|
|
const gridStyle = computed(() => ({
|
|
gridTemplateColumns: `repeat(${props.cols}, 1fr)`,
|
|
gap: props.gap + 'px'
|
|
}));
|
|
|
|
const currentImage = computed(() => props.images?.[currentIndex.value] ?? null);
|
|
|
|
function onImageClick(idx: number) {
|
|
emit('imageClick', idx, props.images[idx]);
|
|
if (props.lightbox) {
|
|
currentIndex.value = idx;
|
|
lightboxOpen.value = true;
|
|
}
|
|
}
|
|
function next() {
|
|
if (!props.images?.length) return;
|
|
currentIndex.value = (currentIndex.value + 1) % props.images.length;
|
|
}
|
|
function prev() {
|
|
if (!props.images?.length) return;
|
|
currentIndex.value = (currentIndex.value - 1 + props.images.length) % props.images.length;
|
|
}
|
|
|
|
return {
|
|
slide,
|
|
lightboxOpen,
|
|
currentIndex,
|
|
currentImage,
|
|
gridStyle,
|
|
onImageClick,
|
|
next,
|
|
prev
|
|
};
|
|
}
|
|
});
|