[feat] add sematic AssetCard

This commit is contained in:
Arjan Singh
2025-09-16 12:12:49 -07:00
committed by Arjan Singh
parent d36999b9a6
commit 8165ec3bf1
5 changed files with 59 additions and 27 deletions

View File

@@ -5,7 +5,7 @@
:key="badge.label"
:class="
cn(
'px-2 py-1 rounded text-[10px] font-medium uppercase tracking-wider text-white',
'px-2 py-1 rounded text-xs font-medium uppercase tracking-wider text-white',
getBadgeColor(badge.type)
)
"

View File

@@ -1,7 +1,7 @@
<template>
<BaseModalLayout
data-component-id="AssetBrowserModal"
class="h-full w-full max-h-full max-w-full min-w-0"
class="size-full max-h-full max-w-full min-w-0"
:content-title="contentTitle"
@close="handleClose"
>
@@ -23,7 +23,7 @@
v-model="searchQuery"
size="lg"
placeholder="Search assets..."
class="max-w-[384px]"
class="max-w-96"
/>
</template>
@@ -80,14 +80,7 @@ const {
// Compute whether to show left panel
const shouldShowLeftPanel = computed(() => {
// If explicitly set to false, don't show
if (props.showLeftPanel === false) return false
// If explicitly set to true, always show
if (props.showLeftPanel === true) return true
// Auto-hide if only one unique asset category (excluding "All Models")
return availableCategories.value.length >= 3
return props.showLeftPanel ?? availableCategories.value.length >= 3
})
// Handle close button - call both the prop callback and emit the event

View File

@@ -46,7 +46,8 @@ type Story = StoryObj<typeof meta>
// Default story with all data provided
export const Default: Story = {
args: {
asset: createAssetData()
asset: createAssetData(),
interactive: true
},
parameters: {
docs: {
@@ -58,6 +59,22 @@ export const Default: Story = {
}
}
// Non-interactive story
export const NonInteractive: Story = {
args: {
asset: createAssetData(),
interactive: false
},
parameters: {
docs: {
description: {
story:
'AssetCard in non-interactive mode - renders as div without button semantics.'
}
}
}
}
// Story with all edge cases in a grid layout
export const EdgeCases: Story = {
render: () => ({
@@ -139,6 +156,7 @@ export const EdgeCases: Story = {
v-for="asset in edgeCases"
:key="asset.id"
:asset="asset"
:interactive="true"
@select="(asset) => console.log('Selected:', asset)"
/>
</div>

View File

@@ -1,23 +1,31 @@
<template>
<div
<component
:is="interactive ? 'button' : 'div'"
data-component-id="AssetCard"
:data-asset-id="asset.id"
role="button"
tabindex="0"
:aria-label="`Select asset ${asset.name}`"
v-bind="elementProps"
:class="
cn(
'rounded-xl overflow-hidden cursor-pointer transition-all duration-200 min-w-60 max-w-64',
'bg-ivory-100 border border-gray-300',
'dark-theme:bg-charcoal-400 dark-theme:border-charcoal-600',
'hover:transform hover:-translate-y-0.5 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] hover:border-gray-400',
'dark-theme:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)] dark-theme:hover:border-charcoal-700',
'focus:outline-none focus:ring-2 focus:ring-blue-500 dark-theme:focus:ring-blue-400'
// Base layout and container styles (always applied)
'rounded-xl overflow-hidden transition-all duration-200 min-w-60 max-w-64',
// Button-specific styles
interactive && [
'appearance-none bg-transparent p-0 m-0 font-inherit text-inherit outline-none cursor-pointer text-left',
'bg-ivory-100 border border-gray-300 dark-theme:bg-charcoal-400 dark-theme:border-charcoal-600',
'hover:transform hover:-translate-y-0.5 hover:shadow-lg hover:shadow-black/10 hover:border-gray-400',
'dark-theme:hover:shadow-lg dark-theme:hover:shadow-black/30 dark-theme:hover:border-charcoal-700',
'focus:outline-none focus:ring-2 focus:ring-blue-500 dark-theme:focus:ring-blue-400'
],
// Div-specific styles
!interactive && [
'bg-ivory-100 border border-gray-300',
'dark-theme:bg-charcoal-400 dark-theme:border-charcoal-600'
]
)
"
@click="$emit('select', asset)"
@keydown.enter="$emit('select', asset)"
@keydown.space.prevent="$emit('select', asset)"
@click="interactive && $emit('select', asset)"
@keydown.enter="interactive && $emit('select', asset)"
@keydown.space.prevent="interactive && $emit('select', asset)"
>
<div class="relative w-full aspect-square overflow-hidden">
<div
@@ -72,19 +80,31 @@
</span>
</div>
</div>
</div>
</component>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
import { cn } from '@/utils/tailwindUtil'
import AssetBadgeGroup from './AssetBadgeGroup.vue'
defineProps<{
const props = defineProps<{
asset: AssetDisplayItem
interactive?: boolean
}>()
const elementProps = computed(() =>
props.interactive
? {
type: 'button',
'aria-label': `Select asset ${props.asset.name}`
}
: {}
)
defineEmits<{
select: [asset: AssetDisplayItem]
}>()

View File

@@ -12,6 +12,7 @@
v-for="(asset, index) in assets"
:key="asset.id"
:asset="asset"
:interactive="true"
role="gridcell"
:aria-posinset="index + 1"
@select="$emit('assetSelect', $event)"