44 data-component-id =" AssetCard"
55 :data-asset-id =" asset.id"
66 v-bind =" elementProps"
7- :class ="
8- cn(
9- // Base layout and container styles (always applied)
10- 'rounded-xl overflow-hidden transition-all duration-200',
11- interactive && 'group',
12- // Button-specific styles
13- interactive && [
14- 'appearance-none bg-transparent p-0 m-0 font-inherit text-inherit outline-none cursor-pointer text-left',
15- 'bg-gray-100 dark-theme:bg-charcoal-800',
16- 'hover:bg-gray-200 dark-theme:hover:bg-charcoal-600',
17- 'border-none',
18- 'focus:outline-solid outline-blue-100 outline-4'
19- ],
20- // Div-specific styles
21- !interactive && 'bg-gray-100 dark-theme:bg-charcoal-800'
22- )
23- "
7+ :class =" cardClasses"
248 @click =" interactive && $emit('select', asset)"
259 @keydown.enter =" interactive && $emit('select', asset)"
2610 >
27- <div class =" relative aspect-square w-full overflow-hidden" >
11+ <div class =" relative aspect-square w-full overflow-hidden rounded-xl" >
12+ <img
13+ v-if =" shouldShowImage"
14+ :src =" asset.preview_url"
15+ class =" h-full w-full object-contain"
16+ />
2817 <div
29- class =" flex h-full w-full items-center justify-center bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-600"
18+ v-else
19+ class =" flex h-full w-full items-center justify-center bg-gradient-to-br from-gray-400 via-gray-800 to-charcoal-400"
3020 ></div >
3121 <AssetBadgeGroup :badges =" asset.badges" />
3222 </div >
3323 <div :class =" cn('p-4 h-32 flex flex-col justify-between')" >
3424 <div >
3525 <h3
26+ :id =" titleId"
3627 :class ="
3728 cn(
38- 'mb-2 m-0 text-base font-semibold overflow-hidden text-ellipsis whitespace-nowrap ',
29+ 'mb-2 m-0 text-base font-semibold line-clamp-2 wrap-anywhere ',
3930 'text-slate-800',
4031 'dark-theme:text-white'
4132 )
4435 {{ asset.name }}
4536 </h3 >
4637 <p
38+ :id =" descId"
4739 :class ="
4840 cn(
4941 'm-0 text-sm leading-6 overflow-hidden [-webkit-box-orient:vertical] [-webkit-line-clamp:2] [display:-webkit-box]',
8375</template >
8476
8577<script setup lang="ts">
86- import { computed } from ' vue'
78+ import { useImage } from ' @vueuse/core'
79+ import { computed , useId } from ' vue'
8780
8881import AssetBadgeGroup from ' @/platform/assets/components/AssetBadgeGroup.vue'
8982import type { AssetDisplayItem } from ' @/platform/assets/composables/useAssetBrowser'
@@ -94,13 +87,51 @@ const props = defineProps<{
9487 interactive? : boolean
9588}>()
9689
90+ const titleId = useId ()
91+ const descId = useId ()
92+
93+ const { error } = useImage ({
94+ src: props .asset .preview_url ?? ' ' ,
95+ alt: props .asset .name
96+ })
97+
98+ const shouldShowImage = computed (() => props .asset .preview_url && ! error .value )
99+
100+ const cardClasses = computed (() => {
101+ const base = [
102+ ' rounded-xl' ,
103+ ' overflow-hidden' ,
104+ ' transition-all' ,
105+ ' duration-200'
106+ ]
107+
108+ if (! props .interactive ) {
109+ return cn (... base , ' bg-gray-100 dark-theme:bg-charcoal-800' )
110+ }
111+
112+ return cn (
113+ ... base ,
114+ ' group' ,
115+ ' appearance-none bg-transparent p-0 m-0' ,
116+ ' font-inherit text-inherit outline-none cursor-pointer text-left' ,
117+ ' bg-gray-100 dark-theme:bg-charcoal-800' ,
118+ ' hover:bg-gray-200 dark-theme:hover:bg-charcoal-600' ,
119+ ' border-none' ,
120+ ' focus:outline-solid outline-blue-100 outline-4'
121+ )
122+ })
123+
97124const elementProps = computed (() =>
98125 props .interactive
99126 ? {
100127 type: ' button' ,
101- ' aria-label' : ` Select asset ${props .asset .name } `
128+ ' aria-labelledby' : titleId ,
129+ ' aria-describedby' : descId
130+ }
131+ : {
132+ ' aria-labelledby' : titleId ,
133+ ' aria-describedby' : descId
102134 }
103- : {}
104135)
105136
106137defineEmits <{
0 commit comments