From be548879dc78f8de311bdca00bf2bbbebd01fae0 Mon Sep 17 00:00:00 2001 From: caadiq Date: Sun, 19 Apr 2026 17:03:10 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20UX?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modal 공용 컴포넌트에 열기/닫기 애니메이션 추가, 뒷배경 클릭 닫기 제거 - ImagePicker에 동일한 애니메이션 + 뒷배경 클릭 차단 적용 - ImagePicker 이미지 크기를 관리 페이지와 동일하게 (p-4 + w-full h-full object-contain) - ImagePicker 하단 빈 pagination 영역이 차지하던 여백 제거 (조건부 렌더) - 그리드 높이를 632px로 고정 + OverlayScrollbars (os-theme-maple) 스크롤 - overscroll-behavior: contain 으로 뒷 페이지 스크롤 전파 방지 Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/components/common/Modal.jsx | 75 +++--- .../admin/pc/components/ImagePicker.jsx | 249 ++++++++++-------- 2 files changed, 179 insertions(+), 145 deletions(-) diff --git a/frontend/src/components/common/Modal.jsx b/frontend/src/components/common/Modal.jsx index 53e2234..deccdb8 100644 --- a/frontend/src/components/common/Modal.jsx +++ b/frontend/src/components/common/Modal.jsx @@ -1,40 +1,53 @@ +import { motion, AnimatePresence } from 'framer-motion' + /** * 관리자 페이지에서 쓰는 일반 모달 래퍼 - * - *
content
- *
+ * - 열기/닫기 애니메이션 포함 + * - 뒷배경 클릭으로는 닫히지 않음 (× 버튼만) */ export default function Modal({ open, onClose, title, children, maxWidth = 'max-w-md' }) { - if (!open) return null return ( -
-
e.stopPropagation()} - > -
+ {open && ( + -

{title}

- -
- {children} -
-
+
+

{title}

+ +
+ {children} + + + )} + ) } diff --git a/frontend/src/features/admin/pc/components/ImagePicker.jsx b/frontend/src/features/admin/pc/components/ImagePicker.jsx index f0bb041..97f8ca3 100644 --- a/frontend/src/features/admin/pc/components/ImagePicker.jsx +++ b/frontend/src/features/admin/pc/components/ImagePicker.jsx @@ -1,5 +1,7 @@ import { useState, useEffect } from 'react' import { useQuery } from '@tanstack/react-query' +import { motion, AnimatePresence } from 'framer-motion' +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react' import { api } from '../../../../api/client' const PAGE_SIZE = 24 @@ -42,22 +44,30 @@ export default function ImagePicker({ open, onClose, onSelect, currentImageId }) const images = data?.items || [] const totalPages = data?.total_pages || 1 - if (!open) return null - return ( -
-
e.stopPropagation()} + + {open && ( + +
- {/* 이미지 그리드 */} -
- {isLoading ? ( -
- {Array.from({ length: 12 }).map((_, i) => ( -
- ))} -
- ) : images.length === 0 ? ( -
- {debouncedSearch ? '검색 결과가 없습니다' : '업로드된 이미지가 없습니다'} -
- ) : ( -
- {images.map((image) => { - const isSelected = currentImageId === image.id - return ( - - ) - })} -
- )} -
- - {/* 페이지네이션 + 액션 */} -
- {totalPages > 1 ? ( -
- - {page} / {totalPages} - -
- ) :
} +
+ {isLoading ? ( +
+ {Array.from({ length: 12 }).map((_, i) => ( +
+ ))} +
+ ) : images.length === 0 ? ( +
+ {debouncedSearch ? '검색 결과가 없습니다' : '업로드된 이미지가 없습니다'} +
+ ) : ( +
+ {images.map((image) => { + const isSelected = currentImageId === image.id + return ( + + ) + })} +
+ )} +
+ - {currentImageId && ( - - )} -
-
-
+ {/* 페이지네이션 + 액션 (없으면 전체 섹션 숨김) */} + {(totalPages > 1 || currentImageId) && ( +
+ {totalPages > 1 ? ( +
+ + {page} / {totalPages} + +
+ ) :
} + + {currentImageId && ( + + )} +
+ )} + + + )} + ) }