관리자 팝오버 표면 공통화
This commit is contained in:
@@ -648,14 +648,18 @@ watch(() => props.member, () => {
|
|||||||
<circle cx="12" cy="12.001" r="4.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
|
<circle cx="12" cy="12.001" r="4.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div v-if="actionMenuOpen" class="admin-member-form__action-popover absolute right-0 top-12 z-20 grid w-52 overflow-hidden rounded-xl border border-line bg-white py-2 text-sm text-[#3f4650] shadow-[0_16px_44px_rgba(15,23,42,0.16)]">
|
<AdminPopoverSurface
|
||||||
|
v-if="actionMenuOpen"
|
||||||
|
root-class="admin-member-form__action-popover absolute right-0 top-12 z-20 grid w-52 rounded-xl border-line py-2 text-sm text-[#3f4650]"
|
||||||
|
clipped
|
||||||
|
>
|
||||||
<button class="admin-member-form__action-item px-4 py-2.5 text-left hover:bg-[#f3f5f7]" type="button" @click="openPasswordModal">
|
<button class="admin-member-form__action-item px-4 py-2.5 text-left hover:bg-[#f3f5f7]" type="button" @click="openPasswordModal">
|
||||||
비밀번호 변경
|
비밀번호 변경
|
||||||
</button>
|
</button>
|
||||||
<button class="admin-member-form__action-item px-4 py-2.5 text-left text-[#d21a26] hover:bg-[#fff1f2]" type="button" @click="openDeleteModal">
|
<button class="admin-member-form__action-item px-4 py-2.5 text-left text-[#d21a26] hover:bg-[#fff1f2]" type="button" @click="openDeleteModal">
|
||||||
멤버 삭제
|
멤버 삭제
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</AdminPopoverSurface>
|
||||||
</div>
|
</div>
|
||||||
<button v-else class="admin-member-form__action-icon flex h-11 w-11 items-center justify-center rounded-md border border-line bg-white text-[#3f4650]" type="button" aria-label="멤버 작업" disabled>
|
<button v-else class="admin-member-form__action-icon flex h-11 w-11 items-center justify-center rounded-md border border-line bg-white text-[#3f4650]" type="button" aria-label="멤버 작업" disabled>
|
||||||
<svg class="h-5 w-5" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="h-5 w-5" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
|||||||
@@ -843,9 +843,9 @@ defineExpose({
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<AdminPopoverSurface
|
||||||
v-if="isPageSectionSuggestionsOpen && hasPageSectionSuggestions"
|
v-if="isPageSectionSuggestionsOpen && hasPageSectionSuggestions"
|
||||||
class="admin-page-form__section-suggestions absolute left-0 right-0 top-[calc(100%+0.25rem)] z-20 max-h-48 overflow-y-auto rounded border border-[#e3e6e8] bg-white py-1 shadow-lg"
|
root-class="admin-page-form__section-suggestions absolute left-0 right-0 top-[calc(100%+0.25rem)] z-20 max-h-48 overflow-y-auto rounded border-[#e3e6e8] py-1"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-for="(option, index) in pageSectionSuggestionOptions"
|
v-for="(option, index) in pageSectionSuggestionOptions"
|
||||||
@@ -859,13 +859,14 @@ defineExpose({
|
|||||||
<span class="truncate">{{ option.name }}</span>
|
<span class="truncate">{{ option.name }}</span>
|
||||||
<span v-if="option.isSelected" class="text-xs text-[#8e9cac]">선택됨</span>
|
<span v-if="option.isSelected" class="text-xs text-[#8e9cac]">선택됨</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</AdminPopoverSurface>
|
||||||
<p
|
<AdminPopoverSurface
|
||||||
v-else-if="isPageSectionSuggestionsOpen && pageSectionInput.trim()"
|
v-else-if="isPageSectionSuggestionsOpen && pageSectionInput.trim()"
|
||||||
class="admin-page-form__section-empty absolute left-0 right-0 top-[calc(100%+0.25rem)] z-20 rounded border border-[#e3e6e8] bg-white px-3 py-2 text-xs text-[#7c8b9a] shadow-lg"
|
as="p"
|
||||||
|
root-class="admin-page-form__section-empty absolute left-0 right-0 top-[calc(100%+0.25rem)] z-20 rounded border-[#e3e6e8] px-3 py-2 text-xs text-[#7c8b9a]"
|
||||||
>
|
>
|
||||||
일치하는 섹션이 없습니다.
|
일치하는 섹션이 없습니다.
|
||||||
</p>
|
</AdminPopoverSurface>
|
||||||
</div>
|
</div>
|
||||||
<span class="admin-page-form__help text-xs text-[#7c8b9a]">
|
<span class="admin-page-form__help text-xs text-[#7c8b9a]">
|
||||||
섹션은 네비게이션의 페이지 섹션에서 추가합니다.
|
섹션은 네비게이션의 페이지 섹션에서 추가합니다.
|
||||||
|
|||||||
41
components/admin/AdminPopoverSurface.vue
Normal file
41
components/admin/AdminPopoverSurface.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
/** 렌더링할 HTML 태그 */
|
||||||
|
as: {
|
||||||
|
type: String,
|
||||||
|
default: 'div'
|
||||||
|
},
|
||||||
|
/** 팝오버 접근성 role */
|
||||||
|
role: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/** 팝오버에 추가할 화면별 클래스 */
|
||||||
|
rootClass: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/** 팝오버에 적용할 인라인 스타일 */
|
||||||
|
surfaceStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
/** 기본 overflow 숨김 사용 여부 */
|
||||||
|
clipped: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="props.as"
|
||||||
|
class="admin-popover-surface border bg-white shadow-[0_18px_50px_rgba(15,23,42,0.14)]"
|
||||||
|
:class="[clipped ? 'overflow-hidden' : '', rootClass]"
|
||||||
|
:style="surfaceStyle"
|
||||||
|
:role="role || undefined"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
@@ -2281,9 +2281,9 @@ defineExpose({
|
|||||||
<p class="mt-1 text-xs text-[#8e9cac]">
|
<p class="mt-1 text-xs text-[#8e9cac]">
|
||||||
{{ selectedTags.length }} / {{ postTagLimit }}개 선택됨
|
{{ selectedTags.length }} / {{ postTagLimit }}개 선택됨
|
||||||
</p>
|
</p>
|
||||||
<div
|
<AdminPopoverSurface
|
||||||
v-if="hasTagSuggestions"
|
v-if="hasTagSuggestions"
|
||||||
class="admin-post-form__tag-suggestions absolute left-0 right-0 top-full z-40 mt-1 max-h-64 overflow-y-auto rounded-lg border border-[#d7dce0] bg-white py-1 text-sm shadow-[0_18px_50px_rgba(15,23,42,0.14)]"
|
root-class="admin-post-form__tag-suggestions absolute left-0 right-0 top-full z-40 mt-1 max-h-64 overflow-y-auto rounded-lg border-[#d7dce0] py-1 text-sm"
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -2300,7 +2300,7 @@ defineExpose({
|
|||||||
<span class="admin-post-form__tag-suggestion-name min-w-0 truncate font-semibold text-[#15171a]">{{ tag.name }}</span>
|
<span class="admin-post-form__tag-suggestion-name min-w-0 truncate font-semibold text-[#15171a]">{{ tag.name }}</span>
|
||||||
<span class="admin-post-form__tag-suggestion-meta shrink-0 text-xs text-muted">{{ getTagSuggestionMeta(tag) }}</span>
|
<span class="admin-post-form__tag-suggestion-meta shrink-0 text-xs text-muted">{{ getTagSuggestionMeta(tag) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</AdminPopoverSurface>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -150,15 +150,16 @@ onBeforeUnmount(() => {
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div
|
<AdminPopoverSurface
|
||||||
v-if="isOpen"
|
v-if="isOpen"
|
||||||
class="admin-row-more-menu__popover fixed z-[80] overflow-hidden rounded-xl border border-[#e2e5e9] bg-white py-2 text-sm text-[#3f4650] shadow-[0_18px_50px_rgba(15,23,42,0.16)]"
|
root-class="admin-row-more-menu__popover fixed z-[80] rounded-xl border-[#e2e5e9] py-2 text-sm text-[#3f4650]"
|
||||||
:style="popoverStyle"
|
:surface-style="popoverStyle"
|
||||||
|
clipped
|
||||||
role="menu"
|
role="menu"
|
||||||
data-admin-row-menu
|
data-admin-row-menu
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</AdminPopoverSurface>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -190,6 +190,7 @@
|
|||||||
| components/admin/AdminUnsavedChangesModal.vue | 관리자 편집 화면 공통 미저장 변경사항 이탈 확인 모달(상단 40px 위치) |
|
| components/admin/AdminUnsavedChangesModal.vue | 관리자 편집 화면 공통 미저장 변경사항 이탈 확인 모달(상단 40px 위치) |
|
||||||
| components/admin/AdminConfirmModal.vue | 관리자 공통 확인 모달(제목·본문 슬롯·확인/취소 버튼, primary/danger 톤) |
|
| components/admin/AdminConfirmModal.vue | 관리자 공통 확인 모달(제목·본문 슬롯·확인/취소 버튼, primary/danger 톤) |
|
||||||
| components/admin/AdminModalFrame.vue | 관리자 공통 일반 모달 프레임(오버레이, dialog 접근성 속성, 기본 패널 스타일, 바깥 클릭 닫기) |
|
| components/admin/AdminModalFrame.vue | 관리자 공통 일반 모달 프레임(오버레이, dialog 접근성 속성, 기본 패널 스타일, 바깥 클릭 닫기) |
|
||||||
|
| components/admin/AdminPopoverSurface.vue | 관리자 공통 팝오버 표면(border·shadow·radius 기반 스타일, 동적 태그·role·인라인 위치 스타일 전달) |
|
||||||
| components/admin/AdminToast.vue | 관리자 공통 우측 상단 토스트 표시(성공·오류·안내 톤, 화면별 상단 위치 조정) |
|
| components/admin/AdminToast.vue | 관리자 공통 우측 상단 토스트 표시(성공·오류·안내 톤, 화면별 상단 위치 조정) |
|
||||||
| components/admin/AdminSearchInput.vue | 관리자 공통 검색 입력(돋보기 아이콘, IME 검색 이벤트 전달, 외부 focus/select 노출) |
|
| components/admin/AdminSearchInput.vue | 관리자 공통 검색 입력(돋보기 아이콘, IME 검색 이벤트 전달, 외부 focus/select 노출) |
|
||||||
| components/admin/AdminSelectInput.vue | 관리자 공통 select 입력(문자·숫자 옵션 값, 기본 필터·정렬 옵션, chevron 아이콘, `v-model` 전달) |
|
| components/admin/AdminSelectInput.vue | 관리자 공통 select 입력(문자·숫자 옵션 값, 기본 필터·정렬 옵션, chevron 아이콘, `v-model` 전달) |
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
- `components/admin/AdminPostForm.vue`: 태그 입력, 대표 이미지 선택, 발행/초안 저장 상태, 우측 설정 패널 책임을 분리.
|
- `components/admin/AdminPostForm.vue`: 태그 입력, 대표 이미지 선택, 발행/초안 저장 상태, 우측 설정 패널 책임을 분리.
|
||||||
- `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리.
|
- `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리.
|
||||||
- 관리자 UI 공통 컴포넌트 정리.
|
- 관리자 UI 공통 컴포넌트 정리.
|
||||||
- 필터 팝오버의 radius·shadow·z-index·색상 규칙을 공통화.
|
|
||||||
- 멤버 고급 필터와 설정 화면 select 입력의 공통 컴포넌트 적용 범위 검토.
|
- 멤버 고급 필터와 설정 화면 select 입력의 공통 컴포넌트 적용 범위 검토.
|
||||||
- 최근 브라우저 회귀 테스트 추가.
|
- 최근 브라우저 회귀 테스트 추가.
|
||||||
- 태그 추천 선택 후 다음 태그 후보가 포커스 해제 없이 유지되는지 확인.
|
- 태그 추천 선택 후 다음 태그 후보가 포커스 해제 없이 유지되는지 확인.
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.11.12
|
||||||
|
|
||||||
|
- 관리자 팝오버 표면 스타일을 공용 `AdminPopoverSurface` 컴포넌트로 분리.
|
||||||
|
- 글쓰기 태그 추천, 게시글 태그 필터, 페이지 섹션 추천, 멤버 작업 메뉴, 행 더보기 메뉴의 반복 border·shadow·radius 마크업 정리.
|
||||||
|
|
||||||
## v1.11.11
|
## v1.11.11
|
||||||
|
|
||||||
- 관리자 일반 모달 오버레이·패널을 공용 `AdminModalFrame` 컴포넌트로 분리.
|
- 관리자 일반 모달 오버레이·패널을 공용 `AdminModalFrame` 컴포넌트로 분리.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.11",
|
"version": "1.11.12",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.11",
|
"version": "1.11.12",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.11",
|
"version": "1.11.12",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
@@ -1074,10 +1074,10 @@ watch(postPageCount, (pageCount) => {
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<AdminPopoverSurface
|
||||||
v-if="isTagFilterOpen && hasTagFilterSuggestions"
|
v-if="isTagFilterOpen && hasTagFilterSuggestions"
|
||||||
id="admin-posts-tag-filter-list"
|
id="admin-posts-tag-filter-list"
|
||||||
class="admin-posts__tag-filter-list absolute left-0 right-0 top-[calc(100%+0.25rem)] z-30 max-h-64 overflow-y-auto rounded-lg border border-[#d7dce0] bg-white py-1 text-sm shadow-[0_18px_50px_rgba(15,23,42,0.14)]"
|
root-class="admin-posts__tag-filter-list absolute left-0 right-0 top-[calc(100%+0.25rem)] z-30 max-h-64 overflow-y-auto rounded-lg border-[#d7dce0] py-1 text-sm"
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -1094,13 +1094,14 @@ watch(postPageCount, (pageCount) => {
|
|||||||
<span class="min-w-0 truncate">{{ option.name }}</span>
|
<span class="min-w-0 truncate">{{ option.name }}</span>
|
||||||
<span v-if="option.slug === tagFilter" class="shrink-0 text-xs text-[#8e9cac]">선택됨</span>
|
<span v-if="option.slug === tagFilter" class="shrink-0 text-xs text-[#8e9cac]">선택됨</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</AdminPopoverSurface>
|
||||||
<p
|
<AdminPopoverSurface
|
||||||
v-else-if="isTagFilterOpen && tagFilterSearch.trim()"
|
v-else-if="isTagFilterOpen && tagFilterSearch.trim()"
|
||||||
class="admin-posts__tag-filter-empty absolute left-0 right-0 top-[calc(100%+0.25rem)] z-30 rounded border border-[#d7dce0] bg-white px-3 py-2 text-xs text-muted shadow-[0_18px_50px_rgba(15,23,42,0.14)]"
|
as="p"
|
||||||
|
root-class="admin-posts__tag-filter-empty absolute left-0 right-0 top-[calc(100%+0.25rem)] z-30 rounded border-[#d7dce0] px-3 py-2 text-xs text-muted"
|
||||||
>
|
>
|
||||||
일치하는 태그가 없습니다.
|
일치하는 태그가 없습니다.
|
||||||
</p>
|
</AdminPopoverSurface>
|
||||||
</div>
|
</div>
|
||||||
<AdminSelectInput
|
<AdminSelectInput
|
||||||
v-model="featuredFilter"
|
v-model="featuredFilter"
|
||||||
|
|||||||
Reference in New Issue
Block a user