관리자 검색 입력 공통화
This commit is contained in:
80
components/admin/AdminSearchInput.vue
Normal file
80
components/admin/AdminSearchInput.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
/** 검색 입력 표시값 */
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** 검색 입력 placeholder */
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '검색'
|
||||
},
|
||||
/** 스크린 리더용 입력 라벨 */
|
||||
label: {
|
||||
type: String,
|
||||
default: '검색'
|
||||
},
|
||||
/** label 루트에 추가할 화면별 클래스 */
|
||||
rootClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** input에 추가할 화면별 클래스 */
|
||||
inputClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** input 너비 클래스 */
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: 'w-52'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['input', 'compositionupdate', 'compositionend'])
|
||||
const inputRef = ref(null)
|
||||
|
||||
/**
|
||||
* 검색 입력에 포커스를 이동한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const focus = () => {
|
||||
inputRef.value?.focus()
|
||||
}
|
||||
|
||||
/**
|
||||
* 검색 입력 텍스트를 선택한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const select = () => {
|
||||
inputRef.value?.select()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
focus,
|
||||
select
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="admin-search-input relative block" :class="rootClass">
|
||||
<span class="sr-only">{{ label }}</span>
|
||||
<svg class="admin-search-input__icon pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#8e9cac]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m21 21-4.34-4.34" />
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
</svg>
|
||||
<input
|
||||
ref="inputRef"
|
||||
:value="props.value"
|
||||
class="admin-search-input__field h-10 rounded border border-line bg-white pl-9 pr-3 text-sm text-[#394047] outline-none transition-colors placeholder:text-[#8e9cac] hover:border-[#c8ced3] focus:border-[#8e9cac]"
|
||||
:class="[widthClass, inputClass]"
|
||||
type="search"
|
||||
:placeholder="placeholder"
|
||||
:aria-label="label"
|
||||
@input="emit('input', $event)"
|
||||
@compositionupdate="emit('compositionupdate', $event)"
|
||||
@compositionend="emit('compositionend', $event)"
|
||||
>
|
||||
</label>
|
||||
</template>
|
||||
@@ -188,6 +188,7 @@
|
||||
| components/admin/AdminUnsavedChangesModal.vue | 관리자 편집 화면 공통 미저장 변경사항 이탈 확인 모달(상단 40px 위치) |
|
||||
| components/admin/AdminConfirmModal.vue | 관리자 공통 확인 모달(제목·본문 슬롯·확인/취소 버튼, primary/danger 톤) |
|
||||
| components/admin/AdminToast.vue | 관리자 공통 우측 상단 토스트 표시(성공·오류·안내 톤, 화면별 상단 위치 조정) |
|
||||
| components/admin/AdminSearchInput.vue | 관리자 공통 검색 입력(돋보기 아이콘, IME 검색 이벤트 전달, 외부 focus/select 노출) |
|
||||
| components/admin/AdminPagination.vue | 관리자 공통 번호형 페이지네이션(이전·다음 화살표, 첫/끝/현재 주변 페이지, ellipsis 접기) |
|
||||
| components/admin/AdminAnalyticsTrendChart.vue | 관리자 통계 추이 막대 차트(축 라벨·증감 툴팁·기간 페이지 이동) |
|
||||
| lib/analytics-chart-ui.js | 통계 차트 라벨·페이지네이션·증감 계산 유틸 |
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리.
|
||||
- 관리자 UI 공통 컴포넌트 정리.
|
||||
- 모달 패널, 필터 팝오버의 radius·shadow·z-index·색상 규칙을 공통화.
|
||||
- 게시글/페이지/미디어 목록의 검색·필터·정렬 레이아웃을 같은 패턴으로 유지.
|
||||
- 게시글/페이지/미디어 목록의 필터·정렬 레이아웃을 같은 패턴으로 유지.
|
||||
- 최근 브라우저 회귀 테스트 추가.
|
||||
- 태그 추천 선택 후 다음 태그 후보가 포커스 해제 없이 유지되는지 확인.
|
||||
- 관리자 미디어 대표 이미지/본문 이미지 탭 전환 중 역할 배지가 노출되지 않는지 확인.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 업데이트 이력
|
||||
|
||||
## v1.11.7
|
||||
|
||||
- 관리자 목록 검색 입력을 공용 `AdminSearchInput` 컴포넌트로 분리.
|
||||
- 게시글·페이지·미디어·멤버 목록의 검색 입력 마크업과 IME 이벤트 연결 중복 제거.
|
||||
|
||||
## v1.11.6
|
||||
|
||||
- 관리자 번호형 페이지네이션을 공용 `AdminPagination` 컴포넌트로 분리.
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.6",
|
||||
"version": "1.11.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.6",
|
||||
"version": "1.11.7",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.6",
|
||||
"version": "1.11.7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"imports": {
|
||||
|
||||
@@ -1380,22 +1380,17 @@ watch(() => mediaPagination.value.page, (page) => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-media__header-actions flex min-w-0 flex-wrap items-center gap-2">
|
||||
<label class="admin-media__search relative">
|
||||
<span class="sr-only">미디어 검색</span>
|
||||
<svg class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#8e9cac]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m21 21-4.34-4.34" />
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
</svg>
|
||||
<input
|
||||
:value="searchTextInput"
|
||||
class="admin-media__search-input h-10 w-full rounded border border-line bg-white pl-9 pr-3 text-sm text-[#394047] outline-none transition-colors placeholder:text-[#8e9cac] hover:border-[#c8ced3] focus:border-[#8e9cac] md:w-72"
|
||||
type="search"
|
||||
placeholder="미디어 검색"
|
||||
@input="onSearchTextInput"
|
||||
@compositionupdate="onSearchTextInput"
|
||||
@compositionend="onSearchTextCompositionEnd"
|
||||
>
|
||||
</label>
|
||||
<AdminSearchInput
|
||||
:value="searchTextInput"
|
||||
label="미디어 검색"
|
||||
placeholder="미디어 검색"
|
||||
root-class="admin-media__search"
|
||||
input-class="admin-media__search-input"
|
||||
width-class="w-full md:w-72"
|
||||
@input="onSearchTextInput"
|
||||
@compositionupdate="onSearchTextInput"
|
||||
@compositionend="onSearchTextCompositionEnd"
|
||||
/>
|
||||
<button
|
||||
v-if="activeTab === 'library'"
|
||||
class="admin-media__upload-button h-10 shrink-0 rounded bg-[#15171a] px-4 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
|
||||
@@ -381,23 +381,17 @@ const formatRelativeTime = (value) => {
|
||||
</h1>
|
||||
</div>
|
||||
<div class="admin-members__actions flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<label class="admin-members__search relative block w-full sm:w-52">
|
||||
<span class="sr-only">멤버 검색</span>
|
||||
<svg class="admin-members__search-icon pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#8e9cac]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m21 21-4.34-4.34" />
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
</svg>
|
||||
<input
|
||||
:value="memberSearchInput"
|
||||
class="admin-members__search-input h-10 w-full rounded border border-line bg-white pl-9 pr-3 text-sm text-[#394047] outline-none transition-colors placeholder:text-[#8e9cac] hover:border-[#c8ced3] focus:border-[#8e9cac]"
|
||||
type="search"
|
||||
placeholder="멤버 검색"
|
||||
@input="onMemberSearchInput"
|
||||
@compositionupdate="onMemberSearchInput"
|
||||
@compositionend="onMemberSearchCompositionEnd"
|
||||
aria-label="멤버 검색"
|
||||
>
|
||||
</label>
|
||||
<AdminSearchInput
|
||||
:value="memberSearchInput"
|
||||
label="멤버 검색"
|
||||
placeholder="멤버 검색"
|
||||
root-class="admin-members__search w-full sm:w-52"
|
||||
input-class="admin-members__search-input"
|
||||
width-class="w-full"
|
||||
@input="onMemberSearchInput"
|
||||
@compositionupdate="onMemberSearchInput"
|
||||
@compositionend="onMemberSearchCompositionEnd"
|
||||
/>
|
||||
<button
|
||||
class="admin-members__filter inline-flex h-11 items-center justify-center gap-2 rounded-md border border-line bg-white px-4 text-sm font-semibold text-[#394047] transition hover:bg-[#eff1f2] hover:text-black"
|
||||
type="button"
|
||||
|
||||
@@ -374,22 +374,16 @@ const deletePage = async (page) => {
|
||||
</div>
|
||||
<div class="admin-pages__header-actions flex min-w-0 flex-1 flex-wrap items-center justify-start gap-2 lg:flex-nowrap lg:justify-end">
|
||||
<div class="admin-pages__filters flex min-w-0 flex-wrap items-center gap-2">
|
||||
<label class="admin-pages__search relative">
|
||||
<span class="sr-only">페이지 검색</span>
|
||||
<svg class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#8e9cac]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m21 21-4.34-4.34" />
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
</svg>
|
||||
<input
|
||||
:value="searchInput"
|
||||
class="admin-pages__search-input h-10 w-52 rounded border border-line bg-white pl-9 pr-3 text-sm text-[#394047] outline-none transition-colors placeholder:text-[#8e9cac] hover:border-[#c8ced3] focus:border-[#8e9cac]"
|
||||
type="search"
|
||||
placeholder="페이지 검색"
|
||||
@input="onSearchInput"
|
||||
@compositionupdate="onSearchInput"
|
||||
@compositionend="onSearchCompositionEnd"
|
||||
>
|
||||
</label>
|
||||
<AdminSearchInput
|
||||
:value="searchInput"
|
||||
label="페이지 검색"
|
||||
placeholder="페이지 검색"
|
||||
root-class="admin-pages__search"
|
||||
input-class="admin-pages__search-input"
|
||||
@input="onSearchInput"
|
||||
@compositionupdate="onSearchInput"
|
||||
@compositionend="onSearchCompositionEnd"
|
||||
/>
|
||||
<label class="admin-pages__filter">
|
||||
<span class="sr-only">상태 필터</span>
|
||||
<span class="admin-pages__filter-select-wrap relative block">
|
||||
|
||||
@@ -989,23 +989,17 @@ watch(postPageCount, (pageCount) => {
|
||||
</div>
|
||||
<div class="admin-posts__header-actions flex min-w-0 flex-1 flex-wrap items-center justify-start gap-2 lg:flex-nowrap lg:justify-end">
|
||||
<div class="admin-posts__filters flex min-w-0 flex-wrap items-center gap-2">
|
||||
<label class="admin-posts__search relative">
|
||||
<span class="sr-only">글 검색</span>
|
||||
<svg class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#8e9cac]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m21 21-4.34-4.34" />
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
</svg>
|
||||
<input
|
||||
ref="postSearchInputRef"
|
||||
:value="searchInput"
|
||||
class="admin-posts__search-input h-10 w-52 rounded border border-line bg-white pl-9 pr-3 text-sm text-[#394047] outline-none transition-colors placeholder:text-[#8e9cac] hover:border-[#c8ced3] focus:border-[#8e9cac]"
|
||||
type="search"
|
||||
placeholder="글 검색"
|
||||
@input="onSearchInput"
|
||||
@compositionupdate="onSearchInput"
|
||||
@compositionend="onSearchCompositionEnd"
|
||||
>
|
||||
</label>
|
||||
<AdminSearchInput
|
||||
ref="postSearchInputRef"
|
||||
:value="searchInput"
|
||||
label="글 검색"
|
||||
placeholder="글 검색"
|
||||
root-class="admin-posts__search"
|
||||
input-class="admin-posts__search-input"
|
||||
@input="onSearchInput"
|
||||
@compositionupdate="onSearchInput"
|
||||
@compositionend="onSearchCompositionEnd"
|
||||
/>
|
||||
<label class="admin-posts__filter">
|
||||
<span class="sr-only">상태 필터</span>
|
||||
<span class="admin-posts__filter-select-wrap relative block">
|
||||
|
||||
Reference in New Issue
Block a user