코드 출처 : https://github.com/Cluster-Taek/next14-boilerplate
GitHub - Cluster-Taek/next14-boilerplate
Contribute to Cluster-Taek/next14-boilerplate development by creating an account on GitHub.
github.com
users/page.tsx
'use client';
const Page = () => {
const pageStyle = PageSva();
const { openModal } = useModals();
const handleClickCreateButton = () => {
openModal({
id: MODAL.USER_CREATE,
component: <UserCreateFormModal />,
});
};
return (
<Box className={pageStyle.wrapper}>
<Button onClick={handleClickCreateButton}>Create</Button>
</Box>
);
};
export default Page;
useModals.ts
import { ModalContext } from '@/contexts/modal-provider';
import { useContext } from 'react';
const useModals = () => {
const { openModal, closeModal } = useContext(ModalContext);
return { openModal, closeModal };
};
export default useModals;
openModal({
id: MODAL.USER_CREATE,
component: <UserCreateFormModal />,
});
- openModal 함수는 모달을 열 때 필요한 설정을 객체 형태로 받습니다.
- id: MODAL.USER_CREATE
- 이 ID는 모달의 고유 식별자입니다.
- MODAL.USER_CREATE는 constants/modal-key-constants에서 정의된 값으로, 특정 모달을 식별하기 위한 키 역할을 합니다.
- component: <UserCreateFormModal />
- 이 속성은 열릴 모달에 렌더링할 컴포넌트를 지정합니다.
- 여기서는 <UserCreateFormModal /> 컴포넌트를 모달 내부에 렌더링합니다.
- id: MODAL.USER_CREATE
- 버튼을 클릭하면 openModal이 호출되어 MODAL.USER_CREATE라는 ID를 가진 모달이 열리고, 모달 내부에는 <UserCreateFormModal /> 컴포넌트가 렌더링됩니다.
- 기존에 ModalProvider를 통해 모달을 생성합니다.
1. 모달을 띄우는 곳
1. 최상단의 layout.tsx 의 <Modal />
import './globals.css';
import Modal from '@/components/common/modal';
import CoreProvider from '@/contexts/core-provider';
import dayjs from 'dayjs';
import 'dayjs/locale/ko';
import type { Metadata } from 'next';
import localFont from 'next/font/local';
dayjs.locale('ko');
const pretendard = localFont({
src: '../fonts/PretendardVariable.woff2',
weight: '45 920',
variable: '--font-pretendard',
});
export const metadata: Metadata = {
title: 'Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${pretendard.className} font-sans`}>
<CoreProvider>
{children}
<Modal />
</CoreProvider>
</body>
</html>
);
}
- ModalProvider
- CoreProvider 내부에서 ModalProvider가 사용되며, ModalContext를 통해 애플리케이션의 모든 하위 컴포넌트들이 모달 상태에 접근할 수 있게 됩니다.
- 이 컨텍스트는 모달을 열고 닫는 로직을 제어하는 데 사용됩니다.
- Modal
- Modal은 실제로 모달 UI를 렌더링하는 컴포넌트입니다.
- ModalProvider에서 관리하는 상태인 openedModals를 참조하여 활성화된 모달의 목록을 화면에 렌더링합니다.
- 왜 Modal이 RootLayout에 위치하는가?
- Modal은 UI 출력의 위치를 정하는 컴포넌트입니다. 보통 모달은 애플리케이션의 루트나 레이아웃 수준에서 렌더링되어야 합니다.
- 이유는 모달이 다른 UI 요소들 위에 표시되어야 하기 때문에, DOM 트리 상단에 위치해야 합니다.
- z-index가 높아도, DOM 트리 하위에 있으면 다른 컴포넌트의 CSS나 레이아웃에 영향을 받을 수 있습니다.
- 반면, ModalProvider는 상태 관리를 위한 로직을 제공하는 역할이므로 애플리케이션의 컨텍스트 트리 안쪽에 있어도 문제가 되지 않습니다.
- 요약
- ModalProvider는 모달 상태를 관리하는 컨텍스트 제공자이고,
- Modal은 해당 컨텍스트를 소비하여 모달을 렌더링하는 UI 컴포넌트입니다.
- 따라서 Modal은 루트 레벨에서 렌더링되어야 다른 UI 위에 렌더링될 수 있으며, ModalProvider는 CoreProvider 내부에 있어도 전혀 문제가 없습니다.
modal.tsx
'use client';
import { MOTION } from '@/constants/motion-constants';
import { ModalContext } from '@/contexts/modal-provider';
import useModals from '@/hooks/use-modals';
import { sva } from '@/styled-system/css';
import { Box } from '@/styled-system/jsx';
import { motion } from 'motion/react';
import { useContext, useEffect } from 'react';
const Modal = () => {
const modalStyle = ModalSva();
const { openedModals } = useContext(ModalContext);
const { closeModal } = useModals();
useEffect(() => {
document.body.style.overflow = openedModals.length > 0 ? 'hidden' : 'auto';
}, [openedModals]);
return (
<>
{openedModals.map((modal, index) => {
const { id, component } = modal;
return (
<Box className={modalStyle.wrapper} key={index}>
<Box className={modalStyle.dimmed} onClick={() => closeModal(id)} />
<motion.div className={modalStyle.modal} {...MOTION.POP}>
{component}
</motion.div>
</Box>
);
})}
</>
);
};
export default Modal;
const ModalSva = sva({
slots: ['wrapper', 'dimmed', 'modal'],
base: {
wrapper: {
position: 'fixed',
left: 0,
top: 0,
zIndex: 10,
display: 'flex',
height: '100dvh',
width: '100vw',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
},
dimmed: {
position: 'fixed',
top: 0,
left: 0,
width: '100dvw',
height: '100dvh',
overflow: 'hidden',
backgroundColor: 'black',
opacity: 0.48,
},
modal: {
position: 'relative',
},
},
});
- ModalSva()는 스타일을 반환하는 함수입니다. 스타일은 styled-system을 사용하여 관리됩니다.
- openedModals는 ModalContext에서 관리되는 배열로, 현재 열려 있는 모달들의 목록을 포함합니다.
- closeModal은 모달을 닫는 함수입니다.
- useEffect는 모달이 열리면 document.body.style.overflow를 'hidden'으로 설정해 페이지의 스크롤을 막고, 모달이 닫히면 'auto'로 돌아가게 합니다.
- openedModals.map()은 열려 있는 모달들을 하나씩 렌더링하며, 각 모달의 배경을 클릭하면 해당 모달이 닫히도록 onClick 이벤트를 설정합니다.
- sva는 스타일을 정의하는 함수로, 스타일을 객체 형태로 반환합니다. slots는 스타일을 적용할 각 요소의 이름을 정의하고, base는 각 요소에 대한 스타일을 정의합니다.
- wrapper: 모달 전체를 감싸는 요소로, 화면 전체를 차지하고 중앙에 정렬됩니다.
- dimmed: 모달의 배경을 흐리게 처리하는 요소입니다. 배경은 검은색이고 투명도를 48%로 설정하여, 모달이 강조되도록 합니다.
- modal: 실제 모달 내용이 들어가는 요소로, 상대적인 위치로 설정되어 있습니다.
'개발 공부 > React' 카테고리의 다른 글
| Next.js 에서 모달 띄우기와 서버작업을 통한 유저 정보 받기 및 등록 (4) (0) | 2025.01.05 |
|---|---|
| Next.js 에서 모달 띄우기와 서버작업을 통한 유저 정보 받기 및 등록 (3) (1) | 2025.01.04 |
| Next.js 에서 모달 띄우기와 서버작업을 통한 유저 정보 받기 및 등록 (1) (1) | 2025.01.02 |
| Next.js에서 레이아웃 구성 (3) / ModalProvider (1) | 2024.12.29 |
| Next.js에서 레이아웃 구성 (2) / QueryProvider (0) | 2024.12.26 |