diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index f5c3e05..17f2265 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -397,3 +397,127 @@ docker compose -f docker-compose.dev.yml up -d | `frontend/src/pages/mobile/public/Schedule.jsx` | 52KB | 모바일 일정 | | `backend/services/youtube-bot.js` | 17KB | YouTube 수집 | | `backend/services/x-bot.js` | 16KB | X 수집 | + +--- + +## 13. 모바일 앨범 갤러리 UI + +### 주요 컴포넌트 + +| 파일 | 설명 | +| ------------------------------------------------------ | ----------------------------- | +| `frontend/src/pages/mobile/public/AlbumGallery.jsx` | 모바일 앨범 갤러리 (전체보기) | +| `frontend/src/pages/mobile/public/AlbumDetail.jsx` | 모바일 앨범 상세 | +| `frontend/src/components/common/LightboxIndicator.jsx` | 공통 슬라이딩 점 인디케이터 | + +### Swiper ViewPager 스타일 라이트박스 + +```jsx +import { Swiper, SwiperSlide } from "swiper/react"; +import { Virtual } from "swiper/modules"; + + { + swiperRef.current = swiper; + }} + onSlideChange={(swiper) => setSelectedIndex(swiper.activeIndex)} + slidesPerView={1} + resistance={true} + resistanceRatio={0.5} +> + {photos.map((photo, index) => ( + + + + ))} +; +``` + +### LightboxIndicator 사용법 + +```jsx +import LightboxIndicator from '../../../components/common/LightboxIndicator'; + +// PC (기본 width 200px) + swiperRef.current?.slideTo(i)} +/> + +// 모바일 (width 120px로 축소) + swiperRef.current?.slideTo(i)} + width={120} +/> +``` + +### 2열 지그재그 Masonry 그리드 + +```jsx +// 1,3,5번 → 왼쪽 열 / 2,4,6번 → 오른쪽 열 +const distributePhotos = () => { + const leftColumn = []; + const rightColumn = []; + photos.forEach((photo, index) => { + if (index % 2 === 0) leftColumn.push({ ...photo, originalIndex: index }); + else rightColumn.push({ ...photo, originalIndex: index }); + }); + return { leftColumn, rightColumn }; +}; +``` + +### 뒤로가기 처리 패턴 + +```jsx +// 모달/라이트박스 열 때 히스토리 추가 +const openLightbox = useCallback((images, index, options = {}) => { + setLightbox({ open: true, images, index, ...options }); + window.history.pushState({ lightbox: true }, ""); +}, []); + +// popstate 이벤트로 닫기 +useEffect(() => { + const handlePopState = () => { + if (showModal) setShowModal(false); + else if (lightbox.open) setLightbox((prev) => ({ ...prev, open: false })); + }; + window.addEventListener("popstate", handlePopState); + return () => window.removeEventListener("popstate", handlePopState); +}, [showModal, lightbox.open]); + +// X 버튼도 history.back() 호출 +; +``` + +### 바텀시트 (정보 표시) + +```jsx + { + if (info.offset.y > 100 || info.velocity.y > 300) { + window.history.back(); + } + }} + className="bg-zinc-900 rounded-t-3xl" +> + {/* 드래그 핸들 */} +
+
+
+ {/* 내용 */} + +```