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가 그 변형입니다.

Need Help

도움이 필요하신가요?

주님의교회 PCL 디자인 시스템을 적용하시다가 막히는 부분이 있다면, 다음 경로로 직접 문의하실 수 있습니다.