feat: media page T1
This commit is contained in:
1 parent
67d456baf6
commit
008b0dfbb9
46 files changed
+5742
-204
No files matched your search
@@ -1,17 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import MediaCardBadges from '@/components/MediaCardBadges.vue'
|
||||
import MediaCheckbox from '@/components/MediaCheckbox.vue'
|
||||
import MediaThumb from '@/components/MediaThumb.vue'
|
||||
import type { MediaItem } from '@/composables/useMedia'
|
||||
import { accessibleName, formatDimensions, formatSize } from '@/lib/mediaFormat'
|
||||
|
||||
const SELECTED_CARD =
|
||||
'border-primary bg-secondary-200 text-primary-900 inset-ring-1 inset-ring-primary dark:bg-secondary-300'
|
||||
const IDLE_CARD = 'border-default bg-default text-default'
|
||||
|
||||
const props = defineProps<{
|
||||
items: MediaItem[]
|
||||
columns: number
|
||||
selected: number[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
open: [id: number, opener: HTMLElement]
|
||||
toggle: [id: number]
|
||||
}>()
|
||||
|
||||
const grid = ref<HTMLElement | null>(null)
|
||||
@@ -38,6 +45,9 @@ function targetIndex(key: string, index: number) {
|
||||
function onKeydown(event: KeyboardEvent, item: MediaItem, index: number) {
|
||||
if (event.key === ' ') {
|
||||
event.preventDefault()
|
||||
if (!event.repeat) {
|
||||
emit('toggle', item.id)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
@@ -62,31 +72,66 @@ function onKeyup(event: KeyboardEvent) {
|
||||
|
||||
<template>
|
||||
<div ref="grid" class="grid grid-cols-2 gap-2.5 md:grid-cols-5 md:gap-4">
|
||||
<button
|
||||
v-for="(item, index) in items"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
data-media-card
|
||||
:data-media-id="item.id"
|
||||
:aria-label="accessibleName(item)"
|
||||
class="flex min-w-0 flex-col border border-default bg-default text-start text-default transition-shadow hover:border-accented hover:shadow-(--shadow-popover) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
||||
@click="emit('open', item.id, $event.currentTarget as HTMLElement)"
|
||||
@keydown="onKeydown($event, item, index)"
|
||||
@keyup="onKeyup"
|
||||
>
|
||||
<MediaThumb :item="item" class="aspect-4/3 w-full border-b border-default" />
|
||||
<span class="flex min-w-0 flex-col gap-1.5 px-3 py-2.5">
|
||||
<span class="flex min-w-0 flex-col gap-0.5">
|
||||
<span class="truncate font-medium text-highlighted">{{ item.filename }}</span>
|
||||
<span class="truncate text-xs/5 text-muted"
|
||||
>{{ formatSize(item.size) }} ·
|
||||
{{ formatDimensions(item.width, item.height) }}</span
|
||||
>
|
||||
<div v-for="(item, index) in items" :key="item.id" class="group/card relative min-w-0">
|
||||
<button
|
||||
type="button"
|
||||
data-media-card
|
||||
:data-media-id="item.id"
|
||||
:data-selected="selected.includes(item.id) || undefined"
|
||||
:aria-label="accessibleName(item)"
|
||||
class="flex w-full min-w-0 flex-col border text-start transition-shadow focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
||||
:class="
|
||||
selected.includes(item.id)
|
||||
? SELECTED_CARD
|
||||
: [IDLE_CARD, 'hover:border-accented hover:shadow-(--shadow-popover)']
|
||||
"
|
||||
@click="emit('open', item.id, $event.currentTarget as HTMLElement)"
|
||||
@keydown="onKeydown($event, item, index)"
|
||||
@keyup="onKeyup"
|
||||
>
|
||||
<MediaThumb
|
||||
:item="item"
|
||||
class="aspect-4/3 w-full border-b"
|
||||
:class="selected.includes(item.id) ? 'border-primary' : 'border-default'"
|
||||
/>
|
||||
<span class="flex min-w-0 flex-col gap-1.5 px-3 py-2.5">
|
||||
<span class="flex min-w-0 flex-col gap-0.5">
|
||||
<span
|
||||
class="truncate font-medium"
|
||||
:class="
|
||||
selected.includes(item.id) ? 'text-primary-900' : 'text-highlighted'
|
||||
"
|
||||
>{{ item.filename }}</span
|
||||
>
|
||||
<span
|
||||
class="truncate text-xs/5"
|
||||
:class="selected.includes(item.id) ? 'text-primary-600' : 'text-muted'"
|
||||
>{{ formatSize(item.size) }} ·
|
||||
{{ formatDimensions(item.width, item.height) }}</span
|
||||
>
|
||||
</span>
|
||||
<span class="flex flex-wrap gap-1">
|
||||
<MediaCardBadges :item="item" />
|
||||
</span>
|
||||
</span>
|
||||
<span class="flex flex-wrap gap-1">
|
||||
<MediaCardBadges :item="item" />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</button>
|
||||
<div
|
||||
data-testid="card-checkbox"
|
||||
class="absolute top-0 left-0 flex size-11 items-center justify-center md:size-9"
|
||||
:class="
|
||||
selected.length > 0
|
||||
? 'opacity-100'
|
||||
: 'max-md:opacity-100 md:opacity-0 md:group-hover/card:opacity-100 md:group-has-[:focus-visible]/card:opacity-100'
|
||||
"
|
||||
@click.self="emit('toggle', item.id)"
|
||||
>
|
||||
<MediaCheckbox
|
||||
:state="selected.includes(item.id) ? 'checked' : 'unchecked'"
|
||||
:label="`Select ${item.filename}`"
|
||||
:tabbable="false"
|
||||
@toggle="emit('toggle', item.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user