목록관리 템플릿 추가 모달 정리

This commit is contained in:
2026-04-08 09:50:19 +09:00
parent 6f8e623b56
commit 2559605318
9 changed files with 120 additions and 16 deletions

View File

@@ -1,4 +1,6 @@
<script setup>
import { computed, onUnmounted, ref, watch } from 'vue'
const props = defineProps({
featuredTemplates: { type: Array, required: true },
availableTemplatesForFeatured: { type: Array, required: true },
@@ -9,6 +11,44 @@ const props = defineProps({
removeFeaturedTemplate: { type: Function, required: true },
addFeaturedTemplate: { type: Function, required: true },
})
const featuredTemplateSearchQuery = ref('')
const featuredTemplatePickerOpen = ref(false)
const filteredAvailableTemplatesForFeatured = computed(() => {
const query = featuredTemplateSearchQuery.value.trim().toLowerCase()
if (!query) return props.availableTemplatesForFeatured
return props.availableTemplatesForFeatured.filter((template) => {
const haystack = `${template.name || ''} ${template.slug || ''} ${template.id || ''}`.toLowerCase()
return haystack.includes(query)
})
})
function openFeaturedTemplatePicker() {
featuredTemplateSearchQuery.value = ''
featuredTemplatePickerOpen.value = true
}
function closeFeaturedTemplatePicker() {
featuredTemplatePickerOpen.value = false
}
function chooseFeaturedTemplate(templateId) {
props.addFeaturedTemplate(templateId)
closeFeaturedTemplatePicker()
}
function setFeaturedTransitionListRef(el) {
props.featuredListRef(el?.$el || el)
}
watch(featuredTemplatePickerOpen, (isOpen) => {
document.body.classList.toggle('modal-scroll-lock', isOpen)
})
onUnmounted(() => {
document.body.classList.remove('modal-scroll-lock')
})
</script>
<template>
@@ -18,14 +58,23 @@ const props = defineProps({
<div class="panel__title"> 화면 상단 고정 순서</div>
<div class="hint hint--tight">여기에 넣은 템플릿은 지정한 순서대로 먼저 노출되고, 나머지 템플릿은 최근 생성순으로 뒤에 이어집니다. 최대 50개까지 설정할 있어요.</div>
</div>
<button class="btn btn--primary" @click="props.saveFeaturedOrder">순서 저장</button>
<div class="featuredOrderPanel__headActions">
<button
class="btn btn--ghost"
:disabled="props.featuredTemplateIds.length >= 50 || !props.availableTemplatesForFeatured.length"
@click="openFeaturedTemplatePicker"
>
템플릿 추가
</button>
<button class="btn btn--primary" @click="props.saveFeaturedOrder">순서 저장</button>
</div>
</div>
<div class="featuredOrderPanel">
<div class="featuredOrderPanel__list">
<div class="section__title">상단 고정 목록</div>
<div v-if="!props.featuredTemplates.length" class="hint">아직 상단 고정 템플릿이 없어요.</div>
<div v-else :ref="props.featuredListRef" class="featuredList">
<TransitionGroup v-else :ref="setFeaturedTransitionListRef" tag="div" name="featured-list" class="featuredList">
<article v-for="(template, index) in props.featuredTemplates" :key="template.id" class="featuredCard" :data-featured-id="template.id">
<div class="featuredCard__meta">
<span class="featuredCard__rank">{{ index + 1 }}</span>
@@ -41,22 +90,35 @@ const props = defineProps({
<button class="btn btn--danger btn--small" @click="props.removeFeaturedTemplate(template.id)">제외</button>
</div>
</article>
</div>
</TransitionGroup>
</div>
</div>
<div class="featuredOrderPanel__picker">
<div class="section__title">템플릿 추가</div>
<div class="featuredPickerList">
<div v-if="featuredTemplatePickerOpen" class="modalOverlay" @click.self="closeFeaturedTemplatePicker">
<div class="modalCard" role="dialog" aria-modal="true">
<div class="modalCard__titleRow">
<div>
<div class="modalCard__title">상단 고정 템플릿 추가</div>
<div class="modalCard__desc">템플릿 이름, slug, ID로 검색한 고정 목록에 추가할 항목을 선택하세요.</div>
</div>
<button class="btn btn--ghost btn--small" @click="closeFeaturedTemplatePicker">닫기</button>
</div>
<div class="modalCard__form modalCard__form--search">
<input v-model="featuredTemplateSearchQuery" class="input" placeholder="템플릿 이름 또는 slug 검색" autofocus />
<span class="hint hint--tight">{{ filteredAvailableTemplatesForFeatured.length }} 결과</span>
</div>
<div class="templatePickerModalList">
<button
v-for="template in props.availableTemplatesForFeatured"
v-for="template in filteredAvailableTemplatesForFeatured"
:key="template.id"
class="featuredPickerItem"
:disabled="props.featuredTemplateIds.length >= 50"
@click="props.addFeaturedTemplate(template.id)"
class="adminTemplatePicker__item"
type="button"
@click="chooseFeaturedTemplate(template.id)"
>
<span>{{ template.name }}</span>
<span class="featuredPickerItem__id">{{ template.slug || template.id }}</span>
<span class="adminTemplatePicker__name">{{ template.name }}</span>
<span class="adminTemplatePicker__meta">{{ template.slug || template.id }}</span>
</button>
<div v-if="!filteredAvailableTemplatesForFeatured.length" class="hint hint--tight">추가할 있는 템플릿이 없어요.</div>
</div>
</div>
</div>

View File

@@ -64,7 +64,6 @@ export function useAdminFeaturedTemplates({
const [moved] = nextIds.splice(currentIndex, 1)
nextIds.splice(nextIndex, 0, moved)
featuredTemplateIds.value = nextIds
syncFeaturedSortable()
}
async function saveFeaturedOrder() {

View File

@@ -3470,12 +3470,22 @@ function openUserProfile(user) {
color: var(--theme-text-muted);
line-height: 1.6;
}
body.modal-scroll-lock {
overflow: hidden;
overscroll-behavior: none;
}
.adminUiScope .featuredOrderPanel {
margin-top: 14px;
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.95fr);
grid-template-columns: minmax(0, 1fr);
gap: 16px;
}
.adminUiScope .featuredOrderPanel__headActions {
display: flex;
gap: 10px;
justify-content: flex-end;
flex-wrap: wrap;
}
.adminUiScope .featuredOrderPanel__list,
.adminUiScope .featuredOrderPanel__picker {
border: 1px solid rgba(255, 255, 255, 0.1);
@@ -3485,6 +3495,7 @@ function openUserProfile(user) {
}
.adminUiScope .featuredList,
.adminUiScope .featuredPickerList {
position: relative;
margin-top: 10px;
display: grid;
gap: 10px;
@@ -3501,6 +3512,20 @@ function openUserProfile(user) {
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
}
.adminUiScope .featured-list-move,
.adminUiScope .featured-list-enter-active,
.adminUiScope .featured-list-leave-active {
transition: transform 180ms ease, opacity 180ms ease;
}
.adminUiScope .featured-list-enter-from,
.adminUiScope .featured-list-leave-to {
opacity: 0;
transform: translateY(8px);
}
.adminUiScope .featured-list-leave-active {
position: absolute;
width: calc(100% - 32px);
}
.adminUiScope .featuredCard__meta {
display: flex;
gap: 12px;