Skip to content

Commit ada0993

Browse files
authored
feat: Improve MediaAssetCard design and add responsive sidebar footer (#6749)
## Summary Implements design feedback for the asset panel, improving visual hierarchy, contrast, and responsiveness based on design tokens update. ## Changes ### 🎨 Design System Updates (style.css) - **New tokens for MediaAssetCard states:** - `--modal-card-background-hovered`: Hover state background - `--modal-card-border-highlighted`: Selected state border color - **Updated tag contrast:** - Light mode: `smoke-200` → `smoke-400` - Dark mode: `charcoal-200` → `ash-800` - **Registered tokens in Tailwind** via `@theme inline` for proper class generation ### 🖼️ MediaAssetCard Improvements - **Added tooltips** to all interactive buttons: - Zoom button: "Inspect" - More button: "More options" - Output count button: "See more outputs" - **Fixed tooltip event conflicts** by wrapping buttons in tooltip divs - **Updated hover/selected states:** - Hover: Uses `--modal-card-background-hovered` for subtle highlight - Selected: Uses `--modal-card-border-highlighted` for border only (no background) - **Updated placeholder background** to use `--modal-card-placeholder-background` - **Tag styling:** Changed from `variant="light"` to `variant="gray"` for better contrast ### 📦 SquareChip Component - **Added `gray` variant** that uses `--modal-card-tag-background` token - Maintains consistency with design system tokens ### 📱 AssetsSidebarTab Responsive Footer - **Responsive button display:** - Width > 350px: Shows icon + text buttons - Width ≤ 350px: Shows icon-only buttons - **Text alignment:** Left-aligns selection count text in compact mode - **Uses `useResizeObserver`** for automatic width detection ### 🌐 Internationalization - Added new i18n keys for tooltips: - `mediaAsset.actions.inspect` - `mediaAsset.actions.more` - `mediaAsset.actions.seeMoreOutputs` ### 🔧 Minor Fixes - **Media3DTop:** Improved text size and icon color for better visual hierarchy ## Visual Changes - **Increased contrast** for asset card tags (more visible in both themes) - **Hover state** now provides clear visual feedback - **Selected state** uses border highlight instead of background fill - **Sidebar footer** gracefully adapts to narrow widths ## Related - Addresses feedback from: https://www.notion.so/comfy-org/Asset-panel-feedback-2aa6d73d3650800baacaf739a49360b3 - Design token updates by @alex Tov ## Test Plan - [ ] Verify asset card hover states in both light and dark themes - [ ] Verify asset card selected states show highlighted border - [ ] Test tooltips on all MediaAssetCard buttons - [ ] Resize sidebar to < 350px and verify footer shows icon-only buttons - [ ] Resize sidebar to > 350px and verify footer shows icon + text buttons - [ ] Verify tag contrast improvement in both themes - [ ] Test 3D asset placeholder appearance ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6749-feat-Improve-MediaAssetCard-design-and-add-responsive-sidebar-footer-2b06d73d365081019b90e110df2f1ae8) by [Unito](https://www.unito.io)
1 parent e427150 commit ada0993

File tree

6 files changed

+125
-69
lines changed

6 files changed

+125
-69
lines changed

packages/design-system/src/css/style.css

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,13 @@
268268
--palette-interface-button-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 82%, var(--contrast-mix-color));
269269

270270
--modal-card-background: var(--secondary-background);
271+
--modal-card-background-hovered: var(--secondary-background-hover);
272+
--modal-card-border-highlighted: var(--secondary-background-selected);
271273
--modal-card-button-surface: var(--color-smoke-300);
272274
--modal-card-placeholder-background: var(--color-smoke-600);
273-
--modal-card-tag-background: var(--color-smoke-200);
275+
--modal-card-tag-background: var(--color-smoke-400);
274276
--modal-card-tag-foreground: var(--base-foreground);
275277
--modal-panel-background: var(--color-white);
276-
277278
}
278279

