미디어 그리드 렌더링 분리
This commit is contained in:
135
components/admin/AdminMediaGrid.vue
Normal file
135
components/admin/AdminMediaGrid.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: 'library'
|
||||
},
|
||||
getMediaBadgeClass: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
getMediaBadgeLabel: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
getMediaGridPreviewUrl: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
getMediaItemKind: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
getMediaUsageBadgeCount: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selectedUrls: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'drag-start',
|
||||
'image-error',
|
||||
'open-detail',
|
||||
'toggle-selection'
|
||||
])
|
||||
|
||||
/**
|
||||
* 미디어 선택 여부를 확인한다.
|
||||
* @param {Object} item - 미디어 항목
|
||||
* @returns {boolean} 선택 여부
|
||||
*/
|
||||
const isMediaSelected = (item) => props.selectedUrls.includes(item?.url)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-media-grid admin-media__grid mt-5 grid grid-cols-3 gap-2 sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10">
|
||||
<div
|
||||
v-for="(item, index) in props.items"
|
||||
:key="item.url"
|
||||
class="admin-media__item group relative overflow-hidden border border-line bg-white text-left outline-none transition hover:border-[#15171a]"
|
||||
:draggable="props.activeTab === 'library'"
|
||||
@dragstart="emit('drag-start', $event, item)"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="admin-media__select-toggle absolute left-1.5 top-1.5 z-10 grid size-7 place-items-center rounded-md border-2 shadow-md outline-none transition focus-visible:ring-2 focus-visible:ring-[#15171a] focus-visible:ring-offset-1"
|
||||
:class="isMediaSelected(item)
|
||||
? 'border-white bg-[#15171a] text-white'
|
||||
: 'border-[#394047] bg-white/95 text-[#15171a] hover:border-[#15171a] hover:bg-white'"
|
||||
:aria-label="`${item.name} 선택`"
|
||||
:aria-pressed="isMediaSelected(item)"
|
||||
@click.stop="emit('toggle-selection', item, index, $event)"
|
||||
>
|
||||
<svg
|
||||
v-if="isMediaSelected(item)"
|
||||
class="size-4 shrink-0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="admin-media__thumb flex w-full flex-col text-left outline-none"
|
||||
type="button"
|
||||
@click="emit('open-detail', item)"
|
||||
>
|
||||
<span class="admin-media__thumb-frame relative block w-full overflow-hidden">
|
||||
<img
|
||||
v-if="props.getMediaItemKind(item) === 'image'"
|
||||
class="admin-media__image aspect-square w-full bg-surface object-cover"
|
||||
:src="props.getMediaGridPreviewUrl(item)"
|
||||
:alt="item.title"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@error="emit('image-error', $event, item)"
|
||||
>
|
||||
<AdminMediaVideoThumbnail
|
||||
v-else-if="props.getMediaItemKind(item) === 'video'"
|
||||
:src="item.url"
|
||||
:alt="item.title"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="admin-media__image flex aspect-square w-full items-center justify-center bg-surface text-xs font-bold uppercase tracking-[0.18em] text-muted"
|
||||
>
|
||||
{{ props.getMediaItemKind(item) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="props.getMediaUsageBadgeCount(item)"
|
||||
class="admin-media__usage-count-badge pointer-events-none absolute right-1.5 top-1.5 z-[1] rounded bg-[#15171a] px-1.5 py-0.5 text-[10px] font-semibold text-white"
|
||||
>
|
||||
{{ props.getMediaUsageBadgeCount(item) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="props.getMediaBadgeLabel(item)"
|
||||
class="admin-media__usage-badge pointer-events-none absolute right-1.5 z-[1] rounded px-1.5 py-0.5 text-[10px] font-semibold"
|
||||
:class="[
|
||||
props.getMediaBadgeClass(item),
|
||||
props.getMediaUsageBadgeCount(item) ? 'top-8' : 'top-1.5'
|
||||
]"
|
||||
>
|
||||
{{ props.getMediaBadgeLabel(item) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="admin-media__name block truncate px-2 py-1.5 text-xs font-semibold text-ink">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user