Type something to search...

04. 공통 UI 컴포넌트 만들기 (UI.jsx)

요약

Gemini CLI로 구현하기 — 공통 UI 컴포넌트

  1. npx gemini
  2. 1
    src/components/UI.jsx 파일을 작성해줘. Button(variant, className, children, ...props), Spinner(message, full, className), Modal(onClose, children), Container(className, children) 4개의 공통 컴포넌트가 필요해. Tailwind CSS를 사용하고, Button의 variant별 스타일은 src/index.css에 @layer components로 정의해줘. [추가 요구사항]
  3. 사용 가이드: [추가 요구사항]에 필요한 변형이 있으면 추가하고, 없으면 삭제한다. CSS 부분은 src/index.css에 붙여넣는다.

1. 왜 공통 UI 컴포넌트를 먼저 만들까?

이 프로젝트에서는 버튼, 로딩 표시, 모달(팝업), 컨테이너(감싸는 영역) 가 여러 페이지에서 반복된다. 이것들을 UI.jsx 한 파일에 모아두면, 필요한 곳에서 가져다 쓰기만 하면 된다.

컴포넌트사용처
Button헤더 검색, 뒤로가기, 예고편 보기, 페이지네이션
Spinner데이터 로딩 중 표시
Modal예고편 영상 팝업
Container섹션 공통 레이아웃

2. Button — 버튼 컴포넌트

src/components/ 폴더 안에 UI.jsx 파일을 새로 만든다. 이 파일에 4개의 컴포넌트를 순서대로 작성한다. 먼저 Button부터 시작한다. variant prop으로 버튼 종류를 지정하면 CSS 클래스 btn-{variant}가 적용된다.

src/components/UI.jsx
1
export function Button({ variant = "primary", className = "", children, ...props }) {
2
return (
3
<button className={`btn-${variant} ${className}`} {...props}>
4
{children}
5
</button>
6
);
7
}
설명
1{ variant, className, children, ...props } — 부모 컴포넌트가 전달하는 **Props(프롭스)**이다. <Button variant="danger">삭제</Button>처럼 쓰면 variant"danger", children"삭제"가 들어온다. variant의 기본값은 "primary"이다.
3템플릿 리터럴로 btn-${variant} 클래스명을 동적으로 생성한다. variant="danger"이면 btn-danger 클래스가 적용된다. {...props}로 나머지 속성을 button 태그에 전달한다.
1
// 이렇게 전달하면
2
<Button variant="danger" onClick={handleClick} disabled={true}>삭제</Button>
3
4
// Button 내부에서 이렇게 풀린다
5
// variant = "danger"
6
// className = ""
7
// children = "삭제"
8
// props = { onClick: handleClick, disabled: true }

...props는 명시적으로 꺼내지 않은 나머지 속성 전부를 하나의 객체로 모은다. 이것을 <button {...props}>로 전달하면 onClick, disabled 등이 그대로 button 태그에 적용된다.

버튼 스타일은 src/index.css에 CSS 클래스로 정의한다. 아래 내용을 index.css 파일에 추가한다.

src/index.css
1
@layer components {
2
.btn-primary { @apply bg-yellow-400 text-black hover:bg-yellow-300; }
3
.btn-danger { @apply bg-red-600 text-white hover:bg-red-500; }
4
.btn-ghost { @apply text-white hover:text-yellow-400; }
5
.btn-secondary { @apply bg-gray-800 text-white hover:bg-gray-700 disabled:opacity-30; }
6
}
구문설명
@layer componentsTailwind CSS의 레이어 시스템이다. base(기본 태그 스타일) → components(컴포넌트 클래스) → utilities(유틸리티 클래스) 순으로 우선순위가 높아진다. 커스텀 컴포넌트 클래스는 components 레이어에 작성한다.
@applyCSS 파일 안에서 Tailwind 유틸리티 클래스를 그대로 사용하는 지시어이다. @apply bg-yellow-400background-color: #facc15와 동일하다.
.btn-primary<Button variant="primary">일 때 적용되는 클래스이다. btn-${variant} 패턴으로 자동 연결된다.

3. Spinner — 로딩 표시

같은 UI.jsx 파일에서, Button 함수 바로 아래에 이어서 Spinner를 작성한다. full prop이 true이면 화면 전체를 덮는 로딩 화면을 렌더링하고, false이면 텍스트만 표시한다.

src/components/UI.jsx
1
export function Spinner({ message = "불러오는 중...", full = false, className = "" }) {
2
if (full) {
3
return (
4
<div className="bg-black min-h-screen flex items-center justify-center">
5
<p className="text-white text-2xl animate-pulse">{message}</p>
6
</div>
7
);
8
}
9
return <p className={`text-white text-xl ${className}`}>{message}</p>;
10
}
설명
1message(표시 텍스트), full(전체 화면 여부), className(추가 클래스)을 받는다. 모두 기본값이 있다.
2-7fulltrue이면 검정 배경으로 화면 전체를 채우고 가운데에 메시지를 표시한다.
4min-h-screen — 최소 높이를 화면 전체 높이로 설정한다. flex — 자식 요소를 유연하게 배치하는 레이아웃 방식이다. items-center(세로 가운데)와 justify-center(가로 가운데)를 합치면 화면 정중앙에 배치된다.
5animate-pulse — 깜빡거리는 효과이다. 로딩 중이라는 느낌을 준다.
9fullfalse이면 텍스트만 간단히 표시한다.

4. Modal — 팝업 컴포넌트

같은 UI.jsx 파일에서, Spinner 함수 바로 아래에 이어서 Modal을 작성한다. fixed inset-0으로 화면 전체를 덮는 오버레이를 생성하고, 그 위에 콘텐츠를 가운데 배치하는 팝업 컴포넌트이다.

src/components/UI.jsx
1
export function Modal({ onClose, children }) {
2
return (
3
<div className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center p-4">
4
<div className="relative">
5
<Button variant="ghost" onClick={onClose} className="absolute -top-10 right-0 text-2xl">
6
<FontAwesomeIcon icon={faXmark} />
7
</Button>
8
{children}
9
</div>
10
</div>
11
);
12
}
설명
1onClose(닫기 함수)와 children(팝업 안에 표시할 내용)을 받는다.
3fixed inset-0으로 화면 전체를 덮고, bg-black/90으로 어두운 배경을 만든다.
5-7우상단에 X 닫기 버튼을 배치한다. 클릭하면 onClose가 호출된다.
8children 자리에 예고편 영상 등이 들어온다.

5. Container — 섹션 레이아웃

같은 UI.jsx 파일에서, Modal 함수 바로 아래에 마지막 컴포넌트인 Container를 작성한다. container mx-auto로 최대 너비를 제한하고 가로 중앙에 배치하는 공통 레이아웃 컴포넌트이다.

src/components/UI.jsx
1
export function Container({ className = "", children }) {
2
return (
3
<section className={`px-11 ${className}`}>
4
<div className="container mx-auto">
5
{children}
6
</div>
7
</section>
8
);
9
}
설명
1className = ""은 기본값이다. 사용하는 곳에서 추가 클래스를 넘기지 않으면 빈 문자열이 된다.
3px-11 — 좌우에 약 44px의 여백(패딩)을 준다.
4container — 화면 크기에 따라 최대 너비가 자동으로 제한된다. mx-auto — 좌우 마진을 자동으로 같게 맞춰서 가운데 정렬한다. 이 두 클래스는 Tailwind에서 레이아웃을 잡을 때 가장 자주 쓰는 조합이다.

6. 전체 코드 확인