06 · Developer Guide · 레시피
이미지 갤러리 라이트박스Photo Gallery
썸네일 그리드 + 클릭 시 풀스크린 모달. 키보드(← → ESC)·외부 클릭으로 닫히고 body 스크롤이 잠깁니다.
언제 쓰는가
- 게시물 한 건당 사진 N장이 있는 갤러리 (예: 새가족 환영회 14장)
- 예배·행사 현장 사진을 사이트 안에서 펼쳐 보고 싶을 때
- 외부 사이트로 보내지 않고 사이트 내에서 동작해야 하는 경우
전체 코드
components/preview/PhotoGallery.tsxtsx
"use client";
import { useEffect, useState } from "react";
import { ChevronLeft, ChevronRight, X } from "lucide-react";
export function PhotoGallery({ images, title }: { images: string[]; title: string }) {
const [openIdx, setOpenIdx] = useState<number | null>(null);
useEffect(() => {
if (openIdx == null) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpenIdx(null);
if (e.key === "ArrowRight")
setOpenIdx((i) => (i == null ? null : Math.min(images.length - 1, i + 1)));
if (e.key === "ArrowLeft")
setOpenIdx((i) => (i == null ? null : Math.max(0, i - 1)));
};
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
document.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = prev;
document.removeEventListener("keydown", onKey);
};
}, [openIdx, images.length]);
if (images.length === 0) return null;
return (
<>
<ul className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
{images.map((src, i) => (
<li key={src}>
<button
type="button"
onClick={() => setOpenIdx(i)}
className="group block w-full overflow-hidden rounded-md"
aria-label={`${title} 사진 ${i + 1} 크게 보기`}
>
<img
src={src}
alt={`${title} ${i + 1}`}
loading="lazy"
className="block aspect-[4/3] w-full object-cover transition group-hover:scale-[1.02]"
/>
</button>
</li>
))}
</ul>
{openIdx != null && (
<div
role="dialog"
aria-modal="true"
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/92 px-4 py-6"
onClick={() => setOpenIdx(null)}
>
{/* ESC · ← → 키, 외부 클릭 닫기 */}
<img
src={images[openIdx]}
alt={`${title} ${openIdx + 1} 크게`}
className="max-h-[88vh] w-auto rounded-md bg-white shadow-2xl"
onClick={(e) => e.stopPropagation()}
/>
</div>
)}
</>
);
}사용 예
tsx
// 게시물 상세 페이지에서 사용
import { PhotoGallery } from "@/components/preview/PhotoGallery";
<PhotoGallery
images={post.images ?? []}
title={post.title}
/>설계 포인트
확장 — 책 펼침면 뷰어
함즐함울처럼 PDF에서 추출한 페이지를 책 형태로 펼침면 보여줄 때는 같은 라이트 박스 패턴에 좌·우 두 페이지를 묶는 spread 단위를 추가합니다. components/preview/HamjulIssueViewer.tsx가 그 변형입니다.