08. Section과 Card — 영화 카드 목록 만들기
더보기 링크가 있는 섹션, 호버 오버레이 카드 컴포넌트를 만든다
코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.
구문
💡TIP
Gemini CLI로 구현하기 — Section & Card 컴포넌트
- 프롬프트:
gemini "src/components/Section.jsx와 Card.jsx를 작성해줘. Section은 title, items, category props를 받아서 제목, 더보기 링크(/category/[category]), [열 수]열 그리드 카드 목록을 렌더링해줘. Card는 item prop을 받아서 TMDB 포스터 이미지(https://image.tmdb.org/t/p/w500/), 호버 시 줄거리+평점 오버레이, 하단에 제목+평점+개봉일을 표시해줘. Tailwind CSS group-hover를 활용해줘."- 사용 가이드:
[열 수]를 원하는 그리드 열 개수(기본: 4)로 바꾼다. 카드에 기능을 추가하려면 프롬프트 끝에 요구사항을 덧붙인다.
- 사용 가이드:
1. 이번 편에서 만들 것
| 컴포넌트 | 역할 |
|---|---|
Section | 제목 + "더보기" 링크 + 영화 카드 그리드 |
Card | 포스터 + 호버 시 줄거리/평점 오버레이 + 하단 정보 |
2. Section.jsx 작성
src/components/Section.jsx 파일을 열고 임시 코드를 모두 지운 뒤 아래를 작성한다. Section은 제목, "더보기" 링크, 카드 그리드를 하나의 단위로 묶는 레이아웃 컴포넌트이다. title과 items prop만 바꿔서 세 가지 카테고리에 재사용한다.
import { Link } from "react-router";
import { Card } from "./Card.jsx";
import { Container } from "./UI.jsx";
export function Section({ title, items, category }) {
return (
<Container className="py-24">
<div className="flex items-center justify-between pt-10 pb-5 px-3">
<h2 className="text-4xl font-bold text-white">{title}</h2>
{category && (
<Link to={`/category/${category}`} className="text-yellow-400 hover:text-yellow-300 text-sm font-bold">
더보기 →
</Link>
)}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
{items.map((el) => (
<Card key={el.id} item={el} />
))}
</div>
</Container>
);
}
| 줄 | 설명 |
|---|---|
| 3 | 4편에서 만든 Container 컴포넌트를 가져온다. 공통 레이아웃 컴포넌트이다. |
| 5 | title(제목), items(영화 배열), category(카테고리 키)를 받는다. |
| 8 | flex — 자식 요소를 가로로 나란히 배치하는 Tailwind 클래스이다. justify-between — 자식들을 양쪽 끝으로 밀어낸다. 결과적으로 제목은 왼쪽, "더보기"는 오른쪽에 배치된다. |
| 10-14 | category가 있을 때만 "더보기" 링크를 표시한다. 클릭하면 /category/popular 같은 페이지로 이동한다. →는 → 화살표이다. |
| 16 | grid — CSS Grid 레이아웃이다. grid-cols-4는 4열 구성이다. gap-6은 그리드 아이템 사이의 간격이다. sm:, md: 반응형 접두사로 뷰포트 크기에 따라 열 수가 변경된다. |
| 17-19 | .map()(맵)은 배열의 각 요소에 콜백 함수를 실행하고, 그 반환값으로 구성된 새 배열을 만드는 메서드이다. items에 영화가 20개 있으면 Card 컴포넌트 20개로 이루어진 새 배열이 만들어지고, React가 이를 화면에 렌더링한다. |
3. Card.jsx — 1단계: import + 포스터
src/components/Card.jsx 파일을 열고 임시 코드를 모두 지운 뒤 작성한다. Card는 영화 포스터, 호버 오버레이(줄거리·평점), 하단 정보를 표시하는 컴포넌트이다. 클릭하면 영화 상세 페이지로 이동한다.
import { Link } from "react-router";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHeart, faStar } from "@fortawesome/free-solid-svg-icons";
export function Card({ item }) {
const poster = `https://image.tmdb.org/t/p/w500/${item.poster_path}`;
| 줄 | 설명 |
|---|---|
| 3 | 하트(faHeart)와 별(faStar) 아이콘을 가져온다. |
| 5 | props 이름이 item이다. (이전 교안에서는 movie였다.) |
| 6 | TMDB 포스터 이미지 URL을 조합한다. |
4. Card.jsx — 2단계: JSX 렌더링
return (
<div className="card py-10 group">
<Link to={`/movie/${item.id}`}>
<div className="relative overflow-hidden rounded-md aspect-[2/3]">
<img
className="object-cover w-full h-full transition-transform duration-300 group-hover:scale-110"
src={poster}
alt={item.title}
/>
<div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col justify-end p-4">
<p className="text-white text-sm line-clamp-3">{item.overview}</p>
<div className="flex items-center gap-1 mt-2 text-yellow-400">
<FontAwesomeIcon icon={faStar} className="text-xs" />
<span className="text-sm font-bold">{(item.vote_average || 0).toFixed(1)}</span>
</div>
</div>
</div>
| 줄 | 설명 |
|---|---|
| 2 | group 클래스를 선언한다. 자식 요소에서 group-hover:를 사용할 수 있다. |
| 6 | group-hover:scale-110 — 카드에 마우스를 올리면 이미지가 1.1배 확대된다. |
| 10 | 호버 오버레이 — absolute inset-0은 top:0, right:0, bottom:0, left:0을 한 번에 설정하는 Tailwind 단축 클래스이다. 부모 요소를 완전히 덮는다. 평소에는 opacity-0(투명), 마우스를 올리면 group-hover:opacity-100(표시)으로 전환된다. |
| 11 | line-clamp-3은 줄거리를 3줄까지만 표시하고 나머지는 말줄임표로 처리한다. |
| 14 | (item.vote_average || 0).toFixed(1) — ||(OR 연산자)는 왼쪽이 거짓 값이면 오른쪽을 반환한다. 평점이 없으면 0으로 대체하고, .toFixed(1)로 소수점 1자리로 고정한다. |
5. 동작 확인
| 확인 항목 | 기대 결과 |
|---|---|
| 메인 페이지 스크롤 | 영화 포스터가 4열 그리드로 나열됨 |
| 카드에 마우스 올림 | 포스터가 확대되고, 어두운 오버레이에 줄거리+평점 표시 |
| "더보기 →" 클릭 | /category/popular 등 카테고리 페이지로 이동 |
| 카드 클릭 | /movie/숫자로 이동 |
| 모바일 크기 | 1열 → 2열 → 4열로 반응형 변화 |
6. 전체 코드
import { Link } from "react-router";
import { Card } from "./Card.jsx";
import { Container } from "./UI.jsx";
export function Section({ title, items, category }) {
return (
<Container className="py-24">
<div className="flex items-center justify-between pt-10 pb-5 px-3">
<h2 className="text-4xl font-bold text-white">{title}</h2>
{category && (
<Link to={`/category/${category}`} className="text-yellow-400 hover:text-yellow-300 text-sm font-bold">
더보기 →
</Link>
)}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
{items.map((el) => (
<Card key={el.id} item={el} />
))}
</div>
</Container>
);
}