Add explicit public image loading and unavailable states, accessible selected-thumbnail state, and Storybook coverage.
This commit is contained in:
@@ -24,3 +24,10 @@ export const BrokenImageFallback = {
|
||||
images: [{ id: 'broken', src: '/fixtures/missing-image.webp', alt: 'Unavailable collectible image' }],
|
||||
},
|
||||
};
|
||||
|
||||
export const LoadingImage = {
|
||||
args: {
|
||||
images: [{ id: 'loading', src: '/fixtures/slow-image.webp', alt: 'Collectible image loading' }],
|
||||
},
|
||||
parameters: { chromatic: { delay: 1000 } },
|
||||
};
|
||||
@@ -1,25 +1,47 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
images: { type: Array, required: true },
|
||||
});
|
||||
const selectedIndex = ref(0);
|
||||
const failedImages = ref(new Set());
|
||||
const loadingImages = ref(new Set());
|
||||
|
||||
function imageKey(image) {
|
||||
return image?.id ?? '';
|
||||
}
|
||||
|
||||
function markLoading(image) {
|
||||
if (!image?.src && !image?.url) return;
|
||||
loadingImages.value = new Set(loadingImages.value).add(imageKey(image));
|
||||
}
|
||||
|
||||
function markFailed(imageId) {
|
||||
failedImages.value = new Set(failedImages.value).add(imageId);
|
||||
const nextLoadingImages = new Set(loadingImages.value);
|
||||
nextLoadingImages.delete(imageId);
|
||||
loadingImages.value = nextLoadingImages;
|
||||
}
|
||||
|
||||
function markLoaded(imageId) {
|
||||
const nextLoadingImages = new Set(loadingImages.value);
|
||||
nextLoadingImages.delete(imageId);
|
||||
loadingImages.value = nextLoadingImages;
|
||||
}
|
||||
|
||||
onMounted(() => markLoading(props.images[selectedIndex.value]));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="image-gallery">
|
||||
<div class="image-gallery__stage">
|
||||
<img v-if="(props.images[selectedIndex]?.src ?? props.images[selectedIndex]?.url) && !failedImages.has(props.images[selectedIndex].id)" :src="props.images[selectedIndex].src ?? props.images[selectedIndex].url" :alt="props.images[selectedIndex].altText ?? props.images[selectedIndex].alt" @error="markFailed(props.images[selectedIndex].id)" />
|
||||
<span v-else class="image-gallery__placeholder" role="img" :aria-label="props.images[selectedIndex]?.src ? 'Collectible image unavailable' : 'No collectible image available'">{{ props.images[selectedIndex]?.src ? 'IMAGE UNAVAILABLE' : (props.images[selectedIndex]?.label ?? 'IMAGE') }}</span>
|
||||
<img v-if="(props.images[selectedIndex]?.src ?? props.images[selectedIndex]?.url) && !failedImages.has(props.images[selectedIndex].id)" :src="props.images[selectedIndex].src ?? props.images[selectedIndex].url" :alt="props.images[selectedIndex].altText ?? props.images[selectedIndex].alt" @load="markLoaded(props.images[selectedIndex].id)" @error="markFailed(props.images[selectedIndex].id)" />
|
||||
<span v-if="loadingImages.has(imageKey(props.images[selectedIndex]))" class="image-gallery__placeholder" role="status" aria-label="Loading collectible image">Loading image...</span>
|
||||
<span v-else-if="!((props.images[selectedIndex]?.src ?? props.images[selectedIndex]?.url) && !failedImages.has(props.images[selectedIndex].id))" class="image-gallery__placeholder" role="img" :aria-label="props.images[selectedIndex]?.src ? 'Collectible image unavailable' : 'No collectible image available'">{{ props.images[selectedIndex]?.src ? 'IMAGE UNAVAILABLE' : (props.images[selectedIndex]?.label ?? 'IMAGE') }}</span>
|
||||
</div>
|
||||
<div v-if="props.images.length > 1" class="image-gallery__controls" aria-label="Gallery images">
|
||||
<button v-for="(image, index) in props.images" :key="image.id" :class="{ 'image-gallery__thumb--selected': index === selectedIndex }" type="button" :aria-label="`Show image ${index + 1}: ${image.alt}`" @click="selectedIndex = index">
|
||||
<button v-for="(image, index) in props.images" :key="image.id" :class="{ 'image-gallery__thumb--selected': index === selectedIndex }" type="button" :aria-label="`Show image ${index + 1}: ${image.alt}`" :aria-current="index === selectedIndex ? 'true' : undefined" @click="selectedIndex = index; markLoading(image)">
|
||||
{{ index + 1 }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user