트러블슈팅

[React] 모달(modal) 레이아웃 구현하기(feat. Typescript, Tailwind)

emmaOH! 2024. 11. 11. 23:22

🌵 모달이란

모달이란 현재 열려있는 페이지에 포함된 창으로, 페이지 위에 일종의 '레이어(층)'을 까는 것을 말합니다.
현재 페이지를 '부모', 현재 페이지에서 열린 모달을 '자식'으로 생각할 수 있습니다.

- 출처 블로그
모달에 대한 더 자세한 내용과 모달과 팝업의 차이는 다음 블로그를 참고해주세요.

 

이번 글에서는 웹 서비스에서 많이 사용되는 모달 구현에 필요한 코드를 정리해보겠습니다.

프로필 편집하기 예시 모달

 


🌵 전체 코드

아래의 코드는 'Modal.tsx'라는 이름의 컴포넌트 파일로,

사진에서 초록색 선으로 둘러싸인 부분과 같이 내용을 제외한 모달의 기본 틀을 제공합니다.

import { ComponentPropsWithoutRef } from 'react';
import closeIcon from '@icons/close_icon.svg';	// 모달을 닫는 X 아이콘

interface ModalProps extends ComponentPropsWithoutRef<'div'> {
  onClick: () => void;	// 모달 닫기를 수행하는 함수
}

export default function Modal({ children, onClick }: ModalProps) {
  return (
  	// 블러 처리된 모달의 뒷 배경
    <div
      role="presentation"
      className="fixed inset-0 z-50 flex h-full w-full cursor-default items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm"
      onClick={onClick}
      aria-label="모달 뒷 배경"
    >
    
      {/* 모달 프레임 */}
      <section
        role="dialog"
        aria-modal="true"
        className={"relative rounded-xl bg-[#1b1b22] px-5 py-10 md:px-10 md:py-[60px] xl:rounded-2xl"}
        onClick={e => e.stopPropagation()}
        onKeyDown={e => {
          if (e.key === 'Enter' || e.key === ' ') e.stopPropagation()
        }}
      >
      
        {/* 모달 닫기 버튼(X 아이콘) */}
        <button className="absolute right-4 top-4 ml-auto md:right-5 md:top-5 md:w-9 xl:w-10" onClick={onClick}>
          <img className="md:w-9 xl:w-10" src={closeIcon} alt="닫기 아이콘" width={24} height={24} />
        </button>
        
        {/* 모달 안에 들어갈 내용(프롭스로 전달 받음) */}
        {children}
      </section>
    </div>
  );
}

 


🌵 코드 설명

0) 모달 컴포넌트

이 컴포넌트는 전체 화면에 모달을 띄우고, 배경을 반투명하고 어둡게 만들어 사용자가 모달 이외의 요소와 상호작용 할 수 없게 합니다.

 

1) 모달 뒷 배경 영역 - <div>

<div
  role="presentation"
  className="fixed inset-0 z-50 flex h-full w-full cursor-default items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm"
  onClick={onClick}
  aria-label="모달 뒷 배경"
>
  {/* 생략 */}
</div>
  • 모달 영역을 제외한 뒷 배경을 담당합니다.
  • <div> 태그에 onClick 이벤트를 주어, 뒷 배경을 클릭할 경우 모달이 닫히게 합니다.

 

2) 모달 본체 프레임 영역 - <section>

<section
  role="dialog"
  aria-modal="true"
  className={"relative rounded-xl bg-[#1b1b22] px-5 py-10 md:px-10 md:py-[60px] xl:rounded-2xl"}
  onClick={e => e.stopPropagation()}
  onKeyDown={e => {
    if (e.key === 'Enter' || e.key === ' ') e.stopPropagation()
  }}
>
    {/* 생략 */}
</section>
  • 모달 본체 영역을 담당합니다.
  • 뒷 배경을 클릭했을 때 동작하는 '모달 닫기 이벤트'가 모달 영역까지 전파되지 않도록 = 모달 영역 클릭 시에는 모달이 닫히지 않도록 'e.stopPropagation()'으로 이벤트 전파를 막습니다.
  • 추가로 'Enter' 또는 'Space' 키가 눌렸을 때 발생하는 키보드 이벤트의 전파도 막아줍니다.

 

3) 닫기 버튼 - <button> 및 내용

import closeIcon from '@icons/close_icon.svg';	

{/* 생략 */}

<button className="absolute right-4 top-4 ml-auto md:right-5 md:top-5 md:w-9 xl:w-10" onClick={onClick}>
  <img className="md:w-9 xl:w-10" src={closeIcon} alt="닫기 아이콘" width={24} height={24} />
</button>

{children}
  • 아이콘 파일을 <button> 태그 안에 넣어, X 아이콘을 클릭할 경우 onClick 이벤트가 동작하여 모달이 닫힙니다.
  • 프롭스로 전달받은 모달 본문 내용을 {children}으로 보여줍니다.

 


🌵용하기

원하는 부모 컴포넌트에서 모달을 띄우기 위해 제가 구현한 코드의 일부를 예시로 보겠습니다.

 

사용자 프로필 컴포넌트 안에 '프로필 편집' 버튼을 누르면 프로필을 편집할 수 있는 모달이 화면에 띄워집니다.

프로필 편집 모달이 열림 여부를 상태로 관리하고, 모달의 열림/닫힘(토글)을 제어하는 'toggleEditProfileModal()' 함수를 모달 컴포넌트에 프롭스로 전달합니다.

모달 띄우기와 관련되지 않은 코드는 생략하였습니다.

import { useCallback, useState } from 'react'
import Modal from '../@shared/Modal'

export default function Profile() {
  const [isEditProfileModalOpen, setIsEditProfileModalOpen] = useState<boolean>(false)

  const toggleEditProfileModal = useCallback(
  	() => setIsEditProfileModalOpen(prev => !prev)
  , [])

  const handleEditProfileButtonClick = () => {
    toggleEditProfileModal()
  }

  return (
    <>
      <button onClick={handleEditProfileButtonClick}>
        프로필 편집
      </button>
      
      {/* 생략 */}
      
      {isEditProfileModalOpen && (
        <Modal onClick={toggleEditProfileModal}>
          모달에 들어갈 본문 내용입니다.
        </Modal>
      )}
    </>
  )
}
  • 모달을 띄우고 싶은 부모 컴포넌트에서 위에서 만들어준 'Modal.tsx' 파일을 임포트해옵니다.
  • 모달의 본문으로 넣고 싶은 내용을 작성하여 <Modal> 태그로 감싸면, 해당 내용이 <Modal> 컴포넌트의 children으로 전달되어 화면에 보입니다.
참고한 블로그입니다

 


읽어주셔서 감사합니다:)