06 · Developer Guide · 레시피

유튜브 영상 카드 + 모달Sermon Card

유튜브 영상을 사이트 안에서 라이트박스(전체 화면 팝업)로 재생합니다.
썸네일 자동 교체 + 영상 자동 재생 + ESC 키로 닫기까지 한 컴포넌트에서 처리합니다.

언제 쓰는가

  • 설교·찬양·예배 영상 카드 (주일·새벽·수요·청년 예배)
  • 외부 유튜브 사이트로 이탈하지 않고 사이트 안에서 시청
  • 한 페이지에 여러 영상이 카드 그리드로 노출되는 경우

전체 코드

components/preview/SermonCard.tsxtsx
"use client";

import { useEffect, useState } from "react";
import { Play, X } from "lucide-react";

export type Sermon = {
  title: string;
  pastor: string;
  bible?: string;
  date: string;
  youtubeId: string;
};

export function SermonCard({ sermon }: { sermon: Sermon }) {
  const [open, setOpen] = useState(false);

  // YouTube 썸네일 — 고화질(maxresdefault) 우선, 로드 실패 시 저화질(hqdefault)로 자동 교체
  const thumb = `https://i.ytimg.com/vi/${sermon.youtubeId}/maxresdefault.jpg`;
  const fallback = `https://i.ytimg.com/vi/${sermon.youtubeId}/hqdefault.jpg`;

  useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setOpen(false);
    };
    document.body.style.overflow = "hidden";
    document.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = "";
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  return (
    <>
      <button type="button" onClick={() => setOpen(true)} className="group block w-full text-left">
        <div className="relative aspect-[16/9] overflow-hidden rounded-lg bg-black">
          <img
            src={thumb}
            alt={sermon.title}
            className="h-full w-full object-cover"
            onError={(e) => {
              const img = e.currentTarget as HTMLImageElement;
              if (img.src !== fallback) img.src = fallback;
            }}
          />
          <div className="absolute inset-0 flex items-center justify-center bg-black/30">
            <span className="flex h-14 w-14 items-center justify-center rounded-full bg-white/90">
              <Play size={22} strokeWidth={2.2} />
            </span>
          </div>
        </div>
        <p className="mt-3 text-[14.5px] font-semibold">{sermon.title}</p>
        <p className="mt-1 text-[12px] text-[color:var(--color-text-muted)]">
          {sermon.date} · {sermon.bible && `${sermon.bible} · `}{sermon.pastor}
        </p>
      </button>

      {open && (
        <div
          role="dialog"
          aria-modal="true"
          className="fixed inset-0 z-[60] flex items-center justify-center bg-black/90 px-4 py-6"
          onClick={() => setOpen(false)}
        >
          <button onClick={() => setOpen(false)} aria-label="닫기" className="absolute right-5 top-5">
            <X size={20} />
          </button>
          <div className="aspect-video w-full max-w-[1180px]" onClick={(e) => e.stopPropagation()}>
            <iframe
              src={`https://www.youtube.com/embed/${sermon.youtubeId}?autoplay=1&rel=0`}
              title={sermon.title}
              allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
              allowFullScreen
              className="h-full w-full"
            />
          </div>
        </div>
      )}
    </>
  );
}

사용 예

tsx
<SermonCard
  sermon={{
    title: "시간은 다시 돌아오지 않습니다",
    pastor: "김화수 담임목사",
    bible: "사도행전 9:1~9",
    date: "2026.05.10",
    youtubeId: "i6yHKs5bcfM",
  }}
/>

설계 포인트

Need Help

도움이 필요하신가요?

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