Skip to content

Commit 8dfdac3

Browse files
authored
[UI] Redesign media asset card layout (#6541)
## Summary - Reorganized media asset card layout for improved UX - Moved duration/format chips to top-left (visible in default state) - Replaced multiple action buttons with zoom + more menu pattern - Repositioned output count to top-right for better visibility ## Changes 1. **Duration & format chips**: Moved from bottom-left to top-left, shown when card is not hovered and media is not playing 2. **Media actions**: Simplified to zoom button + more menu (contains delete, download options), shown on hover or during playback 3. **Output count**: Relocated from bottom-right to top-right for consistent positioning ## Test plan - [x] Verify duration/format chips appear in top-left when card is idle - [x] Confirm action buttons (zoom + more) appear on hover - [x] Check output count displays correctly in top-right - [x] Test transitions between hover/non-hover states - [x] Verify media playback doesn't interfere with UI elements 🤖 Generated with [Claude Code](https://claude.ai/code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6541-UI-Redesign-media-asset-card-layout-29f6d73d365081eb8614d5dc9b2dc214) by [Unito](https://www.unito.io)
1 parent 0692253 commit 8dfdac3

File tree

5 files changed

+49
-149
lines changed

5 files changed

+49
-149
lines changed

src/components/sidebar/tabs/AssetsSidebarTab.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil'
170170
171171
import AssetsSidebarTemplate from './AssetSidebarTemplate.vue'
172172
173-
const activeTab = ref<'input' | 'output'>('input')
173+
const activeTab = ref<'input' | 'output'>('output')
174174
const folderPromptId = ref<string | null>(null)
175175
const folderExecutionTime = ref<number | undefined>(undefined)
176176
const isInFolderView = computed(() => folderPromptId.value !== null)

src/platform/assets/components/MediaAssetActions.vue

Lines changed: 0 additions & 77 deletions
This file was deleted.

src/platform/assets/components/MediaAssetCard.vue

Lines changed: 46 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -44,48 +44,49 @@
4444
/>
4545
</template>
4646

47-
<!-- Actions overlay (top-left) - show on hover or when menu is open -->
48-
<template v-if="showActionsOverlay" #top-left>
49-
<MediaAssetActions
50-
:show-delete-button="showDeleteButton ?? true"
51-
@menu-state-changed="isMenuOpen = $event"
52-
@inspect="handleZoomClick"
53-
@asset-deleted="handleAssetDelete"
54-
@mouseenter="handleOverlayMouseEnter"
55-
@mouseleave="handleOverlayMouseLeave"
56-
/>
57-
</template>
58-
59-
<!-- Zoom button (top-right) - show on hover for all media types -->
60-
<template v-if="showZoomOverlay" #top-right>
61-
<IconButton
62-
size="sm"
63-
@click.stop="handleZoomClick"
64-
@mouseenter="handleOverlayMouseEnter"
65-
@mouseleave="handleOverlayMouseLeave"
66-
>
67-
<i class="icon-[lucide--zoom-in] size-4" />
68-
</IconButton>
69-
</template>
70-
71-
<!-- Duration/Format chips (bottom-left) - show on hover even when playing -->
72-
<template v-if="showDurationChips || showFileFormatChip" #bottom-left>
73-
<div
74-
class="flex flex-wrap items-center gap-1"
75-
@mouseenter="handleOverlayMouseEnter"
76-
@mouseleave="handleOverlayMouseLeave"
77-
>
47+
<!-- Top-left slot: Duration/Format chips OR Media actions -->
48+
<template #top-left>
49+
<!-- Duration/Format chips - show when not hovered and not playing -->
50+
<div v-if="showStaticChips" class="flex flex-wrap items-center gap-1">
7851
<SquareChip
7952
v-if="formattedDuration"
8053
variant="light"
8154
:label="formattedDuration"
8255
/>
8356
<SquareChip v-if="fileFormat" variant="light" :label="fileFormat" />
8457
</div>
58+
59+
<!-- Media actions - show on hover or when playing -->
60+
<IconGroup v-else-if="showActionsOverlay">
61+
<IconButton
62+
size="sm"
63+
@click.stop="handleZoomClick"
64+
@mouseenter="handleOverlayMouseEnter"
65+
@mouseleave="handleOverlayMouseLeave"
66+
>
67+
<i class="icon-[lucide--zoom-in] size-4" />
68+
</IconButton>
69+
<MoreButton
70+
size="sm"
71+
@menu-opened="isMenuOpen = true"
72+
@menu-closed="isMenuOpen = false"
73+
@mouseenter="handleOverlayMouseEnter"
74+
@mouseleave="handleOverlayMouseLeave"
75+
>
76+
<template #default="{ close }">
77+
<MediaAssetMoreMenu
78+
:close="close"
79+
:show-delete-button="showDeleteButton"
80+
@inspect="handleZoomClick"
81+
@asset-deleted="handleAssetDelete"
82+
/>
83+
</template>
84+
</MoreButton>
85+
</IconGroup>
8586
</template>
8687

87-
<!-- Output count (bottom-right) - show on hover even when playing -->
88-
<template v-if="showOutputCount" #bottom-right>
88+
<!-- Output count (top-right) -->
89+
<template v-if="showOutputCount" #top-right>
8990
<IconTextButton
9091
type="secondary"
9192
size="sm"
@@ -134,7 +135,9 @@ import { useElementHover } from '@vueuse/core'
134135
import { computed, defineAsyncComponent, provide, ref, toRef } from 'vue'
135136
136137
import IconButton from '@/components/button/IconButton.vue'
138+
import IconGroup from '@/components/button/IconGroup.vue'
137139
import IconTextButton from '@/components/button/IconTextButton.vue'
140+
import MoreButton from '@/components/button/MoreButton.vue'
138141
import CardBottom from '@/components/card/CardBottom.vue'
139142
import CardContainer from '@/components/card/CardContainer.vue'
140143
import CardTop from '@/components/card/CardTop.vue'
@@ -147,7 +150,7 @@ import { useMediaAssetActions } from '../composables/useMediaAssetActions'
147150
import type { AssetItem } from '../schemas/assetSchema'
148151
import type { MediaKind } from '../schemas/mediaAssetSchema'
149152
import { MediaAssetKey } from '../schemas/mediaAssetSchema'
150-
import MediaAssetActions from './MediaAssetActions.vue'
153+
import MediaAssetMoreMenu from './MediaAssetMoreMenu.vue'
151154
152155
const mediaComponents = {
153156
top: {
@@ -285,37 +288,22 @@ const isCardOrOverlayHovered = computed(
285288
() => isHovered.value || isOverlayHovered.value || isMenuOpen.value
286289
)
287290
288-
const showHoverActions = computed(
289-
() => !loading && !!asset && isCardOrOverlayHovered.value
290-
)
291-
292-
const showActionsOverlay = computed(
293-
() =>
294-
showHoverActions.value &&
295-
(!isVideoPlaying.value || isCardOrOverlayHovered.value)
296-
)
297-
298-
const showZoomOverlay = computed(
299-
() =>
300-
showHoverActions.value &&
301-
fileKind.value !== '3D' &&
302-
(!isVideoPlaying.value || isCardOrOverlayHovered.value)
303-
)
304-
305-
const showDurationChips = computed(
291+
// Show static chips when NOT hovered and NOT playing (normal state)
292+
const showStaticChips = computed(
306293
() =>
307294
!loading &&
308-
(asset?.user_metadata?.executionTimeInSeconds ||
309-
asset?.user_metadata?.duration) &&
310-
(!isVideoPlaying.value || isCardOrOverlayHovered.value)
295+
!!asset &&
296+
!isCardOrOverlayHovered.value &&
297+
!isVideoPlaying.value &&
298+
(formattedDuration.value || fileFormat.value)
311299
)
312300
313-
const showFileFormatChip = computed(
301+
// Show action overlay when hovered OR playing
302+
const showActionsOverlay = computed(
314303
() =>
315304
!loading &&
316305
!!asset &&
317-
!!fileFormat.value &&
318-
(!isVideoPlaying.value || isCardOrOverlayHovered.value)
306+
(isCardOrOverlayHovered.value || isVideoPlaying.value)
319307
)
320308
321309
const handleOverlayMouseEnter = () => {

src/platform/assets/components/MediaAssetMoreMenu.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<IconTextButton
44
v-if="asset?.kind !== '3D'"
55
type="transparent"
6-
class="text-base-foreground"
76
label="Inspect asset"
87
@click="handleInspect"
98
>
@@ -15,7 +14,6 @@
1514
<IconTextButton
1615
v-if="showWorkflowOptions"
1716
type="transparent"
18-
class="text-base-foreground"
1917
label="Add to current workflow"
2018
@click="handleAddToWorkflow"
2119
>
@@ -24,12 +22,7 @@
2422
</template>
2523
</IconTextButton>
2624

27-
<IconTextButton
28-
type="transparent"
29-
class="text-base-foreground"
30-
label="Download"
31-
@click="handleDownload"
32-
>
25+
<IconTextButton type="transparent" label="Download" @click="handleDownload">
3326
<template #icon>
3427
<i class="icon-[lucide--download] size-4" />
3528
</template>
@@ -40,7 +33,6 @@
4033
<IconTextButton
4134
v-if="showWorkflowOptions"
4235
type="transparent"
43-
class="text-base-foreground"
4436
label="Open as workflow in new tab"
4537
@click="handleOpenWorkflow"
4638
>
@@ -52,7 +44,6 @@
5244
<IconTextButton
5345
v-if="showWorkflowOptions"
5446
type="transparent"
55-
class="text-base-foreground"
5647
label="Export workflow"
5748
@click="handleExportWorkflow"
5849
>
@@ -66,7 +57,6 @@
6657
<IconTextButton
6758
v-if="showCopyJobId"
6859
type="transparent"
69-
class="text-base-foreground"
7060
label="Copy job ID"
7161
@click="handleCopyJobId"
7262
>
@@ -80,7 +70,6 @@
8070
<IconTextButton
8171
v-if="shouldShowDeleteButton"
8272
type="transparent"
83-
class="text-base-foreground"
8473
label="Delete"
8574
@click="handleDelete"
8675
>

src/types/buttonTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const getButtonTypeClasses = (type: ButtonType = 'primary') => {
2828
secondary:
2929
'bg-white border-none text-neutral-950 dark-theme:bg-zinc-700 dark-theme:text-white',
3030
transparent:
31-
'bg-transparent border-none text-neutral-600 dark-theme:text-neutral-400'
31+
'bg-transparent border-none text-neutral-600 dark-theme:text-neutral-200'
3232
} as const
3333

3434
return baseByType[type]

0 commit comments

Comments
 (0)