diff --git a/src/platform/assets/components/AssetCard.stories.ts b/src/platform/assets/components/AssetCard.stories.ts index 2b3532a05..2915a219a 100644 --- a/src/platform/assets/components/AssetCard.stories.ts +++ b/src/platform/assets/components/AssetCard.stories.ts @@ -84,6 +84,51 @@ export const NonInteractive: Story = { } } +export const WithPreviewImage: Story = { + args: { + asset: createAssetData({ + preview_url: '/assets/images/comfy-logo-single.svg' + }), + interactive: true + }, + decorators: [ + () => ({ + template: + '
' + }) + ], + parameters: { + docs: { + description: { + story: 'AssetCard with a preview image displayed.' + } + } + } +} + +export const FallbackGradient: Story = { + args: { + asset: createAssetData({ + preview_url: undefined + }), + interactive: true + }, + decorators: [ + () => ({ + template: + '
' + }) + ], + parameters: { + docs: { + description: { + story: + 'AssetCard showing fallback gradient when no preview image is available.' + } + } + } +} + export const EdgeCases: Story = { render: () => ({ components: { AssetCard }, diff --git a/src/platform/assets/components/AssetCard.vue b/src/platform/assets/components/AssetCard.vue index dca0d2d64..dd35b4fc1 100644 --- a/src/platform/assets/components/AssetCard.vue +++ b/src/platform/assets/components/AssetCard.vue @@ -4,38 +4,29 @@ data-component-id="AssetCard" :data-asset-id="asset.id" v-bind="elementProps" - :class=" - cn( - // Base layout and container styles (always applied) - 'rounded-xl overflow-hidden transition-all duration-200', - interactive && 'group', - // Button-specific styles - interactive && [ - 'appearance-none bg-transparent p-0 m-0 font-inherit text-inherit outline-none cursor-pointer text-left', - 'bg-gray-100 dark-theme:bg-charcoal-800', - 'hover:bg-gray-200 dark-theme:hover:bg-charcoal-600', - 'border-none', - 'focus:outline-solid outline-blue-100 outline-4' - ], - // Div-specific styles - !interactive && 'bg-gray-100 dark-theme:bg-charcoal-800' - ) - " + :class="cardClasses" @click="interactive && $emit('select', asset)" @keydown.enter="interactive && $emit('select', asset)" > -
+
+

-import { computed } from 'vue' +import { useImage } from '@vueuse/core' +import { computed, useId } from 'vue' import AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue' import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser' @@ -94,13 +87,51 @@ const props = defineProps<{ interactive?: boolean }>() +const titleId = useId() +const descId = useId() + +const { error } = useImage({ + src: props.asset.preview_url ?? '', + alt: props.asset.name +}) + +const shouldShowImage = computed(() => props.asset.preview_url && !error.value) + +const cardClasses = computed(() => { + const base = [ + 'rounded-xl', + 'overflow-hidden', + 'transition-all', + 'duration-200' + ] + + if (!props.interactive) { + return cn(...base, 'bg-gray-100 dark-theme:bg-charcoal-800') + } + + return cn( + ...base, + 'group', + 'appearance-none bg-transparent p-0 m-0', + 'font-inherit text-inherit outline-none cursor-pointer text-left', + 'bg-gray-100 dark-theme:bg-charcoal-800', + 'hover:bg-gray-200 dark-theme:hover:bg-charcoal-600', + 'border-none', + 'focus:outline-solid outline-blue-100 outline-4' + ) +}) + const elementProps = computed(() => props.interactive ? { type: 'button', - 'aria-label': `Select asset ${props.asset.name}` + 'aria-labelledby': titleId, + 'aria-describedby': descId + } + : { + 'aria-labelledby': titleId, + 'aria-describedby': descId } - : {} ) defineEmits<{