Frontend/HTML

[HTML] <dialog> 태그 + createPortal로 모달(modal) 만들기?? [10/3 study]

동혁이 2024. 10. 3. 14:41

 

 

<dialog> 태그로 모달(modal) 만들기??

 

 

 

 

이 글을 작성하게 된 이유

원래는 실제 프로젝트에서 useModal 커스텀훅과 상위 파일인 layout.tsx에 div태그에 id값을 주어야 하는 방법까지 번거롭게 작업했었다.

하지만 강의를 들으면서 상위 layout 파일에 div 요소도 안넣고 더 간편하게 사용하는 방법을 알게 되어 글을 작성해본다.

import '../styles/globals.css';

import Providers from './providers';

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ko">
      <body>
        <Providers>{children}</Providers>
        <div id="modal-root" />
      </body>
    </html>
  );
}
'use client';

import { type ReactNode } from 'react';
import ReactDOM from 'react-dom';

import { useModal } from '@/_hooks/useModal';

type ModalProps = {
  children: ReactNode;
  isOpen: boolean;
  onClose: () => void;
};

/**
 * 사용법
 * ex)
 * const { isOpen, openModal, closeModal } = useModal();
 * 
 *  <button onClick={openModal}>Open Modal</button>
    <Modal isOpen={isOpen} onClose={closeModal}>
      <div className="m-auto px-[90px] pb-[28px] pt-[26px] text-right text-[18px] md:w-[540px] md:px-[33px]">
        <p className="pb-[43px] pt-[53px] text-center">가입이 완료되었습니다!</p>
        <span className="flex justify-center md:justify-end">
          <button className="h-[42px] w-[138px] rounded-[8px] bg-black text-white">확인</button> // 버튼 컴포넌트 넣어주시면 될 것 같습니다!
          <button onClick={closeModal}>취소</button> // 버튼 컴포넌트 넣어주시면 될 것 같습니다!
        </span>
      </div>
    </Modal>
 */

/**
 * Modal 컴포넌트의 props
 *
 * @property {boolean} isOpen - Modal Open 여부
 * @property {() => void} onClose - Modal Close 함수
 * @property {ReactNode} children - Modal 자식 요소들
 */

export default function Modal({ isOpen, onClose, children }: ModalProps) {
  const { isMounted } = useModal();

  const handleBackgroundClick = (event: React.MouseEvent<HTMLDivElement>) => {
    // 배경 클릭 시 모달 닫기
    if (event.target === event.currentTarget) {
      onClose();
    }
  };

  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
    if (event.key === 'Escape') {
      onClose();
    }
  };

  if (!isOpen || !isMounted) return null;

  return ReactDOM.createPortal(
    <div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black bg-opacity-70" onClick={handleBackgroundClick} onKeyDown={handleKeyDown}>
      <div className="relative rounded-[12px] bg-white shadow-lg">{children}</div>
    </div>,
    document.getElementById('modal-root') as HTMLElement,
  );
}

 

바로바로 dialog 태그와 createPortal를 이용하면 된다!

딱봐도 코드가 더 간결해지고 children을 받으면서 쉽게 커스텀도 용이해졌다!

지금이라도 내 게시글을 보는 사람들은 이 방법으로 바꿨으면 좋겠다! (더 좋은 방법이 있으면 말좀요...! ㅎ)

import { createPortal } from "react-dom";
import React, { useRef, useState } from "react";

type ModalProps = {
  isOpen: boolean;
  onClose: () => void;
  children: React.ReactNode;
};

  const ModalComp = (
    <dialog
      ref={ref}
      open={isOpen}
      id={"modal"}
      className={`absolute left-0 top-0 w-screen h-screen bg-opacity-20 bg-gray-800 ${animation}`}
      onClick={(e) => {
        if (e.target === ref.current) {
          handleClose();
        }
      }}
    >
      <article
        id={"modal-content"}
        className={"fixed left-1/4 top-1/4 w-1/2 h-1/2 bg-white rounded-lg p-5"}
      >
        {children}
      </article>
    </dialog>
  );

  return createPortal(ModalComp, document.body);
}