🌵 모달이란
모달이란 현재 열려있는 페이지에 포함된 창으로, 페이지 위에 일종의 '레이어(층)'을 까는 것을 말합니다.
현재 페이지를 '부모', 현재 페이지에서 열린 모달을 '자식'으로 생각할 수 있습니다.
- 출처 블로그
모달에 대한 더 자세한 내용과 모달과 팝업의 차이는 다음 블로그를 참고해주세요.
이번 글에서는 웹 서비스에서 많이 사용되는 모달 구현에 필요한 코드를 정리해보겠습니다.
🌵 전체 코드
아래의 코드는 '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으로 전달되어 화면에 보입니다.
참고한 블로그입니다
읽어주셔서 감사합니다:)