라이트박스 뒤로가기 버튼 처리 추가
- PC/모바일 앨범 상세, 갤러리 페이지에 뒤로가기 처리 - PC/모바일 X 일정 상세 페이지에 뒤로가기 처리 - 라이트박스 열릴 때 history.pushState 호출 - popstate 이벤트로 라이트박스 닫기 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d8055c00e5
commit
67e9992cf1
4 changed files with 92 additions and 30 deletions
|
|
@ -315,6 +315,7 @@ function XSection({ schedule }) {
|
||||||
const openLightbox = (index) => {
|
const openLightbox = (index) => {
|
||||||
setLightboxIndex(index);
|
setLightboxIndex(index);
|
||||||
setLightboxOpen(true);
|
setLightboxOpen(true);
|
||||||
|
window.history.pushState({ lightbox: true }, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToPrev = () => {
|
const goToPrev = () => {
|
||||||
|
|
@ -341,6 +342,18 @@ function XSection({ schedule }) {
|
||||||
};
|
};
|
||||||
}, [lightboxOpen]);
|
}, [lightboxOpen]);
|
||||||
|
|
||||||
|
// 뒤로가기 처리
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePopState = () => {
|
||||||
|
if (lightboxOpen) {
|
||||||
|
setLightboxOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('popstate', handlePopState);
|
||||||
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
|
}, [lightboxOpen]);
|
||||||
|
|
||||||
// 링크 데코레이터 (새 탭에서 열기)
|
// 링크 데코레이터 (새 탭에서 열기)
|
||||||
const linkDecorator = (href, text, key) => (
|
const linkDecorator = (href, text, key) => (
|
||||||
<a
|
<a
|
||||||
|
|
@ -478,12 +491,12 @@ function XSection({ schedule }) {
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="fixed inset-0 bg-black z-50 flex items-center justify-center"
|
className="fixed inset-0 bg-black z-50 flex items-center justify-center"
|
||||||
onClick={() => setLightboxOpen(false)}
|
onClick={() => window.history.back()}
|
||||||
>
|
>
|
||||||
{/* 닫기 버튼 */}
|
{/* 닫기 버튼 */}
|
||||||
<button
|
<button
|
||||||
className="absolute top-4 right-4 p-2 text-white/70 z-10"
|
className="absolute top-4 right-4 p-2 text-white/70 z-10"
|
||||||
onClick={() => setLightboxOpen(false)}
|
onClick={() => window.history.back()}
|
||||||
>
|
>
|
||||||
<X size={28} />
|
<X size={28} />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,30 @@ function AlbumDetail() {
|
||||||
}));
|
}));
|
||||||
}, [lightbox.images.length]);
|
}, [lightbox.images.length]);
|
||||||
|
|
||||||
|
// 라이트박스 열기 - 히스토리 추가
|
||||||
|
const openLightbox = useCallback((images, index, options = {}) => {
|
||||||
|
setLightbox({ open: true, images, index, teasers: options.teasers });
|
||||||
|
window.history.pushState({ lightbox: true }, '');
|
||||||
|
}, []);
|
||||||
|
|
||||||
const closeLightbox = useCallback(() => {
|
const closeLightbox = useCallback(() => {
|
||||||
setLightbox(prev => ({ ...prev, open: false }));
|
setLightbox(prev => ({ ...prev, open: false }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 뒤로가기 처리
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePopState = () => {
|
||||||
|
if (showDescriptionModal) {
|
||||||
|
setShowDescriptionModal(false);
|
||||||
|
} else if (lightbox.open) {
|
||||||
|
setLightbox(prev => ({ ...prev, open: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('popstate', handlePopState);
|
||||||
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
|
}, [showDescriptionModal, lightbox.open]);
|
||||||
|
|
||||||
// 라이트박스 열릴 때 body 스크롤 숨기기
|
// 라이트박스 열릴 때 body 스크롤 숨기기
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lightbox.open) {
|
if (lightbox.open) {
|
||||||
|
|
@ -98,7 +118,7 @@ function AlbumDetail() {
|
||||||
goToNext();
|
goToNext();
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
closeLightbox();
|
window.history.back();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -202,11 +222,10 @@ function AlbumDetail() {
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{ duration: 0.4 }}
|
transition={{ duration: 0.4 }}
|
||||||
className="w-80 h-80 flex-shrink-0 rounded-2xl overflow-hidden shadow-lg cursor-pointer group"
|
className="w-80 h-80 flex-shrink-0 rounded-2xl overflow-hidden shadow-lg cursor-pointer group"
|
||||||
onClick={() => setLightbox({
|
onClick={() => openLightbox(
|
||||||
open: true,
|
[album.cover_original_url || album.cover_medium_url],
|
||||||
images: [album.cover_original_url || album.cover_medium_url],
|
0
|
||||||
index: 0
|
)}
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={album.cover_medium_url || album.cover_original_url}
|
src={album.cover_medium_url || album.cover_original_url}
|
||||||
|
|
@ -253,6 +272,7 @@ function AlbumDetail() {
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowDescriptionModal(true);
|
setShowDescriptionModal(true);
|
||||||
|
window.history.pushState({ description: true }, '');
|
||||||
setShowMenu(false);
|
setShowMenu(false);
|
||||||
}}
|
}}
|
||||||
className="w-full flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
|
className="w-full flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
|
||||||
|
|
@ -291,16 +311,15 @@ function AlbumDetail() {
|
||||||
<p className="text-xs text-gray-400 mb-2">티저 이미지</p>
|
<p className="text-xs text-gray-400 mb-2">티저 이미지</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{album.teasers.map((teaser, index) => (
|
{album.teasers.map((teaser, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => setLightbox({
|
onClick={() => openLightbox(
|
||||||
open: true,
|
album.teasers.map(t =>
|
||||||
images: album.teasers.map(t =>
|
|
||||||
t.media_type === 'video' ? (t.video_url || t.original_url) : t.original_url
|
t.media_type === 'video' ? (t.video_url || t.original_url) : t.original_url
|
||||||
),
|
),
|
||||||
index,
|
index,
|
||||||
teasers: album.teasers // media_type 정보 전달
|
{ teasers: album.teasers }
|
||||||
})}
|
)}
|
||||||
className="w-24 h-24 bg-gray-200 rounded-lg overflow-hidden cursor-pointer transition-all duration-300 ease-out hover:scale-105 hover:shadow-xl hover:z-10 relative"
|
className="w-24 h-24 bg-gray-200 rounded-lg overflow-hidden cursor-pointer transition-all duration-300 ease-out hover:scale-105 hover:shadow-xl hover:z-10 relative"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
|
|
@ -397,9 +416,9 @@ function AlbumDetail() {
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4 gap-4">
|
<div className="grid grid-cols-4 gap-4">
|
||||||
{previewPhotos.map((photo, idx) => (
|
{previewPhotos.map((photo, idx) => (
|
||||||
<div
|
<div
|
||||||
key={photo.id}
|
key={photo.id}
|
||||||
onClick={() => setLightbox({ open: true, images: [photo.original_url], index: 0 })}
|
onClick={() => openLightbox([photo.original_url], 0)}
|
||||||
className="aspect-square bg-gray-200 rounded-xl overflow-hidden cursor-pointer transition-all duration-300 ease-out hover:scale-[1.03] hover:shadow-xl hover:z-10"
|
className="aspect-square bg-gray-200 rounded-xl overflow-hidden cursor-pointer transition-all duration-300 ease-out hover:scale-[1.03] hover:shadow-xl hover:z-10"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
|
@ -445,9 +464,9 @@ function AlbumDetail() {
|
||||||
<Download size={28} />
|
<Download size={28} />
|
||||||
</button>
|
</button>
|
||||||
{/* 닫기 버튼 */}
|
{/* 닫기 버튼 */}
|
||||||
<button
|
<button
|
||||||
className="text-white/70 hover:text-white transition-colors"
|
className="text-white/70 hover:text-white transition-colors"
|
||||||
onClick={closeLightbox}
|
onClick={() => window.history.back()}
|
||||||
>
|
>
|
||||||
<X size={32} />
|
<X size={32} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -537,7 +556,7 @@ function AlbumDetail() {
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4"
|
className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4"
|
||||||
onClick={() => setShowDescriptionModal(false)}
|
onClick={() => window.history.back()}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||||
|
|
@ -550,7 +569,7 @@ function AlbumDetail() {
|
||||||
<div className="flex items-center justify-between p-5 border-b border-gray-100">
|
<div className="flex items-center justify-between p-5 border-b border-gray-100">
|
||||||
<h3 className="text-lg font-bold">앨범 소개</h3>
|
<h3 className="text-lg font-bold">앨범 소개</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowDescriptionModal(false)}
|
onClick={() => window.history.back()}
|
||||||
className="p-1.5 hover:bg-gray-100 rounded-full transition-colors"
|
className="p-1.5 hover:bg-gray-100 rounded-full transition-colors"
|
||||||
>
|
>
|
||||||
<X size={20} className="text-gray-500" />
|
<X size={20} className="text-gray-500" />
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,30 @@ function AlbumGallery() {
|
||||||
return allPhotos;
|
return allPhotos;
|
||||||
}, [album]);
|
}, [album]);
|
||||||
|
|
||||||
// 라이트박스 열기
|
// 라이트박스 열기 - 히스토리 추가
|
||||||
const openLightbox = (index) => {
|
const openLightbox = useCallback((index) => {
|
||||||
setImageLoaded(false);
|
setImageLoaded(false);
|
||||||
setLightbox({ open: true, index });
|
setLightbox({ open: true, index });
|
||||||
};
|
window.history.pushState({ lightbox: true }, '');
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 라이트박스 닫기
|
// 라이트박스 닫기
|
||||||
const closeLightbox = useCallback(() => {
|
const closeLightbox = useCallback(() => {
|
||||||
setLightbox(prev => ({ ...prev, open: false }));
|
setLightbox(prev => ({ ...prev, open: false }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 뒤로가기 처리
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePopState = () => {
|
||||||
|
if (lightbox.open) {
|
||||||
|
setLightbox(prev => ({ ...prev, open: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('popstate', handlePopState);
|
||||||
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
|
}, [lightbox.open]);
|
||||||
|
|
||||||
// 라이트박스 열릴 때 body 스크롤 숨기기
|
// 라이트박스 열릴 때 body 스크롤 숨기기
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lightbox.open) {
|
if (lightbox.open) {
|
||||||
|
|
@ -146,7 +159,7 @@ function AlbumGallery() {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'ArrowLeft': goToPrev(); break;
|
case 'ArrowLeft': goToPrev(); break;
|
||||||
case 'ArrowRight': goToNext(); break;
|
case 'ArrowRight': goToNext(); break;
|
||||||
case 'Escape': closeLightbox(); break;
|
case 'Escape': window.history.back(); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -267,9 +280,9 @@ function AlbumGallery() {
|
||||||
>
|
>
|
||||||
<Download size={28} />
|
<Download size={28} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="text-white/70 hover:text-white transition-colors"
|
className="text-white/70 hover:text-white transition-colors"
|
||||||
onClick={closeLightbox}
|
onClick={() => window.history.back()}
|
||||||
>
|
>
|
||||||
<X size={32} />
|
<X size={32} />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Linkify from 'react-linkify';
|
import Linkify from 'react-linkify';
|
||||||
import { decodeHtmlEntities } from './utils';
|
import { decodeHtmlEntities } from './utils';
|
||||||
|
|
@ -33,10 +33,27 @@ function XSection({ schedule }) {
|
||||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||||
|
|
||||||
const openLightbox = (index) => {
|
const openLightbox = useCallback((index) => {
|
||||||
setLightboxIndex(index);
|
setLightboxIndex(index);
|
||||||
setLightboxOpen(true);
|
setLightboxOpen(true);
|
||||||
};
|
window.history.pushState({ lightbox: true }, '');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeLightbox = useCallback(() => {
|
||||||
|
setLightboxOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 뒤로가기 처리
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePopState = () => {
|
||||||
|
if (lightboxOpen) {
|
||||||
|
setLightboxOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('popstate', handlePopState);
|
||||||
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
|
}, [lightboxOpen]);
|
||||||
|
|
||||||
// 링크 데코레이터 (새 탭에서 열기)
|
// 링크 데코레이터 (새 탭에서 열기)
|
||||||
const linkDecorator = (href, text, key) => (
|
const linkDecorator = (href, text, key) => (
|
||||||
|
|
@ -160,7 +177,7 @@ function XSection({ schedule }) {
|
||||||
images={schedule.imageUrls || []}
|
images={schedule.imageUrls || []}
|
||||||
currentIndex={lightboxIndex}
|
currentIndex={lightboxIndex}
|
||||||
isOpen={lightboxOpen}
|
isOpen={lightboxOpen}
|
||||||
onClose={() => setLightboxOpen(false)}
|
onClose={() => window.history.back()}
|
||||||
onIndexChange={setLightboxIndex}
|
onIndexChange={setLightboxIndex}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue