diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx
index b31070a..8c9c2b3 100644
--- a/src/components/Modal/Modal.tsx
+++ b/src/components/Modal/Modal.tsx
@@ -24,6 +24,7 @@ export default function Modal({
ariaLabelledby,
ariaDescribedby,
className,
+ contentClassName,
closeOnOverlayClick = true,
closeOnEscape = true,
}: ModalProps) {
@@ -55,7 +56,7 @@ export default function Modal({
role="presentation"
>
.modalContent {
+ width: 384px;
+ height: 235px;
+ display: inline-flex;
+ padding: 16px 16px 32px;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ border-radius: 24px;
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+}
+
+.container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
+ box-sizing: border-box;
+}
+
+.buttonContainer {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ margin-right: 8px;
+}
+
+.header {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+}
+
+.title {
+ margin: 0;
+ color: var(--Text-Primary, #1e293b);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 19px;
+}
+
+.closeButton {
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.buttonContainer .closeButton,
+.buttonContainer .closeButton:hover:not(:disabled),
+.buttonContainer .closeButton:active:not(:disabled) {
+ border: none;
+ background: transparent;
+ color: inherit;
+}
+
+.form {
+ display: flex;
+ flex: 1;
+ min-height: 0;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.form .input {
+ display: flex;
+ width: 280px;
+ height: 48px;
+ padding: 16px;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: 1px solid var(--Border-Primary, #e2e8f0);
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+ margin: 0 auto;
+}
+
+.form .input::placeholder {
+ color: var(--Text-Default, #64748b);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 19px;
+}
+
+.footer {
+ display: flex;
+ justify-content: center;
+ margin-top: auto;
+}
+
+.button {
+ display: flex;
+ width: 280px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ flex-shrink: 0;
+ border: none;
+ border-radius: 12px;
+ background: var(--Color-Brand-Primary, #5189fa);
+ color: var(--Text-inverse, #fff);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+}
+
+@media (max-width: 480px) {
+ section > .modalContent {
+ border-radius: 24px 24px 0 0;
+ }
+}
diff --git a/src/components/Modal/domain/components/AddTodoList/AddTodoList.tsx b/src/components/Modal/domain/components/AddTodoList/AddTodoList.tsx
new file mode 100644
index 0000000..934aa92
--- /dev/null
+++ b/src/components/Modal/domain/components/AddTodoList/AddTodoList.tsx
@@ -0,0 +1,90 @@
+'use client';
+
+import Image from 'next/image';
+import type { FormEvent } from 'react';
+import BaseButton from '@/components/Button/base/BaseButton';
+import { Input } from '@/components/input';
+import Modal from '../../../Modal';
+import styles from './AddTodoList.module.css';
+import xMarkBig from '@/assets/icons/xMark/xMarkBig.svg';
+import {
+ CLOSE_BUTTON_ARIA_LABEL,
+ DEFAULT_PLACEHOLDER,
+ DEFAULT_SUBMIT_LABEL,
+ DEFAULT_TITLE,
+ TITLE_ID,
+} from './AddTodoList.constants';
+import type { AddTodoListProps } from './AddTodoList.types';
+export type { AddTodoListProps } from './AddTodoList.types';
+
+/**
+ * @param props.isOpen 모달 표시 여부를 boolean으로 전달합니다.
+ * @param props.onClose 모달을 닫을 때 실행할 함수를 전달합니다.
+ * @param props.onSubmit 할 일 생성 버튼 클릭 시 실행할 함수를 전달합니다.
+ * @param props.text 모달 제목과 버튼 문구 같은 텍스트 옵션을 객체로 전달합니다.
+ * @param props.input 할 일 입력창에 적용할 옵션을 객체로 전달합니다.
+ * @param props.closeOptions 오버레이 클릭과 Escape 닫힘 옵션을 객체로 전달합니다.
+ */
+export default function AddTodoList({
+ isOpen,
+ onClose,
+ onSubmit,
+ text,
+ input,
+ closeOptions,
+}: AddTodoListProps) {
+ const title = text?.title ?? DEFAULT_TITLE;
+ const submitLabel = text?.submitLabel ?? DEFAULT_SUBMIT_LABEL;
+ const inputPlaceholder = text?.inputPlaceholder ?? DEFAULT_PLACEHOLDER;
+ const closeOnOverlayClick = closeOptions?.overlayClick ?? true;
+ const closeOnEscape = closeOptions?.escape ?? true;
+
+ const handleSubmit = (e: FormEvent
) => {
+ e.preventDefault();
+ onSubmit();
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Modal/domain/components/AddTodoList/AddTodoList.types.ts b/src/components/Modal/domain/components/AddTodoList/AddTodoList.types.ts
new file mode 100644
index 0000000..2833191
--- /dev/null
+++ b/src/components/Modal/domain/components/AddTodoList/AddTodoList.types.ts
@@ -0,0 +1,20 @@
+import type { InputProps } from '@/components/input/types/types';
+import type { BaseDomainModalProps } from '../../types/types';
+
+export type TodoInputProps = Omit;
+
+export interface AddTodoListTextOptions {
+ title?: string;
+ submitLabel?: string;
+ inputPlaceholder?: string;
+}
+
+export interface AddTodoListInputOptions {
+ props?: TodoInputProps;
+}
+
+export interface AddTodoListProps extends BaseDomainModalProps {
+ onSubmit: () => void;
+ text?: AddTodoListTextOptions;
+ input?: AddTodoListInputOptions;
+}
diff --git a/src/components/Modal/domain/components/ChangePassword/ChangePassword.constants.ts b/src/components/Modal/domain/components/ChangePassword/ChangePassword.constants.ts
new file mode 100644
index 0000000..752fa1b
--- /dev/null
+++ b/src/components/Modal/domain/components/ChangePassword/ChangePassword.constants.ts
@@ -0,0 +1,10 @@
+export const TITLE_ID = 'change-password-title';
+export const NEW_PASSWORD_NAME = 'newPassword';
+export const CONFIRM_PASSWORD_NAME = 'confirmPassword';
+export const DEFAULT_TITLE = '비밀번호 변경하기';
+export const DEFAULT_NEW_PASSWORD_LABEL = '새 비밀번호';
+export const DEFAULT_CONFIRM_PASSWORD_LABEL = '새 비밀번호 확인';
+export const DEFAULT_NEW_PASSWORD_PLACEHOLDER = '새 비밀번호를 입력해주세요.';
+export const DEFAULT_CONFIRM_PASSWORD_PLACEHOLDER = '새 비밀번호를 다시 한 번 입력해주세요.';
+export const DEFAULT_CLOSE_LABEL = '닫기';
+export const DEFAULT_SUBMIT_LABEL = '변경하기';
diff --git a/src/components/Modal/domain/components/ChangePassword/ChangePassword.module.css b/src/components/Modal/domain/components/ChangePassword/ChangePassword.module.css
new file mode 100644
index 0000000..05f3488
--- /dev/null
+++ b/src/components/Modal/domain/components/ChangePassword/ChangePassword.module.css
@@ -0,0 +1,132 @@
+section > .modalContent {
+ display: flex;
+ width: 384px;
+ height: 353px;
+ padding: 16px 16px 32px;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
+ border-radius: 12px;
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+}
+
+.container {
+ width: 100%;
+ height: 100%;
+ padding-top: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ box-sizing: border-box;
+}
+
+.title {
+ margin: 0;
+ width: 100%;
+ color: var(--Text-Primary, #1e293b);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 19px;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ flex: 1;
+ min-height: 0;
+}
+
+.field {
+ display: flex;
+ width: 280px;
+ margin: 0 auto;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.label {
+ color: var(--Text-Primary, #1e293b);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 19px;
+}
+
+.field .input {
+ display: flex;
+ width: 280px;
+ height: 48px;
+ padding: 16px;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: 1px solid var(--Border-Primary, #e2e8f0);
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+}
+
+.field .input::placeholder {
+ color: var(--Text-Default, #64748b);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 19px;
+}
+
+.actions {
+ display: flex;
+ width: 280px;
+ margin: auto auto 0;
+ gap: 8px;
+}
+
+.closeButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: 1px solid var(--Color-Brand-Primary, #5189fa);
+ background: var(--Background-Primary, #fff);
+ color: var(--Color-Brand-Primary, #5189fa);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.submitButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border: none;
+ border-radius: 12px;
+ background: var(--Color-Brand-Primary, #5189fa);
+ color: var(--Text-Inverse, #fff);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+@media (max-width: 480px) {
+ section > .modalContent {
+ border-radius: 12px 12px 0 0;
+ }
+}
diff --git a/src/components/Modal/domain/components/ChangePassword/ChangePassword.tsx b/src/components/Modal/domain/components/ChangePassword/ChangePassword.tsx
new file mode 100644
index 0000000..8aacd0b
--- /dev/null
+++ b/src/components/Modal/domain/components/ChangePassword/ChangePassword.tsx
@@ -0,0 +1,117 @@
+'use client';
+
+import { useId } from 'react';
+
+import Modal from '../../../Modal';
+import BaseButton from '@/components/Button/base/BaseButton';
+import { Input } from '@/components/input';
+import styles from './ChangePassword.module.css';
+import { CONFIRM_PASSWORD_NAME, NEW_PASSWORD_NAME, TITLE_ID } from './ChangePassword.constants';
+import type { ChangePasswordProps } from './ChangePassword.types';
+import {
+ createSubmitHandler,
+ resolveChangePasswordText,
+ resolveCloseOptions,
+ resolvePasswordInputIds,
+} from './ChangePassword.utils';
+export type { ChangePasswordProps } from './ChangePassword.types';
+
+/**
+ * @param props.isOpen 모달 표시 여부를 boolean으로 전달합니다.
+ * @param props.onClose 모달을 닫을 때 실행할 함수를 전달합니다.
+ * @param props.onSubmit 비밀번호 변경 제출 시 실행할 함수를 전달합니다.
+ * @param props.text 제목과 버튼 문구와 라벨 같은 텍스트 옵션을 객체로 전달합니다.
+ * @param props.input 비밀번호 입력창들에 적용할 옵션을 객체로 전달합니다.
+ * @param props.closeOptions 오버레이 클릭과 Escape 닫힘 옵션을 객체로 전달합니다.
+ */
+export default function ChangePassword({
+ isOpen,
+ onClose,
+ onSubmit,
+ text,
+ input,
+ closeOptions,
+}: ChangePasswordProps) {
+ const {
+ title,
+ newPasswordLabel,
+ confirmPasswordLabel,
+ newPasswordPlaceholder,
+ confirmPasswordPlaceholder,
+ closeLabel,
+ submitLabel,
+ } = resolveChangePasswordText(text);
+ const { closeOnOverlayClick, closeOnEscape } = resolveCloseOptions(closeOptions);
+
+ const generatedNewPasswordId = useId();
+ const generatedConfirmPasswordId = useId();
+ const { newPasswordId, confirmPasswordId } = resolvePasswordInputIds(
+ input,
+ generatedNewPasswordId,
+ generatedConfirmPasswordId,
+ );
+ const handleSubmit = createSubmitHandler(onSubmit);
+
+ return (
+
+
+
+ {title}
+
+
+
+
+
+ );
+}
diff --git a/src/components/Modal/domain/components/ChangePassword/ChangePassword.types.ts b/src/components/Modal/domain/components/ChangePassword/ChangePassword.types.ts
new file mode 100644
index 0000000..1ef44b1
--- /dev/null
+++ b/src/components/Modal/domain/components/ChangePassword/ChangePassword.types.ts
@@ -0,0 +1,28 @@
+import type { InputProps } from '@/components/input/types/types';
+import type { BaseDomainModalProps } from '../../types/types';
+
+export type PasswordInputFieldProps = Omit<
+ InputProps,
+ 'className' | 'type' | 'name' | 'autoComplete' | 'placeholder'
+>;
+
+export interface ChangePasswordTextOptions {
+ title?: string;
+ newPasswordLabel?: string;
+ confirmPasswordLabel?: string;
+ newPasswordPlaceholder?: string;
+ confirmPasswordPlaceholder?: string;
+ closeLabel?: string;
+ submitLabel?: string;
+}
+
+export interface ChangePasswordInputOptions {
+ newPassword?: PasswordInputFieldProps;
+ confirmPassword?: PasswordInputFieldProps;
+}
+
+export interface ChangePasswordProps extends BaseDomainModalProps {
+ onSubmit: () => void;
+ text?: ChangePasswordTextOptions;
+ input?: ChangePasswordInputOptions;
+}
diff --git a/src/components/Modal/domain/components/ChangePassword/ChangePassword.utils.ts b/src/components/Modal/domain/components/ChangePassword/ChangePassword.utils.ts
new file mode 100644
index 0000000..e39d516
--- /dev/null
+++ b/src/components/Modal/domain/components/ChangePassword/ChangePassword.utils.ts
@@ -0,0 +1,50 @@
+import type { FormEvent } from 'react';
+import type { DomainModalCloseOptions } from '../../types/types';
+import type { ChangePasswordInputOptions, ChangePasswordTextOptions } from './ChangePassword.types';
+import {
+ DEFAULT_CLOSE_LABEL,
+ DEFAULT_CONFIRM_PASSWORD_LABEL,
+ DEFAULT_CONFIRM_PASSWORD_PLACEHOLDER,
+ DEFAULT_NEW_PASSWORD_LABEL,
+ DEFAULT_NEW_PASSWORD_PLACEHOLDER,
+ DEFAULT_SUBMIT_LABEL,
+ DEFAULT_TITLE,
+} from './ChangePassword.constants';
+
+export function resolveChangePasswordText(text?: ChangePasswordTextOptions) {
+ return {
+ title: text?.title ?? DEFAULT_TITLE,
+ newPasswordLabel: text?.newPasswordLabel ?? DEFAULT_NEW_PASSWORD_LABEL,
+ confirmPasswordLabel: text?.confirmPasswordLabel ?? DEFAULT_CONFIRM_PASSWORD_LABEL,
+ newPasswordPlaceholder: text?.newPasswordPlaceholder ?? DEFAULT_NEW_PASSWORD_PLACEHOLDER,
+ confirmPasswordPlaceholder:
+ text?.confirmPasswordPlaceholder ?? DEFAULT_CONFIRM_PASSWORD_PLACEHOLDER,
+ closeLabel: text?.closeLabel ?? DEFAULT_CLOSE_LABEL,
+ submitLabel: text?.submitLabel ?? DEFAULT_SUBMIT_LABEL,
+ };
+}
+
+export function resolveCloseOptions(closeOptions?: DomainModalCloseOptions) {
+ return {
+ closeOnOverlayClick: closeOptions?.overlayClick ?? true,
+ closeOnEscape: closeOptions?.escape ?? true,
+ };
+}
+
+export function resolvePasswordInputIds(
+ input: ChangePasswordInputOptions | undefined,
+ fallbackNewPasswordId: string,
+ fallbackConfirmPasswordId: string,
+) {
+ return {
+ newPasswordId: input?.newPassword?.id ?? fallbackNewPasswordId,
+ confirmPasswordId: input?.confirmPassword?.id ?? fallbackConfirmPasswordId,
+ };
+}
+
+export function createSubmitHandler(onSubmit: () => void) {
+ return (e: FormEvent) => {
+ e.preventDefault();
+ onSubmit();
+ };
+}
diff --git a/src/components/Modal/domain/components/LogoutModal/LogoutModal.constants.ts b/src/components/Modal/domain/components/LogoutModal/LogoutModal.constants.ts
new file mode 100644
index 0000000..4cc086c
--- /dev/null
+++ b/src/components/Modal/domain/components/LogoutModal/LogoutModal.constants.ts
@@ -0,0 +1,4 @@
+export const TITLE_ID = 'logout-modal-title';
+export const DEFAULT_TITLE = '로그아웃 하시겠어요?';
+export const DEFAULT_CLOSE_LABEL = '닫기';
+export const DEFAULT_CONFIRM_LABEL = '로그아웃';
diff --git a/src/components/Modal/domain/components/LogoutModal/LogoutModal.module.css b/src/components/Modal/domain/components/LogoutModal/LogoutModal.module.css
new file mode 100644
index 0000000..63b0d3c
--- /dev/null
+++ b/src/components/Modal/domain/components/LogoutModal/LogoutModal.module.css
@@ -0,0 +1,107 @@
+section > .modalContent {
+ width: 384px;
+ height: 171px;
+ box-sizing: border-box;
+ display: inline-flex;
+ padding: 16px 16px 32px;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ border-radius: 24px;
+ background: var(--Background-Primary, #fff);
+}
+
+.container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+ margin-top: 8px;
+ box-sizing: border-box;
+}
+
+.title {
+ margin: 0;
+ color: var(--Text-Primary, #1e293b);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 19px;
+ margin-top: 12px;
+}
+
+.actions {
+ display: flex;
+ width: 280px;
+ gap: 8px;
+ margin-top: auto;
+}
+
+.closeButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: 1px solid var(--Border-Secondary, #cbd5e1);
+ background: var(--Background-Primary, #fff);
+ color: var(--Text-Default, #64748b);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.actions .closeButton,
+.actions .closeButton:hover:not(:disabled),
+.actions .closeButton:active:not(:disabled) {
+ border: 1px solid var(--Border-Secondary, #cbd5e1);
+ background: var(--Background-Primary, #fff);
+ color: var(--Text-Default, #64748b);
+}
+
+.confirmButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border: none;
+ border-radius: 12px;
+ background: var(--color-status-danger, #fc4b4b);
+ color: var(--Text-inverse, #fff);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.confirmButton:hover {
+ background: var(--color-status-danger-hover, #e53e3e);
+}
+
+.confirmButton:active {
+ background: var(--color-status-danger-pressed, #c53030);
+}
+
+@media (max-width: 480px) {
+ section > .modalContent {
+ width: 100%;
+ max-width: 375px;
+ border-radius: 24px 24px 0 0;
+ background: var(--Background-Primary, #fff);
+ }
+}
diff --git a/src/components/Modal/domain/components/LogoutModal/LogoutModal.tsx b/src/components/Modal/domain/components/LogoutModal/LogoutModal.tsx
new file mode 100644
index 0000000..59fa57e
--- /dev/null
+++ b/src/components/Modal/domain/components/LogoutModal/LogoutModal.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import Modal from '../../../Modal';
+import BaseButton from '@/components/Button/base/BaseButton';
+import styles from './LogoutModal.module.css';
+import {
+ DEFAULT_CLOSE_LABEL,
+ DEFAULT_CONFIRM_LABEL,
+ DEFAULT_TITLE,
+ TITLE_ID,
+} from './LogoutModal.constants';
+import type { LogoutModalProps } from './LogoutModal.types';
+export type { LogoutModalProps } from './LogoutModal.types';
+
+/**
+ * @param props.isOpen 모달 표시 여부를 boolean으로 전달합니다.
+ * @param props.onClose 모달을 닫을 때 실행할 함수를 전달합니다.
+ * @param props.onConfirm 로그아웃 버튼 클릭 시 실행할 함수를 전달합니다.
+ * @param props.text 모달 제목과 버튼 문구 같은 텍스트 옵션을 객체로 전달합니다.
+ * @param props.closeOptions 오버레이 클릭과 Escape 닫힘 옵션을 객체로 전달합니다.
+ */
+export default function LogoutModal({
+ isOpen,
+ onClose,
+ onConfirm,
+ text,
+ closeOptions,
+}: LogoutModalProps) {
+ const title = text?.title ?? DEFAULT_TITLE;
+ const closeLabel = text?.closeLabel ?? DEFAULT_CLOSE_LABEL;
+ const confirmLabel = text?.confirmLabel ?? DEFAULT_CONFIRM_LABEL;
+ const closeOnOverlayClick = closeOptions?.overlayClick ?? true;
+ const closeOnEscape = closeOptions?.escape ?? true;
+
+ return (
+
+
+
+ {title}
+
+
+
+
+
+ );
+}
diff --git a/src/components/Modal/domain/components/LogoutModal/LogoutModal.types.ts b/src/components/Modal/domain/components/LogoutModal/LogoutModal.types.ts
new file mode 100644
index 0000000..f87adca
--- /dev/null
+++ b/src/components/Modal/domain/components/LogoutModal/LogoutModal.types.ts
@@ -0,0 +1,12 @@
+import type { BaseDomainModalProps } from '../../types/types';
+
+export interface LogoutModalTextOptions {
+ title?: string;
+ closeLabel?: string;
+ confirmLabel?: string;
+}
+
+export interface LogoutModalProps extends BaseDomainModalProps {
+ onConfirm: () => void;
+ text?: LogoutModalTextOptions;
+}
diff --git a/src/components/Modal/domain/components/MemberInvite/MemberInvite.constants.ts b/src/components/Modal/domain/components/MemberInvite/MemberInvite.constants.ts
new file mode 100644
index 0000000..ab0e5b5
--- /dev/null
+++ b/src/components/Modal/domain/components/MemberInvite/MemberInvite.constants.ts
@@ -0,0 +1,6 @@
+export const TITLE_ID = 'member-invite-title';
+export const DESCRIPTION_ID = 'member-invite-description';
+export const CLOSE_BUTTON_ARIA_LABEL = '닫기';
+export const DEFAULT_TITLE = '멤버 초대';
+export const DEFAULT_DESCRIPTION = '그룹에 참여할 수 있는 링크를 복사합니다.';
+export const DEFAULT_COPY_LABEL = '링크 복사하기';
diff --git a/src/components/Modal/domain/components/MemberInvite/MemberInvite.module.css b/src/components/Modal/domain/components/MemberInvite/MemberInvite.module.css
new file mode 100644
index 0000000..412ffc7
--- /dev/null
+++ b/src/components/Modal/domain/components/MemberInvite/MemberInvite.module.css
@@ -0,0 +1,98 @@
+@import '@shared/styles/color.css';
+
+.container {
+ position: relative;
+ width: 384px;
+ height: 211px;
+ padding: 40px 24px 32px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ text-align: center;
+}
+
+section > .modalContent {
+ border-radius: 24px;
+}
+
+.closeButton {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.container .closeButton,
+.container .closeButton:hover:not(:disabled),
+.container .closeButton:active:not(:disabled) {
+ border: none;
+ background: transparent;
+ color: inherit;
+}
+
+.title {
+ margin: 8px 0 0;
+ color: var(--Text-Primary, #1e293b);
+ font-family: Pretendard, sans-serif;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 19px;
+}
+
+.description {
+ margin: 4px 0 16px;
+ color: var(--Text-Secondary, #334155);
+ font-family: Pretendard, sans-serif;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 17px;
+}
+
+.copyButton {
+ display: flex;
+ width: 280px;
+ height: 48px;
+ padding: 14px 24px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border-radius: 16px;
+ border: none;
+ background: var(--color-brand-primary);
+ color: var(--Text-inverse, #fff);
+ font-family: Pretendard, sans-serif;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.copyButton:disabled {
+ background: var(--color-interaction-inactive);
+ cursor: not-allowed;
+}
+
+@media (max-width: 480px) {
+ section > .modalContent {
+ border-radius: 24px 24px 0 0;
+ }
+
+ .container {
+ width: 100%;
+ max-width: 375px;
+ height: 195px;
+ flex-shrink: 0;
+ border-radius: 24px 24px 0 0;
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+ }
+}
diff --git a/src/components/Modal/domain/components/MemberInvite/MemberInvite.tsx b/src/components/Modal/domain/components/MemberInvite/MemberInvite.tsx
new file mode 100644
index 0000000..f50f2d2
--- /dev/null
+++ b/src/components/Modal/domain/components/MemberInvite/MemberInvite.tsx
@@ -0,0 +1,77 @@
+'use client';
+
+import Image from 'next/image';
+import Modal from '../../../Modal';
+import styles from './MemberInvite.module.css';
+import BaseButton from '@/components/Button/base/BaseButton';
+import xMarkBig from '@/assets/icons/xMark/xMarkBig.svg';
+import {
+ CLOSE_BUTTON_ARIA_LABEL,
+ DEFAULT_COPY_LABEL,
+ DEFAULT_DESCRIPTION,
+ DEFAULT_TITLE,
+ DESCRIPTION_ID,
+ TITLE_ID,
+} from './MemberInvite.constants';
+import type { MemberInviteProps } from './MemberInvite.types';
+export type { MemberInviteProps } from './MemberInvite.types';
+
+/**
+ * @param props.isOpen 모달 표시 여부를 boolean으로 전달합니다.
+ * @param props.onClose 모달을 닫을 때 실행할 함수를 전달합니다.
+ * @param props.invite 초대 링크와 복사 핸들러를 객체로 전달합니다.
+ * @param props.text 모달 제목과 설명과 버튼 문구를 객체로 전달합니다.
+ * @param props.closeOptions 오버레이 클릭과 Escape 닫힘 옵션을 객체로 전달합니다.
+ */
+export default function MemberInvite({
+ isOpen,
+ onClose,
+ invite,
+ text,
+ closeOptions,
+}: MemberInviteProps) {
+ const title = text?.title ?? DEFAULT_TITLE;
+ const description = text?.description ?? DEFAULT_DESCRIPTION;
+ const copyButtonLabel = text?.copyButtonLabel ?? DEFAULT_COPY_LABEL;
+ const closeOnOverlayClick = closeOptions?.overlayClick ?? true;
+ const closeOnEscape = closeOptions?.escape ?? true;
+
+ const handleCopy = () => invite.onCopyLink?.(invite.link);
+
+ return (
+
+
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+ {copyButtonLabel}
+
+
+
+ );
+}
diff --git a/src/components/Modal/domain/components/MemberInvite/MemberInvite.types.ts b/src/components/Modal/domain/components/MemberInvite/MemberInvite.types.ts
new file mode 100644
index 0000000..7f487b8
--- /dev/null
+++ b/src/components/Modal/domain/components/MemberInvite/MemberInvite.types.ts
@@ -0,0 +1,17 @@
+import type { BaseDomainModalProps } from '../../types/types';
+
+export interface MemberInviteTextOptions {
+ title?: string;
+ description?: string;
+ copyButtonLabel?: string;
+}
+
+export interface MemberInviteInviteOptions {
+ link: string;
+ onCopyLink?: (link: string) => void;
+}
+
+export interface MemberInviteProps extends BaseDomainModalProps {
+ invite: MemberInviteInviteOptions;
+ text?: MemberInviteTextOptions;
+}
diff --git a/src/components/Modal/domain/components/ProfileModal/ProfileModal.constants.ts b/src/components/Modal/domain/components/ProfileModal/ProfileModal.constants.ts
new file mode 100644
index 0000000..0d10a46
--- /dev/null
+++ b/src/components/Modal/domain/components/ProfileModal/ProfileModal.constants.ts
@@ -0,0 +1,5 @@
+export const TITLE_ID = 'profile-modal-title';
+export const EMAIL_ID = 'profile-modal-email';
+export const CLOSE_BUTTON_ARIA_LABEL = '닫기';
+export const DEFAULT_COPY_LABEL = '이메일 복사하기';
+export const DEFAULT_PROFILE_ALT = '프로필 이미지';
diff --git a/src/components/Modal/domain/components/ProfileModal/ProfileModal.module.css b/src/components/Modal/domain/components/ProfileModal/ProfileModal.module.css
new file mode 100644
index 0000000..4378c62
--- /dev/null
+++ b/src/components/Modal/domain/components/ProfileModal/ProfileModal.module.css
@@ -0,0 +1,130 @@
+section > .modalContent {
+ width: 344px;
+ height: 243px;
+ display: inline-flex;
+ padding: 16px 32px 32px;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ border-radius: 24px;
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+}
+
+.container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+}
+
+.content {
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ transform: translateY(12px);
+}
+
+.closeButton {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.container .closeButton,
+.container .closeButton:hover:not(:disabled),
+.container .closeButton:active:not(:disabled) {
+ border: none;
+ background: transparent;
+ color: inherit;
+}
+
+.profileImage {
+ display: flex;
+ width: 40px;
+ height: 40px;
+ justify-content: center;
+ align-items: center;
+ aspect-ratio: 1 / 1;
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.profileImage :global(img) {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.title {
+ margin: 0;
+ color: var(--Text-Primary, #1e293b);
+ font-family: Pretendard;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 17px;
+}
+
+.email {
+ margin: 0;
+ color: var(--Text-Secondary, #334155);
+ font-family: Pretendard;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 14px;
+}
+
+.copyButton {
+ display: flex;
+ width: 280px;
+ height: 48px;
+ margin-top: 16px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border: none;
+ border-radius: 12px;
+ background: var(--Color-Brand-Primary, #5189fa);
+ color: var(--Text-inverse, #fff);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.copyButton:hover {
+ background: var(--color-interaction-hover, #416ec8);
+}
+
+.copyButton:active {
+ background: var(--color-interaction-pressed, #3b63b5);
+}
+
+@media (max-width: 480px) {
+ section > .modalContent {
+ width: 100%;
+ max-width: 375px;
+ border-radius: 24px 24px 0 0;
+ background: var(--Background-Primary, #fff);
+ }
+}
diff --git a/src/components/Modal/domain/components/ProfileModal/ProfileModal.tsx b/src/components/Modal/domain/components/ProfileModal/ProfileModal.tsx
new file mode 100644
index 0000000..1cca58a
--- /dev/null
+++ b/src/components/Modal/domain/components/ProfileModal/ProfileModal.tsx
@@ -0,0 +1,88 @@
+'use client';
+
+import Image from 'next/image';
+
+import Modal from '../../../Modal';
+import styles from './ProfileModal.module.css';
+import BaseButton from '@/components/Button/base/BaseButton';
+import profileFallback from '@/assets/icons/img/img.svg';
+import xMarkBig from '@/assets/icons/xMark/xMarkBig.svg';
+import {
+ CLOSE_BUTTON_ARIA_LABEL,
+ DEFAULT_COPY_LABEL,
+ DEFAULT_PROFILE_ALT,
+ EMAIL_ID,
+ TITLE_ID,
+} from './ProfileModal.constants';
+import type { ProfileModalProps } from './ProfileModal.types';
+export type { ProfileModalProps } from './ProfileModal.types';
+
+/**
+ * @param props.isOpen 모달 표시 여부를 boolean으로 전달합니다.
+ * @param props.onClose 모달을 닫을 때 실행할 함수를 전달합니다.
+ * @param props.onCopyEmail 이메일 복사 버튼 클릭 시 실행할 함수를 전달합니다.
+ * @param props.profile 프로필 정보와 표시 텍스트를 객체로 전달합니다.
+ * @param props.closeOptions 오버레이 클릭과 Escape 닫힘 옵션을 객체로 전달합니다.
+ */
+export default function ProfileModal({
+ isOpen,
+ onClose,
+ onCopyEmail,
+ profile,
+ closeOptions,
+}: ProfileModalProps) {
+ const profileImageAlt = profile.imageAlt ?? DEFAULT_PROFILE_ALT;
+ const copyButtonLabel = profile.copyButtonLabel ?? DEFAULT_COPY_LABEL;
+ const closeOnOverlayClick = closeOptions?.overlayClick ?? true;
+ const closeOnEscape = closeOptions?.escape ?? true;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {profile.title}
+
+
+ {profile.email}
+
+
+
+ {copyButtonLabel}
+
+
+
+
+ );
+}
diff --git a/src/components/Modal/domain/components/ProfileModal/ProfileModal.types.ts b/src/components/Modal/domain/components/ProfileModal/ProfileModal.types.ts
new file mode 100644
index 0000000..33ebe1a
--- /dev/null
+++ b/src/components/Modal/domain/components/ProfileModal/ProfileModal.types.ts
@@ -0,0 +1,15 @@
+import type { ImageProps } from 'next/image';
+import type { BaseDomainModalProps } from '../../types/types';
+
+export interface ProfileModalProfileOptions {
+ title: string;
+ email: string;
+ imageSrc?: ImageProps['src'];
+ imageAlt?: string;
+ copyButtonLabel?: string;
+}
+
+export interface ProfileModalProps extends BaseDomainModalProps {
+ onCopyEmail: () => void;
+ profile: ProfileModalProfileOptions;
+}
diff --git a/src/components/Modal/domain/components/ResetPassword/ResetPassword.constants.ts b/src/components/Modal/domain/components/ResetPassword/ResetPassword.constants.ts
new file mode 100644
index 0000000..0938364
--- /dev/null
+++ b/src/components/Modal/domain/components/ResetPassword/ResetPassword.constants.ts
@@ -0,0 +1,7 @@
+export const TITLE_ID = 'reset-password-title';
+export const DESCRIPTION_ID = 'reset-password-description';
+export const DEFAULT_TITLE = '비밀번호 재설정';
+export const DEFAULT_DESCRIPTION = '비밀번호 재설정 링크를 보내드립니다.';
+export const DEFAULT_CLOSE_LABEL = '닫기';
+export const DEFAULT_SUBMIT_LABEL = '링크 보내기';
+export const DEFAULT_EMAIL_PLACEHOLDER = '이메일을 입력하세요';
diff --git a/src/components/Modal/domain/components/ResetPassword/ResetPassword.module.css b/src/components/Modal/domain/components/ResetPassword/ResetPassword.module.css
new file mode 100644
index 0000000..15b2064
--- /dev/null
+++ b/src/components/Modal/domain/components/ResetPassword/ResetPassword.module.css
@@ -0,0 +1,123 @@
+section > .modalContent {
+ display: inline-flex;
+ padding: 16px 16px 32px 16px;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
+ border-radius: 24px;
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+ width: 384px;
+ height: 260px;
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ margin-top: 24px;
+}
+
+.header {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ align-items: center;
+}
+
+.title {
+ margin: 0;
+ color: var(--Text-Primary, #1e293b);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 19px;
+ text-align: center;
+}
+
+.description {
+ margin: 0;
+ color: var(--Text-Default, #64748b);
+ font-family: Pretendard;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 17px;
+ text-align: center;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ flex: 1;
+}
+
+.form .input {
+ display: flex;
+ width: 280px;
+ height: 48px;
+ padding: 16px;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: 1px solid var(--Border-Primary, #e2e8f0);
+ background: var(--Background-Primary, #fff);
+ box-sizing: border-box;
+ margin: 0 auto;
+}
+
+.actions {
+ display: flex;
+ width: 280px;
+ gap: 8px;
+ margin: auto auto 0;
+}
+
+.closeButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: 1px solid var(--Color-Brand-Primary, #5189fa);
+ background: transparent;
+ color: var(--Color-Brand-Primary, #5189fa);
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.sendButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: none;
+ background: var(--Color-Brand-Primary, #5189fa);
+ color: #fff;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+@media (max-width: 480px) {
+ section > .modalContent {
+ border-radius: 24px 24px 0 0;
+ }
+}
diff --git a/src/components/Modal/domain/components/ResetPassword/ResetPassword.tsx b/src/components/Modal/domain/components/ResetPassword/ResetPassword.tsx
new file mode 100644
index 0000000..0e3a2d2
--- /dev/null
+++ b/src/components/Modal/domain/components/ResetPassword/ResetPassword.tsx
@@ -0,0 +1,97 @@
+'use client';
+
+import type { FormEvent } from 'react';
+
+import Modal from '../../../Modal';
+import BaseButton from '@/components/Button/base/BaseButton';
+import { Input } from '@/components/input';
+import styles from './ResetPassword.module.css';
+import {
+ DEFAULT_CLOSE_LABEL,
+ DEFAULT_DESCRIPTION,
+ DEFAULT_EMAIL_PLACEHOLDER,
+ DEFAULT_SUBMIT_LABEL,
+ DEFAULT_TITLE,
+ DESCRIPTION_ID,
+ TITLE_ID,
+} from './ResetPassword.constants';
+import type { ResetPasswordProps } from './ResetPassword.types';
+export type { ResetPasswordProps } from './ResetPassword.types';
+
+/**
+ * @param props.isOpen 모달 표시 여부를 boolean으로 전달합니다.
+ * @param props.onClose 모달을 닫을 때 실행할 함수를 전달합니다.
+ * @param props.onSubmit 링크 보내기 제출 시 실행할 함수를 전달합니다.
+ * @param props.text 제목과 버튼 문구와 안내 문구 같은 텍스트 옵션을 객체로 전달합니다.
+ * @param props.input 이메일 입력창에 적용할 옵션을 객체로 전달합니다.
+ * @param props.closeOptions 오버레이 클릭과 Escape 닫힘 옵션을 객체로 전달합니다.
+ */
+export default function ResetPassword({
+ isOpen,
+ onClose,
+ onSubmit,
+ text,
+ input,
+ closeOptions,
+}: ResetPasswordProps) {
+ const title = text?.title ?? DEFAULT_TITLE;
+ const description = text?.description ?? DEFAULT_DESCRIPTION;
+ const closeLabel = text?.closeLabel ?? DEFAULT_CLOSE_LABEL;
+ const submitLabel = text?.submitLabel ?? DEFAULT_SUBMIT_LABEL;
+ const emailPlaceholder = text?.emailPlaceholder ?? DEFAULT_EMAIL_PLACEHOLDER;
+ const closeOnOverlayClick = closeOptions?.overlayClick ?? true;
+ const closeOnEscape = closeOptions?.escape ?? true;
+
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ onSubmit();
+ };
+
+ return (
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Modal/domain/components/ResetPassword/ResetPassword.types.ts b/src/components/Modal/domain/components/ResetPassword/ResetPassword.types.ts
new file mode 100644
index 0000000..2f7f0a3
--- /dev/null
+++ b/src/components/Modal/domain/components/ResetPassword/ResetPassword.types.ts
@@ -0,0 +1,25 @@
+import type { InputProps } from '@/components/input/types/types';
+import type { BaseDomainModalProps } from '../../types/types';
+
+export type EmailInputFieldProps = Omit<
+ InputProps,
+ 'className' | 'type' | 'name' | 'autoComplete' | 'placeholder'
+>;
+
+export interface ResetPasswordTextOptions {
+ title?: string;
+ description?: string;
+ closeLabel?: string;
+ submitLabel?: string;
+ emailPlaceholder?: string;
+}
+
+export interface ResetPasswordInputOptions {
+ email?: EmailInputFieldProps;
+}
+
+export interface ResetPasswordProps extends BaseDomainModalProps {
+ onSubmit: () => void;
+ text?: ResetPasswordTextOptions;
+ input?: ResetPasswordInputOptions;
+}
diff --git a/src/components/Modal/domain/components/WarningModal/WarningModal.constants.ts b/src/components/Modal/domain/components/WarningModal/WarningModal.constants.ts
new file mode 100644
index 0000000..d7d4e56
--- /dev/null
+++ b/src/components/Modal/domain/components/WarningModal/WarningModal.constants.ts
@@ -0,0 +1,7 @@
+export const TITLE_ID = 'warning-modal-title';
+export const DESCRIPTION_ID = 'warning-modal-description';
+export const DEFAULT_TITLE = '회원 탈퇴를 진행하시겠어요?';
+export const DEFAULT_DESCRIPTION =
+ '그룹장으로 있는 그룹은 자동으로 삭제되고,\n모든 그룹에서 나가집니다.';
+export const DEFAULT_CLOSE_LABEL = '닫기';
+export const DEFAULT_CONFIRM_LABEL = '회원 탈퇴';
diff --git a/src/components/Modal/domain/components/WarningModal/WarningModal.module.css b/src/components/Modal/domain/components/WarningModal/WarningModal.module.css
new file mode 100644
index 0000000..cd3718a
--- /dev/null
+++ b/src/components/Modal/domain/components/WarningModal/WarningModal.module.css
@@ -0,0 +1,128 @@
+section > .modalContent {
+ box-sizing: border-box;
+ display: inline-flex;
+ width: 384px;
+ padding: 16px 16px 32px;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ border-radius: 24px;
+ background: var(--Background-Primary, #fff);
+}
+
+.container {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ margin-top: 20px;
+}
+
+.header {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 10px;
+}
+
+.icon {
+ flex-shrink: 0;
+}
+
+.title {
+ margin: 0;
+ color: var(--Text-Primary, #1e293b);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 19px;
+}
+
+.description {
+ margin: 0;
+ width: 280px;
+ color: var(--Text-Secondary, #334155);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 17px;
+ white-space: pre-line;
+}
+
+.actions {
+ display: flex;
+ width: 280px;
+ gap: 8px;
+ margin-top: 16px;
+}
+
+.closeButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border-radius: 12px;
+ border: 1px solid var(--Border-Secondary, #cbd5e1);
+ background: var(--Background-Primary, #fff);
+ color: var(--Text-Default, #64748b);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.actions .closeButton,
+.actions .closeButton:hover:not(:disabled),
+.actions .closeButton:active:not(:disabled) {
+ border: 1px solid var(--Border-Secondary, #cbd5e1);
+ background: var(--Background-Primary, #fff);
+ color: var(--Text-Default, #64748b);
+}
+
+.confirmButton {
+ display: flex;
+ width: 136px;
+ height: 48px;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ border: none;
+ border-radius: 12px;
+ background: var(--color-status-danger, #fc4b4b);
+ color: var(--Text-inverse, #fff);
+ text-align: center;
+ font-family: Pretendard;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 19px;
+ cursor: pointer;
+}
+
+.confirmButton:hover {
+ background: var(--color-status-danger-hover, #e53e3e);
+}
+
+.confirmButton:active {
+ background: var(--color-status-danger-pressed, #c53030);
+}
+
+@media (max-width: 480px) {
+ section > .modalContent {
+ width: 100%;
+ max-width: 375px;
+ border-radius: 24px 24px 0 0;
+ background: var(--Background-Primary, #fff);
+ }
+}
diff --git a/src/components/Modal/domain/components/WarningModal/WarningModal.tsx b/src/components/Modal/domain/components/WarningModal/WarningModal.tsx
new file mode 100644
index 0000000..89aaf44
--- /dev/null
+++ b/src/components/Modal/domain/components/WarningModal/WarningModal.tsx
@@ -0,0 +1,91 @@
+'use client';
+
+import Image from 'next/image';
+
+import Modal from '../../../Modal';
+import BaseButton from '@/components/Button/base/BaseButton';
+import styles from './WarningModal.module.css';
+import alertSmall from '@/assets/icons/alert/alertSmall.svg';
+import {
+ DEFAULT_CLOSE_LABEL,
+ DEFAULT_CONFIRM_LABEL,
+ DEFAULT_DESCRIPTION,
+ DEFAULT_TITLE,
+ DESCRIPTION_ID,
+ TITLE_ID,
+} from './WarningModal.constants';
+import type { WarningModalProps } from './WarningModal.types';
+export type { WarningModalProps } from './WarningModal.types';
+
+/**
+ * @param props.isOpen 모달 표시 여부를 boolean으로 전달합니다.
+ * @param props.onClose 모달을 닫을 때 실행할 함수를 전달합니다.
+ * @param props.onConfirm 회원 탈퇴 확인 버튼 클릭 시 실행할 함수를 전달합니다.
+ * @param props.text 경고 모달 제목과 설명과 버튼 문구를 객체로 전달합니다.
+ * @param props.closeOptions 오버레이 클릭과 Escape 닫힘 옵션을 객체로 전달합니다.
+ */
+export default function WarningModal({
+ isOpen,
+ onClose,
+ onConfirm,
+ text,
+ closeOptions,
+}: WarningModalProps) {
+ const title = text?.title ?? DEFAULT_TITLE;
+ const description = text?.description ?? DEFAULT_DESCRIPTION;
+ const closeLabel = text?.closeLabel ?? DEFAULT_CLOSE_LABEL;
+ const confirmLabel = text?.confirmLabel ?? DEFAULT_CONFIRM_LABEL;
+ const closeOnOverlayClick = closeOptions?.overlayClick ?? true;
+ const closeOnEscape = closeOptions?.escape ?? true;
+
+ return (
+
+
+
+
+
+ {description}
+
+
+
+
+
+ );
+}
diff --git a/src/components/Modal/domain/components/WarningModal/WarningModal.types.ts b/src/components/Modal/domain/components/WarningModal/WarningModal.types.ts
new file mode 100644
index 0000000..4f9343e
--- /dev/null
+++ b/src/components/Modal/domain/components/WarningModal/WarningModal.types.ts
@@ -0,0 +1,13 @@
+import type { BaseDomainModalProps } from '../../types/types';
+
+export interface WarningModalTextOptions {
+ title?: string;
+ description?: string;
+ closeLabel?: string;
+ confirmLabel?: string;
+}
+
+export interface WarningModalProps extends BaseDomainModalProps {
+ onConfirm: () => void;
+ text?: WarningModalTextOptions;
+}
diff --git a/src/components/Modal/domain/types/types.ts b/src/components/Modal/domain/types/types.ts
new file mode 100644
index 0000000..c79b7c8
--- /dev/null
+++ b/src/components/Modal/domain/types/types.ts
@@ -0,0 +1,10 @@
+export interface DomainModalCloseOptions {
+ overlayClick?: boolean;
+ escape?: boolean;
+}
+
+export interface BaseDomainModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ closeOptions?: DomainModalCloseOptions;
+}
diff --git a/src/components/Modal/types/types.ts b/src/components/Modal/types/types.ts
index 6c53d07..98b4c1c 100644
--- a/src/components/Modal/types/types.ts
+++ b/src/components/Modal/types/types.ts
@@ -6,6 +6,7 @@ interface BaseModalProps {
children?: ReactNode;
ariaDescribedby?: string;
className?: string;
+ contentClassName?: string;
closeOnOverlayClick?: boolean;
closeOnEscape?: boolean;
}