관리자 글쓰기 전체 화면 레이아웃 보정

This commit is contained in:
2026-05-07 10:53:08 +09:00
parent 1ef50c111b
commit 9054c9625c
8 changed files with 124 additions and 87 deletions

View File

@@ -480,70 +480,69 @@ defineExpose({
</script>
<template>
<form class="admin-post-form flex min-h-[calc(100vh-2.5rem)] flex-col bg-white" @submit.prevent="submitPost">
<header class="admin-post-form__toolbar flex h-[82px] shrink-0 items-start border-b border-transparent bg-white p-6">
<div class="admin-post-form__toolbar-inner flex h-[34px] min-w-0 flex-1 items-center justify-between">
<div class="admin-post-form__toolbar-left flex h-full items-center gap-2">
<NuxtLink class="admin-post-form__toolbar-link inline-flex items-center gap-2 rounded px-3 py-1.5 text-base text-black" to="/admin/posts">
<span class="admin-post-form__toolbar-back text-lg leading-none" aria-hidden="true">&lt;</span>
<span>Posts</span>
</NuxtLink>
<span class="admin-post-form__toolbar-status rounded px-3 py-1.5 text-base text-[#8e9cac]">
{{ editorStatusLabel }}
</span>
<form class="admin-post-form flex h-screen min-h-screen overflow-hidden bg-white" @submit.prevent="submitPost">
<div class="admin-post-form__workspace flex min-w-0 flex-1 flex-col bg-white">
<header class="admin-post-form__toolbar flex h-[56px] shrink-0 items-center bg-white px-8">
<div class="admin-post-form__toolbar-inner flex h-[34px] min-w-0 flex-1 items-center justify-between">
<div class="admin-post-form__toolbar-left flex h-full min-w-0 items-center gap-3">
<NuxtLink class="admin-post-form__toolbar-link inline-flex items-center gap-2 rounded px-2 py-1.5 text-sm font-medium text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black" to="/admin/posts">
<span class="admin-post-form__toolbar-back text-lg leading-none" aria-hidden="true">&lt;</span>
<span>Posts</span>
</NuxtLink>
<span class="admin-post-form__toolbar-status truncate rounded px-2 py-1.5 text-sm text-[#8e9cac]">
{{ editorStatusLabel }}
</span>
</div>
<div class="admin-post-form__toolbar-actions flex h-full shrink-0 items-center gap-2">
<button
class="admin-post-form__toolbar-preview rounded px-3 py-1.5 text-sm font-bold text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black"
type="button"
@click="previewPost"
>
미리보기
</button>
<button
class="admin-post-form__toolbar-submit rounded px-3 py-1.5 text-sm font-bold text-[#2bba3c] transition-colors hover:bg-[#eaf8ec] hover:text-[#159624] disabled:opacity-50"
type="submit"
:disabled="saving"
>
{{ saving ? '저장 중' : submitLabel }}
</button>
<button
class="admin-post-form__settings-toggle grid size-[34px] place-items-center rounded text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black"
type="button"
:aria-pressed="isSettingsOpen"
aria-label="게시물 설정 패널 전환"
@click="toggleSettingsPanel"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2.16699C14.3242 2.16699 14.4998 2.39365 14.5 2.57129V13.4287C14.5 13.606 14.3242 13.834 14 13.834H11.5V2.16699H14ZM2 2.16699H10.5V13.834H2C1.6756 13.834 1.5 13.6064 1.5 13.4287V2.57129C1.50024 2.39409 1.67607 2.16699 2 2.16699Z" stroke="currentColor" />
</svg>
</button>
</div>
</div>
<div class="admin-post-form__toolbar-actions flex h-full items-center gap-2">
<button
class="admin-post-form__toolbar-preview rounded px-3 py-1.5 text-base font-bold text-[#394047]"
type="button"
@click="previewPost"
>
미리보기
</button>
<button
class="admin-post-form__toolbar-submit rounded px-3 py-1.5 text-base font-bold text-[#2bba3c] disabled:opacity-50"
type="submit"
:disabled="saving"
>
{{ saving ? '저장 중' : submitLabel }}
</button>
<button
class="admin-post-form__settings-toggle grid size-[34px] place-items-center rounded text-[#394047] hover:bg-[#eff1f2]"
type="button"
:aria-pressed="isSettingsOpen"
aria-label="게시물 설정 패널 전환"
@click="toggleSettingsPanel"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2.16699C14.3242 2.16699 14.4998 2.39365 14.5 2.57129V13.4287C14.5 13.606 14.3242 13.834 14 13.834H11.5V2.16699H14ZM2 2.16699H10.5V13.834H2C1.6756 13.834 1.5 13.6064 1.5 13.4287V2.57129C1.50024 2.39409 1.67607 2.16699 2 2.16699Z" stroke="#394047"/>
</svg>
</header>
<!-- <span class="admin-post-form__settings-toggle-icon block h-4 w-4 border-x-2 border-[#394047]" aria-hidden="true" /> -->
</button>
</div>
</div>
</header>
<div class="admin-post-form__main flex min-h-0 flex-1 flex-col bg-white lg:flex-row">
<section class="admin-post-form__content mx-auto w-full max-w-[804px] px-8 pb-16 pt-14">
<main class="admin-post-form__editor-scroll min-h-0 flex-1 overflow-y-auto">
<section class="admin-post-form__content mx-auto w-full max-w-[804px] px-8 pb-16 pt-32">
<div class="admin-post-form__feature-block mb-9">
<figure v-if="form.featuredImage" class="admin-post-form__featured-editor overflow-hidden rounded border border-line bg-white">
<img class="admin-post-form__featured-editor-image aspect-[16/9] w-full bg-surface object-cover" :src="form.featuredImage" alt="">
<figcaption class="admin-post-form__featured-editor-actions flex flex-wrap items-center gap-2 border-t border-line p-3">
<button class="admin-post-form__featured-change rounded border border-line px-3 py-1.5 text-xs font-semibold" type="button" @click="openMediaPicker('featuredImage')">
<button class="admin-post-form__featured-change rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]" type="button" @click="openMediaPicker('featuredImage')">
변경
</button>
<label class="admin-post-form__featured-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
<label class="admin-post-form__featured-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]">
업로드
<input class="sr-only" type="file" accept="image/*" @change="uploadFeaturedImage">
</label>
<button class="admin-post-form__featured-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700" type="button" @click="removeFeaturedImage">
<button class="admin-post-form__featured-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700 transition-colors hover:bg-red-50" type="button" @click="removeFeaturedImage">
삭제
</button>
</figcaption>
</figure>
<div v-else class="admin-post-form__feature-empty flex h-6 items-start">
<button class="admin-post-form__feature-add inline-flex items-center gap-1.5 text-sm text-[#8e9cac]" type="button" @click="openMediaPicker('featuredImage')">
<button class="admin-post-form__feature-add inline-flex items-center gap-1.5 rounded px-2 py-1 text-sm text-[#8e9cac] transition-colors hover:bg-[#eff1f2] hover:text-[#394047]" type="button" @click="openMediaPicker('featuredImage')">
<span aria-hidden="true">+</span>
<span>대표 이미지 추가</span>
</button>
@@ -567,10 +566,10 @@ defineExpose({
{{ formatAutosaveTime(autosaveNotice.savedAt) }} 저장된 작성 내용이 있습니다.
</p>
<div class="admin-post-form__autosave-actions flex gap-2">
<button class="admin-post-form__autosave-restore rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white" type="button" @click="restoreAutosave">
<button class="admin-post-form__autosave-restore rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-black" type="button" @click="restoreAutosave">
복원
</button>
<button class="admin-post-form__autosave-discard rounded border border-line bg-white px-3 py-1.5 text-xs font-semibold" type="button" @click="discardAutosave">
<button class="admin-post-form__autosave-discard rounded border border-line bg-white px-3 py-1.5 text-xs font-semibold transition-colors hover:bg-[#eff1f2]" type="button" @click="discardAutosave">
삭제
</button>
</div>
@@ -579,22 +578,29 @@ defineExpose({
<div class="admin-post-form__field admin-post-form__content-editor text-sm">
<AdminBlockEditor ref="blockEditor" v-model="form.content" />
</div>
</section>
</section>
</main>
</div>
<aside v-if="isSettingsOpen" class="admin-post-form__settings flex h-auto w-full shrink-0 flex-col border-t border-[#e3e6e8] bg-white lg:w-[420px] lg:border-l lg:border-t-0">
<div class="admin-post-form__settings-header flex h-[82px] shrink-0 items-center justify-between px-6">
<aside
class="admin-post-form__settings flex h-screen shrink-0 flex-col overflow-hidden border-[#e3e6e8] bg-white transition-[width,border-color] duration-300 ease-out"
:class="isSettingsOpen ? 'w-[420px] border-l' : 'w-0 border-l-0'"
:aria-hidden="!isSettingsOpen"
>
<div class="admin-post-form__settings-inner flex h-full w-[420px] flex-col">
<div class="admin-post-form__settings-header flex h-[56px] shrink-0 items-center justify-between px-6">
<h2 class="admin-post-form__settings-title text-xl font-bold text-black">
게시물 설정
</h2>
<button class="admin-post-form__settings-close grid size-6 place-items-center text-[#394047]" type="button" aria-label="게시물 설정 닫기" @click="toggleSettingsPanel">
<button class="admin-post-form__settings-close grid size-8 place-items-center rounded text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black" type="button" aria-label="게시물 설정 닫기" @click="toggleSettingsPanel">
<span aria-hidden="true">x</span>
</button>
</div>
<div class="admin-post-form__settings-body grid content-start gap-4 overflow-y-auto px-6 pb-8">
<div class="admin-post-form__settings-body grid content-start gap-4 overflow-y-auto px-6 pb-8 pt-8">
<label class="admin-post-form__field grid gap-2 text-sm">
<span class="admin-post-form__label font-medium">상태</span>
<select v-model="form.status" class="admin-post-form__select h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2">
<select v-model="form.status" class="admin-post-form__select h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none">
<option value="draft">초안</option>
<option value="published">발행</option>
<option value="private">비공개</option>
@@ -607,7 +613,7 @@ defineExpose({
</span>
<input
v-model="form.publishedAt"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
type="datetime-local"
>
<span class="admin-post-form__hint text-xs text-muted">
@@ -619,7 +625,7 @@ defineExpose({
<span class="admin-post-form__label font-medium">슬러그</span>
<input
v-model="form.slug"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
type="text"
pattern="[a-z0-9가-힣]+(-[a-z0-9가-힣]+)*"
required
@@ -631,7 +637,7 @@ defineExpose({
<span class="admin-post-form__label font-medium">요약</span>
<textarea
v-model="form.excerpt"
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
/>
</label>
@@ -639,7 +645,7 @@ defineExpose({
<span class="admin-post-form__label font-medium">태그</span>
<input
v-model="form.tagsText"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
type="text"
>
</label>
@@ -658,7 +664,7 @@ defineExpose({
<span class="admin-post-form__label font-medium">SEO 제목</span>
<input
v-model="form.seoTitle"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
type="text"
maxlength="80"
placeholder="비워두면 글 제목을 사용"
@@ -672,7 +678,7 @@ defineExpose({
<span class="admin-post-form__label font-medium">SEO 설명</span>
<textarea
v-model="form.seoDescription"
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
maxlength="180"
placeholder="비워두면 요약을 사용"
/>
@@ -685,7 +691,7 @@ defineExpose({
<span class="admin-post-form__label font-medium">Canonical URL</span>
<input
v-model="form.canonicalUrl"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
type="url"
placeholder="비워두면 기본 글 주소를 사용"
>
@@ -713,32 +719,32 @@ defineExpose({
{{ form.ogImage }}
</p>
<div class="admin-post-form__og-buttons flex flex-wrap gap-2">
<button class="admin-post-form__og-change rounded border border-line px-3 py-1.5 text-xs font-semibold" type="button" @click="openMediaPicker('ogImage')">
<button class="admin-post-form__og-change rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]" type="button" @click="openMediaPicker('ogImage')">
변경
</button>
<label class="admin-post-form__og-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
<label class="admin-post-form__og-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]">
업로드
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
</label>
<button class="admin-post-form__og-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700" type="button" @click="removeOgImage">
<button class="admin-post-form__og-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700 transition-colors hover:bg-red-50" type="button" @click="removeOgImage">
삭제
</button>
</div>
</figcaption>
</figure>
<div v-else class="admin-post-form__og-empty grid gap-2 rounded border border-dashed border-line bg-white p-4">
<button class="admin-post-form__og-select rounded border border-line px-3 py-2 text-sm font-semibold" type="button" @click="openMediaPicker('ogImage')">
<button class="admin-post-form__og-select rounded border border-line px-3 py-2 text-sm font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]" type="button" @click="openMediaPicker('ogImage')">
미디어에서 선택
</button>
<label class="admin-post-form__og-upload cursor-pointer rounded bg-[#15171a] px-3 py-2 text-center text-sm font-semibold text-white">
<label class="admin-post-form__og-upload cursor-pointer rounded bg-[#15171a] px-3 py-2 text-center text-sm font-semibold text-white transition-colors hover:bg-black">
{{ isUploadingOgImage ? '업로드 중' : '새 이미지 업로드' }}
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
</label>
</div>
</div>
</div>
</div>
</aside>
</div>
<div
v-if="isMediaPickerOpen"
@@ -752,7 +758,7 @@ defineExpose({
<h2 class="admin-post-form__media-picker-title text-lg font-semibold">
{{ mediaPickerTarget === 'ogImage' ? 'OG 이미지 선택' : '대표 이미지 선택' }}
</h2>
<button class="admin-post-form__media-picker-close rounded border border-line px-3 py-1.5 text-sm font-semibold" type="button" @click="closeMediaPicker">
<button class="admin-post-form__media-picker-close rounded border border-line px-3 py-1.5 text-sm font-semibold transition-colors hover:bg-[#eff1f2]" type="button" @click="closeMediaPicker">
닫기
</button>
</div>
@@ -764,7 +770,7 @@ defineExpose({
<button
v-for="item in mediaItems"
:key="item.url"
class="admin-post-form__media-picker-item overflow-hidden border border-line bg-white text-left"
class="admin-post-form__media-picker-item overflow-hidden border border-line bg-white text-left transition hover:border-[#8e9cac] hover:shadow-sm"
type="button"
@click="selectPickedImage(item)"
>