feat(SPA): media page T0

This commit is contained in:
fsylvain committed 2026-09-30 19:45:47 +02:00
1 parent 0d99544579
commit 67d456baf6
32 files changed
+4874 -40

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
<script setup lang="ts">
import { ref } from 'vue'
import MediaCardBadges from '@/components/MediaCardBadges.vue'
import MediaThumb from '@/components/MediaThumb.vue'
import type { MediaItem } from '@/composables/useMedia'
import { accessibleName, formatDimensions, formatSize } from '@/lib/mediaFormat'
const props = defineProps<{
items: MediaItem[]
columns: number
}>()
const emit = defineEmits<{
open: [id: number, opener: HTMLElement]
}>()
const grid = ref<HTMLElement | null>(null)
function cards() {
return Array.from(grid.value?.querySelectorAll<HTMLElement>('[data-media-card]') ?? [])
}
function targetIndex(key: string, index: number) {
const steps: Record<string, number> = {
ArrowLeft: -1,
ArrowRight: 1,
ArrowUp: -props.columns,
ArrowDown: props.columns,
}
const step = steps[key]
if (step === undefined) {
return null
}
const next = index + step
return next >= 0 && next < props.items.length ? next : index
}
function onKeydown(event: KeyboardEvent, item: MediaItem, index: number) {
if (event.key === ' ') {
event.preventDefault()
return
}
if (event.key === 'Enter') {
event.preventDefault()
emit('open', item.id, event.currentTarget as HTMLElement)
return
}
const target = targetIndex(event.key, index)
if (target === null) {
return
}
event.preventDefault()
cards()[target]?.focus()
}
function onKeyup(event: KeyboardEvent) {
if (event.key === ' ') {
event.preventDefault()
}
}
</script>
<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
>
</span>
<span class="flex flex-wrap gap-1">
<MediaCardBadges :item="item" />
</span>
</span>
</button>
</div>
</template>