🗓 dot.daily — 모달 시스템 설계와 사용법
모달(Modal)은 사용자 경험에서 매우 중요한 UI 컴포넌트입니다.
단순한 팝업부터, 바텀시트, 풀스크린, 카드형 등 다양한 형태가 필요하고,
전역적으로 관리되어야 하며,
Provider는 React의 Context API에서 "하위 트리 전체에 어떤 값(상태, 함수 등)을 공급하는 컴포넌트"입니다.
<MyContext.Provider value={...}>
<App />
</MyContext.Provider>
src/components/ui/Modal/
components/
BottomSheetModal.tsx
FullScreenModal.tsx
ModalItem.tsx
providers/
FullScreenModalProvider.tsx
ModalProvider.tsx
// _app.tsx 또는 layout.tsx
<FullScreenModalProvider>
<App />
</FullScreenModalProvider>
import { useFullScreenModal } from '@/components/ui/Modal/providers/FullScreenModalProvider';
const { openModal, closeModal } = useFullScreenModal();
// 모달 열기
openModal('taskForm', { defaultDate: '2025-06-20' });
// 모달 닫기
closeModal();
<FullScreenModal open={true} onClose={closeModal} variant="card">
<TaskFormModal onClose={closeModal} />
</FullScreenModal>
<BottomSheetModal open={true} onClose={closeModal}>
<DateNavigationModal onClose={closeModal} />
</BottomSheetModal>
<ModalItem open={open} onClose={closeModal}>
<div>간단한 내용</div>
</ModalItem>
FullScreenModalProvider 내부에서 현재 열려있는 모달 이름에 따라 적절한 모달 컴포넌트를 렌더링합니다.
const FullScreenModalRenderer = () => {
const { modalName, modalProps, closeModal } = useFullScreenModal();
return (
<AnimatePresence>
{modalName === 'taskForm' && (
<FullScreenModal open={true} onClose={closeModal} variant="card">
<TaskFormModal onClose={closeModal} {...modalProps} />
</FullScreenModal>
)}
{modalName === 'retrospectForm' && (
<FullScreenModal open={true} onClose={closeModal} variant="card">
<RetrospectModal onClose={closeModal} />
</FullScreenModal>
)}
{modalName === 'dateNavigationForm' && (
<BottomSheetModal open={true} onClose={closeModal}>
<DateNavigationModal onClose={closeModal} />
</BottomSheetModal>
)}
</AnimatePresence>
);
};
import { useFullScreenModal } from '@/components/ui/Modal/providers/FullScreenModalProvider';
function MyComponent() {
const { openModal } = useFullScreenModal();
return (
<button onClick={() => openModal('taskForm', { defaultDate: '2025-06-20' })}>
할 일 등록
</button>
);
}
React에서 "포탈(Portal)"은 모달, 드롭다운, 토스트 등 "화면의 특정 DOM 계층 밖에" UI를 렌더링해야 할 때 사용하는 기술입니다.
import { createPortal } from 'react-dom';
function MyModal({ children }) {
return createPortal(
<div className="modal">{children}</div>,
document.body // body 바로 아래에 렌더링
);
}
import { createPortal } from 'react-dom';
function Modal({ open, children }) {
if (!open) return null;
return createPortal(
<div className="fixed inset-0 z-50 bg-black/40">{children}</div>,
document.body
);
}