diff --git a/frontend/src/pages/mobile/public/ScheduleDetail.jsx b/frontend/src/pages/mobile/public/ScheduleDetail.jsx index ffe0507..8abea95 100644 --- a/frontend/src/pages/mobile/public/ScheduleDetail.jsx +++ b/frontend/src/pages/mobile/public/ScheduleDetail.jsx @@ -2,7 +2,7 @@ import { useParams, Link } from 'react-router-dom'; import { useQuery, keepPreviousData } from '@tanstack/react-query'; import { useEffect, useState, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Calendar, Clock, ChevronLeft, Check, Link2, MapPin, Navigation, ExternalLink } from 'lucide-react'; +import { Calendar, Clock, ChevronLeft, Check, Link2, MapPin, Navigation, ExternalLink, X, ChevronRight } from 'lucide-react'; import Linkify from 'react-linkify'; import { getSchedule } from '../../../api/public/schedules'; import '../../../mobile.css'; @@ -308,6 +308,39 @@ function XSection({ schedule }) { const displayName = profile?.displayName || username; const avatarUrl = profile?.avatarUrl; + // 라이트박스 상태 + const [lightboxOpen, setLightboxOpen] = useState(false); + const [lightboxIndex, setLightboxIndex] = useState(0); + + const openLightbox = (index) => { + setLightboxIndex(index); + setLightboxOpen(true); + }; + + const goToPrev = () => { + if (schedule.imageUrls?.length > 1) { + setLightboxIndex((lightboxIndex - 1 + schedule.imageUrls.length) % schedule.imageUrls.length); + } + }; + + const goToNext = () => { + if (schedule.imageUrls?.length > 1) { + setLightboxIndex((lightboxIndex + 1) % schedule.imageUrls.length); + } + }; + + // 라이트박스 열릴 때 body 스크롤 방지 + useEffect(() => { + if (lightboxOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { + document.body.style.overflow = ''; + }; + }, [lightboxOpen]); + // 링크 데코레이터 (새 탭에서 열기) const linkDecorator = (href, text, key) => ( openLightbox(0)} /> ) : (
openLightbox(i)} /> ))}
@@ -433,6 +469,72 @@ function XSection({ schedule }) {
+ + {/* 모바일 라이트박스 */} + + {lightboxOpen && schedule.imageUrls?.length > 0 && ( + setLightboxOpen(false)} + > + {/* 닫기 버튼 */} + + + {/* 이미지 */} + e.stopPropagation()} + /> + + {/* 이전/다음 버튼 */} + {schedule.imageUrls.length > 1 && ( + <> + + + + )} + + {/* 인디케이터 */} + {schedule.imageUrls.length > 1 && ( +
+ {schedule.imageUrls.map((_, i) => ( +
+ )} +
+ )} +
+ ); } diff --git a/frontend/src/pages/pc/public/schedule-sections/XSection.jsx b/frontend/src/pages/pc/public/schedule-sections/XSection.jsx index 6d3fe33..498dad1 100644 --- a/frontend/src/pages/pc/public/schedule-sections/XSection.jsx +++ b/frontend/src/pages/pc/public/schedule-sections/XSection.jsx @@ -1,6 +1,8 @@ +import { useState } from 'react'; import { motion } from 'framer-motion'; import Linkify from 'react-linkify'; import { decodeHtmlEntities } from './utils'; +import Lightbox from '../../../../components/common/Lightbox'; // datetime 포맷팅 (2026-01-18 19:00 → 오후 7:00 · 2026년 1월 18일) const formatXDateTime = (datetime) => { @@ -27,6 +29,15 @@ function XSection({ schedule }) { const displayName = profile?.displayName || username; const avatarUrl = profile?.avatarUrl; + // 라이트박스 상태 + const [lightboxOpen, setLightboxOpen] = useState(false); + const [lightboxIndex, setLightboxIndex] = useState(0); + + const openLightbox = (index) => { + setLightboxIndex(index); + setLightboxOpen(true); + }; + // 링크 데코레이터 (새 탭에서 열기) const linkDecorator = (href, text, key) => ( openLightbox(0)} /> ) : (
openLightbox(i)} /> ))}
@@ -141,6 +154,15 @@ function XSection({ schedule }) {
+ + {/* 라이트박스 */} + setLightboxOpen(false)} + onIndexChange={setLightboxIndex} + /> ); }