279280
.dark-theme {
@@ -377,9 +378,11 @@
377378
--component-node-widget-background-highlighted: var(--color-graphite-400);
378379

379380
--modal-card-background: var(--secondary-background);
381+
--modal-card-background-hovered: var(--secondary-background-hover);
382+
--modal-card-border-highlighted: var(--color-ash-400);
380383
--modal-card-button-surface: var(--color-charcoal-300);
381384
--modal-card-placeholder-background: var(--secondary-background);
382-
--modal-card-tag-background: var(--color-charcoal-200);
385+
--modal-card-tag-background: var(--color-ash-800);
383386
--modal-card-tag-foreground: var(--base-foreground);
384387
--modal-panel-background: var(--color-charcoal-600);
385388

@@ -395,12 +398,14 @@
395398
--color-subscription-button-gradient: var(--subscription-button-gradient);
396399

397400
--color-modal-card-background: var(--modal-card-background);
401+
--color-modal-card-background-hovered: var(--modal-card-background-hovered);
402+
--color-modal-card-border-highlighted: var(--modal-card-border-highlighted);
398403
--color-modal-card-button-surface: var(--modal-card-button-surface);
399404
--color-modal-card-placeholder-background: var(--modal-card-placeholder-background);
400405
--color-modal-card-tag-background: var(--modal-card-tag-background);
401406
--color-modal-card-tag-foreground: var(--modal-card-tag-foreground);
402407
--color-modal-panel-background: var(--modal-panel-background);
403-
408+
404409
--color-dialog-surface: var(--dialog-surface);
405410
--color-interface-menu-component-surface-hovered: var(
406411
--interface-menu-component-surface-hovered

src/components/chip/SquareChip.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ import { cn } from '@/utils/tailwindUtil'
1111
1212
const { label, variant = 'dark' } = defineProps<{
1313
label: string
14-
variant?: 'dark' | 'light'
14+
variant?: 'dark' | 'light' | 'gray'
1515
}>()
1616
1717
const baseClasses =
1818
'inline-flex shrink-0 items-center justify-center gap-1 rounded px-2 py-1 text-xs font-bold'
1919
2020
const variantStyles = {
2121
dark: 'bg-zinc-500/40 text-white/90',
22-
light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground')
22+
light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground'),
23+
gray: cn(
24+
'backdrop-blur-[2px] bg-modal-card-tag-background text-base-foreground'
25+
)
2326
}
2427
2528
const chipClasses = computed(() => {

src/components/sidebar/tabs/AssetsSidebarTab.vue

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -97,43 +97,62 @@
9797
<template #footer>
9898
<div
9999
v-if="hasSelection"
100+
ref="footerRef"
100101
class="flex gap-1 h-18 w-full items-center justify-between"
101102
>
102-
<div ref="selectionCountButtonRef" class="flex-1 pl-4">
103-
<TextButton
104-
:label="
105-
isHoveringSelectionCount
106-
? $t('mediaAsset.selection.deselectAll')
107-
: $t('mediaAsset.selection.selectedCount', {
108-
count: totalOutputCount
109-
})
110-
"
111-
type="transparent"
112-
@click="handleDeselectAll"
113-
/>
103+
<div class="flex-1 pl-4">
104+
<div ref="selectionCountButtonRef" class="inline-flex w-48">
105+
<TextButton
106+
:label="
107+
isHoveringSelectionCount
108+
? $t('mediaAsset.selection.deselectAll')
109+
: $t('mediaAsset.selection.selectedCount', {
110+
count: totalOutputCount
111+
})
112+
"
113+
type="transparent"
114+
:class="isCompact ? 'text-left' : ''"
115+
@click="handleDeselectAll"
116+
/>
117+
</div>
114118
</div>
115119
<div class="flex gap-2 pr-4">
116-
<IconTextButton
117-
v-if="shouldShowDeleteButton"
118-
:label="$t('mediaAsset.selection.deleteSelected')"
119-
type="secondary"
120-
icon-position="right"
121-
@click="handleDeleteSelected"
122-
>
123-
<template #icon>
120+
<template v-if="isCompact">
121+
<!-- Compact mode: Icon only -->
122+
<IconButton
123+
v-if="shouldShowDeleteButton"
124+
@click="handleDeleteSelected"
125+
>
124126
<i class="icon-[lucide--trash-2] size-4" />
125-
</template>
126-
</IconTextButton>
127-
<IconTextButton
128-
:label="$t('mediaAsset.selection.downloadSelected')"
129-
type="secondary"
130-
icon-position="right"
131-
@click="handleDownloadSelected"
132-
>
133-
<template #icon>
127+
</IconButton>
128+
<IconButton @click="handleDownloadSelected">
134129
<i class="icon-[lucide--download] size-4" />
135-
</template>
136-
</IconTextButton>
130+
</IconButton>
131+
</template>
132+
<template v-else>
133+
<!-- Normal mode: Icon + Text -->
134+
<IconTextButton
135+
v-if="shouldShowDeleteButton"
136+
:label="$t('mediaAsset.selection.deleteSelected')"
137+
type="secondary"
138+
icon-position="right"
139+
@click="handleDeleteSelected"
140+
>
141+
<template #icon>
142+
<i class="icon-[lucide--trash-2] size-4" />
143+
</template>
144+
</IconTextButton>
145+
<IconTextButton
146+
:label="$t('mediaAsset.selection.downloadSelected')"
147+
type="secondary"
148+
icon-position="right"
149+
@click="handleDownloadSelected"
150+
>
151+
<template #icon>
152+
<i class="icon-[lucide--download] size-4" />
153+
</template>
154+
</IconTextButton>
155+
</template>
137156
</div>
138157
</div>
139158
</template>
@@ -145,11 +164,12 @@
145164
</template>
146165

147166
<script setup lang="ts">
148-
import { useDebounceFn, useElementHover } from '@vueuse/core'
167+
import { useDebounceFn, useElementHover, useResizeObserver } from '@vueuse/core'
149168
import ProgressSpinner from 'primevue/progressspinner'
150169
import { useToast } from 'primevue/usetoast'
151170
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
152171
172+
import IconButton from '@/components/button/IconButton.vue'
153173
import IconTextButton from '@/components/button/IconTextButton.vue'
154174
import TextButton from '@/components/button/TextButton.vue'
155175
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
@@ -221,6 +241,22 @@ const {
221241
222242
const { downloadMultipleAssets, deleteMultipleAssets } = useMediaAssetActions()
223243
244+
// Footer responsive behavior
245+
const footerRef = ref<HTMLElement | null>(null)
246+
const footerWidth = ref(0)
247+
248+
// Track footer width changes
249+
useResizeObserver(footerRef, (entries) => {
250+
const entry = entries[0]
251+
footerWidth.value = entry.contentRect.width
252+
})
253+
254+
// Determine if we should show compact mode (icon only)
255+
// Threshold: 350px or less shows icon only
256+
const isCompact = computed(
257+
() => footerWidth.value > 0 && footerWidth.value <= 350
258+
)
259+
224260
// Hover state for selection count button
225261
const selectionCountButtonRef = ref<HTMLElement | null>(null)
226262
const isHoveringSelectionCount = useElementHover(selectionCountButtonRef)

src/locales/en/main.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,11 @@
21022102
"assetDeletedSuccessfully": "Asset deleted successfully",
21032103
"deletingImportedFilesCloudOnly": "Deleting imported files is only supported in cloud version",
21042104
"failedToDeleteAsset": "Failed to delete asset",
2105+
"actions": {
2106+
"inspect": "Inspect",
2107+
"more": "More options",
2108+
"seeMoreOutputs": "See more outputs"
2109+
},
21052110
"jobIdToast": {
21062111
"jobIdCopied": "Job ID copied to clipboard",
21072112
"jobIdCopyFailed": "Failed to copy Job ID",

src/platform/assets/components/Media3DTop.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<div
44
class="flex size-full flex-col items-center justify-center gap-2 bg-modal-card-placeholder-background transition-transform duration-300 group-hover:scale-105 group-data-[selected=true]:scale-105"
55
>
6-
<i class="icon-[lucide--box] text-3xl text-base-foreground" />
7-
<span class="text-base-foreground">{{
6+
<i class="icon-[lucide--box] text-3xl text-muted-foreground" />
7+
<span class="text-sm text-base-foreground">{{
88
$t('assetBrowser.media.threeDModelPlaceholder')
99
}}</span>
1010
</div>

src/platform/assets/components/MediaAssetCard.vue

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<!-- Loading State -->
2828
<template v-if="loading">
2929
<div
30-
class="size-full animate-pulse rounded-lg bg-modal-card-button-surface"
30+
class="size-full animate-pulse rounded-lg bg-modal-card-placeholder-background"
3131
/>
3232
</template>
3333

@@ -51,44 +51,51 @@
5151
<div v-if="showStaticChips" class="flex flex-wrap items-center gap-1">
5252
<SquareChip
5353
v-if="formattedDuration"
54-
variant="light"
54+
variant="gray"
5555
:label="formattedDuration"
5656
/>
57-
<SquareChip v-if="fileFormat" variant="light" :label="fileFormat" />
57+
<SquareChip v-if="fileFormat" variant="gray" :label="fileFormat" />
5858
</div>
5959

6060
<!-- Media actions - show on hover or when playing -->
6161
<IconGroup v-else-if="showActionsOverlay">
62-
<IconButton
63-
size="sm"
64-
@click.stop="handleZoomClick"
65-
@mouseenter="handleOverlayMouseEnter"
66-
@mouseleave="handleOverlayMouseLeave"
67-
>
68-
<i class="icon-[lucide--zoom-in] size-4" />
69-
</IconButton>
70-
<MoreButton
71-
size="sm"
72-
@menu-opened="isMenuOpen = true"
73-
@menu-closed="isMenuOpen = false"
74-
@mouseenter="handleOverlayMouseEnter"
75-
@mouseleave="handleOverlayMouseLeave"
76-
>
77-
<template #default="{ close }">
78-
<MediaAssetMoreMenu
79-
:close="close"
80-
:show-delete-button="showDeleteButton"
81-
@inspect="handleZoomClick"
82-
@asset-deleted="handleAssetDelete"
83-
/>
84-
</template>
85-
</MoreButton>
62+
<div v-tooltip.top="$t('mediaAsset.actions.inspect')">
63+
<IconButton
64+
size="sm"
65+
@click.stop="handleZoomClick"
66+
@mouseenter="handleOverlayMouseEnter"
67+
@mouseleave="handleOverlayMouseLeave"
68+
>
69+
<i class="icon-[lucide--zoom-in] size-4" />
70+
</IconButton>
71+
</div>
72+
<div v-tooltip.top="$t('mediaAsset.actions.more')">
73+
<MoreButton
74+
size="sm"
75+
@menu-opened="isMenuOpen = true"
76+
@menu-closed="isMenuOpen = false"
77+
@mouseenter="handleOverlayMouseEnter"
78+
@mouseleave="handleOverlayMouseLeave"
79+
>
80+
<template #default="{ close }">
81+
<MediaAssetMoreMenu
82+
:close="close"
83+
:show-delete-button="showDeleteButton"
84+
@inspect="handleZoomClick"
85+
@asset-deleted="handleAssetDelete"
86+
/>
87+
</template>
88+
</MoreButton>
89+
</div>
8690
</IconGroup>
8791
</template>
8892

8993
<!-- Output count (top-right) -->
9094
<template v-if="showOutputCount" #top-right>
9195
<IconTextButton
96+
v-tooltip.top.pt:pointer-events-none="
97+
$t('mediaAsset.actions.seeMoreOutputs')
98+
"
9299
type="secondary"
93100
size="sm"
94101
:label="String(outputCount)"
@@ -251,8 +258,8 @@ const containerClasses = computed(() =>
251258
cn(
252259
'gap-1 select-none group',
253260
selected
254-
? 'ring-3 ring-inset ring-base-foreground bg-modal-card-background'
255-
: 'hover:bg-modal-card-background'
261+
? 'ring-3 ring-inset ring-modal-card-border-highlighted'
262+
: 'hover:bg-modal-card-background-hovered'
256263
)
257264
)
258265

0 commit comments

Comments
 (0)