feat: 라이트박스 UI 개선 - min-width/height 적용, body 스크롤 숨김, 이미지 크기 증가, 멤버 태그 개별 표시

This commit is contained in:
caadiq 2026-01-02 11:07:51 +09:00
parent ab92e3117e
commit 79fb58e2ee
2 changed files with 205 additions and 146 deletions

View file

@ -37,6 +37,21 @@ function AlbumDetail() {
setLightbox(prev => ({ ...prev, open: false })); setLightbox(prev => ({ ...prev, open: false }));
}, []); }, []);
// body
useEffect(() => {
if (lightbox.open) {
document.documentElement.style.overflow = 'hidden';
document.body.style.overflow = 'hidden';
} else {
document.documentElement.style.overflow = '';
document.body.style.overflow = '';
}
return () => {
document.documentElement.style.overflow = '';
document.body.style.overflow = '';
};
}, [lightbox.open]);
// //
const downloadImage = useCallback(async () => { const downloadImage = useCallback(async () => {
const imageUrl = lightbox.images[lightbox.index]; const imageUrl = lightbox.images[lightbox.index];
@ -373,8 +388,10 @@ function AlbumDetail() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center" className="fixed inset-0 bg-black/95 z-50 overflow-auto"
> >
{/* 내부 컨테이너 - min-width, min-height 적용 */}
<div className="min-w-[1200px] min-h-[800px] w-full h-full relative flex items-center justify-center">
{/* 상단 버튼들 */} {/* 상단 버튼들 */}
<div className="absolute top-6 right-6 flex gap-3 z-10"> <div className="absolute top-6 right-6 flex gap-3 z-10">
{/* 다운로드 버튼 */} {/* 다운로드 버튼 */}
@ -399,7 +416,7 @@ function AlbumDetail() {
{/* 이전 버튼 */} {/* 이전 버튼 */}
{lightbox.images.length > 1 && ( {lightbox.images.length > 1 && (
<button <button
className="absolute left-6 text-white/70 hover:text-white transition-colors z-10" className="absolute left-6 p-2 text-white/70 hover:text-white transition-colors z-10"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
goToPrev(); goToPrev();
@ -417,22 +434,24 @@ function AlbumDetail() {
)} )}
{/* 이미지 */} {/* 이미지 */}
<div className="flex flex-col items-center mx-24">
<motion.img <motion.img
key={lightbox.index} key={lightbox.index}
src={lightbox.images[lightbox.index]} src={lightbox.images[lightbox.index]}
alt="확대 이미지" alt="확대 이미지"
className={`max-w-[90vw] max-h-[90vh] object-contain rounded-lg transition-opacity duration-200 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`} className={`max-w-[1100px] max-h-[75vh] object-contain rounded-lg transition-opacity duration-200 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onLoad={() => setImageLoaded(true)} onLoad={() => setImageLoaded(true)}
initial={{ x: slideDirection * 100 }} initial={{ x: slideDirection * 100 }}
animate={{ x: 0 }} animate={{ x: 0 }}
transition={{ duration: 0.25, ease: 'easeOut' }} transition={{ duration: 0.25, ease: 'easeOut' }}
/> />
</div>
{/* 다음 버튼 */} {/* 다음 버튼 */}
{lightbox.images.length > 1 && ( {lightbox.images.length > 1 && (
<button <button
className="absolute right-6 text-white/70 hover:text-white transition-colors z-10" className="absolute right-6 p-2 text-white/70 hover:text-white transition-colors z-10"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
goToNext(); goToNext();
@ -442,21 +461,23 @@ function AlbumDetail() {
</button> </button>
)} )}
{/* 인디케이터 - 이미지 2개 이상일 때만 표시 */} {/* 인디케이터 */}
{lightbox.images.length > 1 && ( {lightbox.images.length > 1 && (
<div className="absolute bottom-6 flex gap-2"> <div className="absolute bottom-6 left-1/2 -translate-x-1/2 flex gap-1.5 overflow-x-auto scrollbar-hide" style={{ maxWidth: '1000px' }}>
{lightbox.images.map((_, i) => ( {lightbox.images.map((_, i) => (
<button <button
key={i} key={i}
className={`w-2 h-2 rounded-full transition-colors ${i === lightbox.index ? 'bg-white' : 'bg-white/40'}`} className={`w-2 h-2 rounded-full transition-colors flex-shrink-0 ${i === lightbox.index ? 'bg-white' : 'bg-white/40'}`}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setImageLoaded(false);
setLightbox({ ...lightbox, index: i }); setLightbox({ ...lightbox, index: i });
}} }}
/> />
))} ))}
</div> </div>
)} )}
</div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>

View file

@ -76,6 +76,21 @@ function AlbumGallery() {
setLightbox(prev => ({ ...prev, open: false })); setLightbox(prev => ({ ...prev, open: false }));
}, []); }, []);
// body
useEffect(() => {
if (lightbox.open) {
document.documentElement.style.overflow = 'hidden';
document.body.style.overflow = 'hidden';
} else {
document.documentElement.style.overflow = '';
document.body.style.overflow = '';
}
return () => {
document.documentElement.style.overflow = '';
document.body.style.overflow = '';
};
}, [lightbox.open]);
// / // /
const goToPrev = useCallback(() => { const goToPrev = useCallback(() => {
if (photos.length <= 1) return; if (photos.length <= 1) return;
@ -225,8 +240,10 @@ function AlbumGallery() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/95 z-50 flex items-center justify-center" className="fixed inset-0 bg-black/95 z-50 overflow-auto"
> >
{/* 내부 컨테이너 - min-width, min-height 적용 (화면 줄여도 크기 유지, 스크롤) */}
<div className="min-w-[1200px] min-h-[800px] w-full h-full relative flex items-center justify-center">
{/* 상단 버튼들 */} {/* 상단 버튼들 */}
<div className="absolute top-6 right-6 flex gap-3 z-10"> <div className="absolute top-6 right-6 flex gap-3 z-10">
<button <button
@ -248,10 +265,10 @@ function AlbumGallery() {
{lightbox.index + 1} / {photos.length} {lightbox.index + 1} / {photos.length}
</div> </div>
{/* 이전 버튼 */} {/* 이전 버튼 - margin으로 이미지와 간격 */}
{photos.length > 1 && ( {photos.length > 1 && (
<button <button
className="absolute left-6 text-white/70 hover:text-white transition-colors z-10" className="absolute left-6 p-2 text-white/70 hover:text-white transition-colors z-10"
onClick={goToPrev} onClick={goToPrev}
> >
<ChevronLeft size={48} /> <ChevronLeft size={48} />
@ -265,34 +282,54 @@ function AlbumGallery() {
</div> </div>
)} )}
{/* 이미지 */} {/* 이미지 + 컨셉 정보 - 양옆 margin으로 화살표와 간격 */}
<div className="flex flex-col items-center mx-24">
<motion.img <motion.img
key={lightbox.index} key={lightbox.index}
src={photos[lightbox.index]?.originalUrl} src={photos[lightbox.index]?.originalUrl}
alt="확대 이미지" alt="확대 이미지"
className={`max-w-[90vw] max-h-[85vh] object-contain rounded-lg transition-opacity duration-200 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`} className={`max-w-[1100px] max-h-[75vh] object-contain rounded-lg transition-opacity duration-200 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setImageLoaded(true)} onLoad={() => setImageLoaded(true)}
initial={{ x: slideDirection * 100 }} initial={{ x: slideDirection * 100 }}
animate={{ x: 0 }} animate={{ x: 0 }}
transition={{ duration: 0.25, ease: 'easeOut' }} transition={{ duration: 0.25, ease: 'easeOut' }}
/> />
{/* 컨셉 정보 - 정보가 있을 때만 표시 */}
{imageLoaded && photos[lightbox.index]?.title && (
<div className="mt-6 flex flex-col items-center gap-2">
<span className="px-4 py-2 bg-white/10 backdrop-blur-sm rounded-full text-white font-medium text-base">
{photos[lightbox.index]?.title}
</span>
{/* 멤버가 있고 빈 문자열이 아닐 때만 표시, 쉼표로 분리해서 개별 태그 */}
{photos[lightbox.index]?.members && String(photos[lightbox.index]?.members).trim() && (
<div className="flex items-center gap-2">
{String(photos[lightbox.index]?.members).split(',').map((member, idx) => (
<span key={idx} className="px-3 py-1.5 bg-primary/80 rounded-full text-white text-sm">
{member.trim()}
</span>
))}
</div>
)}
</div>
)}
</div>
{/* 다음 버튼 */} {/* 다음 버튼 - margin으로 이미지와 간격 */}
{photos.length > 1 && ( {photos.length > 1 && (
<button <button
className="absolute right-6 text-white/70 hover:text-white transition-colors z-10" className="absolute right-6 p-2 text-white/70 hover:text-white transition-colors z-10"
onClick={goToNext} onClick={goToNext}
> >
<ChevronRight size={48} /> <ChevronRight size={48} />
</button> </button>
)} )}
{/* 하단 점 인디케이터 */} {/* 하단 점 인디케이터 - 한 줄 고정, 스크롤바 숨김 */}
<div className="absolute bottom-6 flex gap-1.5 flex-wrap justify-center max-w-[80vw]"> <div className="absolute bottom-6 left-1/2 -translate-x-1/2 flex gap-1.5 overflow-x-auto scrollbar-hide" style={{ maxWidth: '1000px' }}>
{photos.map((_, i) => ( {photos.map((_, i) => (
<button <button
key={i} key={i}
className={`w-2 h-2 rounded-full transition-colors ${i === lightbox.index ? 'bg-white' : 'bg-white/40'}`} className={`w-2 h-2 rounded-full transition-colors flex-shrink-0 ${i === lightbox.index ? 'bg-white' : 'bg-white/40'}`}
onClick={() => { onClick={() => {
setImageLoaded(false); setImageLoaded(false);
setLightbox({ ...lightbox, index: i }); setLightbox({ ...lightbox, index: i });
@ -300,6 +337,7 @@ function AlbumGallery() {
/> />
))} ))}
</div> </div>
</div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>