프론트엔드/Component

[Component] 전역 모달 컴포넌트

순코딩 2024. 12. 18. 13:57
이 글은 Next.js, MUI, styled-component, zustand 환경에서 작성된 코드를 공유합니다.

 

소개

어디서든 열 수 있는 모달 컴포넌트입니다.

 

코드
모달 개발
src / components / InfoModal.tsx
"use client";

import styled from "styled-components";
import Modal from "@mui/material/Modal";
import Fade from "@mui/material/Fade";
import { Box, Button } from "@mui/material";
import { useModalStore } from "@/store";

const InfoModal = () => {
  const {isOpen, title, content, close} = useModalStore();

  return (
    <>
      {/* Modal 컴포넌트 */}
      <Modal
        open={isOpen} // 모달의 열림/닫힘 상태 제어
        onClose={close} // 모달 닫기 이벤트 핸들러
        closeAfterTransition // 트랜지션 종료 후 모달 제거
        slotProps={{
          backdrop: {
            invisible: true,
          },
        }}
      >
        {/* Fade 애니메이션으로 모달 표시 */}
        <Fade in={isOpen} timeout={300}>
          {/* 모달 내부 내용 */}
          <StyledBox>
            <div>{title}</div>
            <div>{content}</div>
            <Button onClick={close}>닫기</Button>
          </StyledBox>
        </Fade>
      </Modal>
    </>
  );
};

export default InfoModal;

// Styled Component: 모달 박스 스타일 정의
const StyledBox = styled(Box)`
  position: absolute; // 박스를 화면 중앙에 위치시키기 위한 절대 위치 지정
  top: 50%; // 화면 세로 중앙
  left: 50%; // 화면 가로 중앙
  transform: translate(-50%, -50%); // 박스 크기 고려하여 정확히 중앙에 배치
  width: 200px; // 박스의 너비
  background-color: white;
  border: 2px solid #000; // 검은색 테두리
  box-shadow: 24px 24px 24px rgba(0, 0, 0, 0.2); // 그림자 효과
  padding: 16px; // 내부 여백
`;

모달이 열림 상태일 때 보여줄 컴포넌트입니다.

모달 열림 여부는 store에 정의된 state를 사용합니다.

 

모달 상태를 저장할 store 개발
src / store / modalStore.ts
import { ModalState } from "@/types/store/modal.type";
import { create } from "zustand";

export const useModalStore = create<ModalState>((set) => ({
  isOpen: false,
  title: "",
  content: "",
  open: ({ title, content }) => set((state) => ({ ...state, isOpen: true, title: title, content: content })),
  close: () => set((state) => ({ ...state, isOpen: false })),
}));

 

스토어 정의에 필요한 타입 정의
src / type / store / modal.type.ts
export type ModalState = {
  isOpen: boolean;
  title: string | null;
  content: string | null;
  open: ({ title, content }: openFuncParam) => void;
  close: () => void;
};

type openFuncParam = {
  title: string;
  content: string;
};

 

모달 관련 상태가 저장된 store와 store type입니다.

isOpen으로 모달 열림 여부를 판단하며 title과 content에 모달에 들어갈 내용이 담겨있습니다.

open함수로 모달 열기와 동시에 모달 내용을 설정할 수 있으며 close로 모달을 닫을 수 있습니다.

 

최상위 DOM에서 모달 컴포넌트 호출
src / app / layout.tsx
import type { Metadata } from "next";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
import InfoModal from "@/components/common/view/InfoModal";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ko">
      <body>
              <InfoModal/>
              {children}
      </body>
    </html>
  );
}

모달을 DOM 최상위에 위치하기 위해 layout.tsx 내부에 삽입합니다.

 

원하는 위치에서 모달 열고 닫기
"use client";

import { useModalStore } from "@/store";

const Home = () => {
  const { open } = useModalStore();

  const onClick = ()=>{
    open({title:"예시 타이틀", content:"예시 내용"});
  }
  
  return (
    <>
      <button onClick={onClick}>모달 열기</button>
    </>
  );
};

export default Home;

원하는 위치에서 open 함수를 통해 모달을 열 수 있습니다.