card quick action

This commit is contained in:
Gauthier Roebroeck 2026-01-13 11:51:09 +08:00
parent 8041f8731b
commit 22d01651f3
4 changed files with 59 additions and 0 deletions

View file

@ -28,6 +28,7 @@ const meta = {
width: 150,
onSelection: fn(),
onClickFab: fn(),
onClickQuickAction: fn(),
preSelect: false,
selected: false,
},
@ -84,6 +85,15 @@ export const TopRightIcon: Story = {
},
}
export const QuickActionIcon: Story = {
args: {
quickActionIcon: 'i-mdi:pencil',
},
play: ({ canvas, userEvent }) => {
userEvent.hover(canvas.getByRole('img'))
},
}
export const SelectableHover: Story = {
args: {},
play: ({ canvas, userEvent }) => {

View file

@ -50,6 +50,7 @@
transition="fade-transition"
content-class="fill-height w-100"
>
<!-- Top right number / icon -->
<v-icon-btn
v-if="!disableSelection"
:icon="
@ -63,6 +64,7 @@
@click="emit('selection', !selected)"
/>
<!-- Center FAB -->
<v-hover
v-if="isHovering && fabIcon && !hideFab"
v-slot="{ isHovering: fabHover, props: fabProps }"
@ -78,6 +80,16 @@
@click="emit('clickFab')"
/>
</v-hover>
<!-- Bottom left quick action icon -->
<v-icon-btn
v-if="isHovering && quickActionIcon && !hideQuickAction"
:icon="quickActionIcon"
variant="plain"
color="white"
class="bottom-0 left-0 position-absolute"
@click="emit('clickQuickAction')"
/>
</v-overlay>
</div>
</v-hover>
@ -145,13 +157,21 @@ const {
* Icon displayed in the top-right corner. Takes precedence over `topRight`.
*/
topRightIcon?: string
/**
* Icon displayed in the middle.
*/
fabIcon?: string
/**
* Icon displayed in the bottom-left corner.
*/
quickActionIcon?: string
}
>()
const emit = defineEmits<
ItemCardEmits & {
clickFab: []
clickQuickAction: []
}
>()
@ -164,6 +184,7 @@ const isPreSelect = computed(() => preSelect && !selected && !disableSelection)
const overlayTransparent = computed(() => (selected || isPreSelect.value) && !isHovering.value)
const overlayShown = computed(() => isHovering.value || overlayTransparent.value || preSelect)
const hideFab = computed(() => selected || isPreSelect.value)
const hideQuickAction = computed(() => selected || isPreSelect.value)
</script>
<style lang="scss">

View file

@ -3,6 +3,8 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite'
import Series from './Series.vue'
import { mockSeries1 } from '@/mocks/api/handlers/series'
import { fn } from 'storybook/test'
import { httpTyped } from '@/mocks/api/httpTyped'
import { userRegular } from '@/mocks/api/handlers/users'
const meta = {
component: Series,
@ -68,3 +70,24 @@ export const Selected: Story = {
selected: true,
},
}
export const Hover: Story = {
args: {},
play: ({ canvas, userEvent }) => {
userEvent.hover(canvas.getByRole('img'))
},
}
export const HoverNonAdmin: Story = {
args: {},
parameters: {
msw: {
handlers: [
httpTyped.get('/api/v2/users/me', ({ response }) => response(200).json(userRegular)),
],
},
},
play: ({ canvas, userEvent }) => {
userEvent.hover(canvas.getByRole('img'))
},
}

View file

@ -6,6 +6,7 @@
:top-right="unreadCount"
:top-right-icon="isRead ? 'i-mdi:check' : undefined"
fab-icon="i-mdi:play"
:quick-action-icon="quickActionIcon"
v-bind="props"
@selection="(val) => emit('selection', val)"
/>
@ -16,6 +17,7 @@ import type { components } from '@/generated/openapi/komga'
import { seriesThumbnailUrl } from '@/api/images'
import { useIntl } from 'vue-intl'
import type { ItemCardEmits, ItemCardLine, ItemCardProps, ItemCardTitle } from '@/types/ItemCard'
import { useCurrentUser } from '@/colada/users'
const intl = useIntl()
@ -71,4 +73,7 @@ const lines = computed<ItemCardLine[]>(() => {
},
]
})
const { isAdmin } = useCurrentUser()
const quickActionIcon = computed(() => (isAdmin.value ? 'i-mdi:pencil' : undefined))
</script>