React로 Modal 컴포넌트 만들기 - 1편

Modal의 정의

  • 웹이나 앱 UI에서 자주 쓰이는 "팝업 대화상자" 형태의 컴포넌트.
  • 사용자가 현재 보고 있는 화면 위에 오버레이 형태로 나타나며, 사용자의 주의를 집중시키거나 추가 입력을 받는 용도로 사용된다.
modal

Modal의 동작 특성

  • 사용자가 현재 페이지를 떠나게 되면 Modal 인스턴스도 같이 제거된다.
  • Modal을 열면 스크롤이 잠기고 닫으면 해제된다.
  • 오버레이 배경 영역을 클릭하거나 ESC키를 입력하면 닫는다.
  • 하나의 Modal을 표시하고 있는 상태에서 추가로 표시하는 경우 더 높은 레이어에서 표시된다.

Modal의 구조

modal-sketch

Modal을 만들기 위해 필요한 두 하위 컴포넌트는 ModalLayer, ModalPanel이다.

각각의 컴포넌트에 역할을 부여하여 보자.

  • ModalLayer
    • 페이지 콘텐트가 표시되는 루트 레이어보다 높은 레이어에 Overlay를 표시하는 역할.
    • ModalPanel의 위치가 항상 화면상의 중앙에 오도록 하는 역할.
    • 이 영역을 클릭하면 모달을 닫는 역할.
    • ESC키를 입력하면 모달을 닫는 역할.
  • ModalPanel
    • Modal에 표시하고 싶은 콘텐츠를 보여주는 역할.

Modal의 마크업과 스타일링

ModalLayer 컴포넌트를 작성해보자.

우선 사용자의 화면을 꽉 채우는 하나의 불투명한 레이어가 필요하다.

ModalLayer.tsx
1import { PropsWithChildren } from 'react';
2import { twMerge } from 'tailwind-merge';
3
4interface Props extends PropsWithChildren {
5  className?: string;
6}
7
8export default function ModalLayer({
9  className,
10  children,
11}: PropsWithChildren<Props>) {
12  return (
13    <div
14      className={twMerge(
15        'fixed inset-0 bg-black/50 flex items-center justify-center',
16        className,
17      )}
18    >
19      {children}
20    </div>
21  );
22}

ModalPanel 컴포넌트를 작성한다.

이 컴포넌트는 하얀 네모박스안에 children 으로 전달한 어떤 요소든 표시하기 시작한다.

ModalPanel.tsx
1import { PropsWithChildren } from 'react';
2
3export default function ModalPanel({ children }: PropsWithChildren) {
4  return <div className="bg-white rounded-[4px] shadow-md">{children}</div>;
5}

두 컴포넌트를 합성하여 Modal 컴포넌트로 추상화하자.

React.createPortal을 통해 document.body 하위에 Modal을 위치시키는 것이 중요하다.

왜 Portal 을 사용해야 할까? 그 이유는 다음편에서 다루도록 하자.

Modal.tsx
1import { PropsWithChildren } from 'react';
2import { createPortal } from 'react-dom';
3import ModalLayer from './ModalLayer.tsx';
4import ModalPanel from './ModalPanel.tsx';
5
6export default function Modal({ children }: PropsWithChildren) {
7  return createPortal(
8    <ModalLayer>
9      <ModalPanel>{children}</ModalPanel>
10    </ModalLayer>,
11    document.body,
12  );
13}

여기까지가 Modal 컴포넌트를 만들기위한 구조화, HTML 마크업, CSS 스타일링 과정이다.

현재까지의 구현은 렌더링만 가능하며 열고 닫거나 할 수 없는 상태이다.

다음편에서는 Modal의 동작특성을 만족하기 위해 기능을 추가하는 내용을 다룬다.