Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { RouterProvider } from "react-router-dom";

import Modal from "./components/Modal/Modal";
import { router } from "./Router";

import ToastContainer from "@/components/Toast/ToastContainer";

function App() {
return (
<>
<RouterProvider router={router} />
<ToastContainer />
<Modal />
</>
);
}
Expand Down
70 changes: 70 additions & 0 deletions src/components/Modal/AlertModalLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Button from "../Button";

import { Check, Notice } from "@/assets/icon";
import { useModalStore } from "@/store/useModalStore";

interface ModalButton {
label: string;
style: "primary" | "white";
onClick: () => void;
}

interface Props {
type: "alert" | "message";
iconType?: "check" | "warning" | "none";
message: string;
button: ModalButton;
}

const ICONS = {
check: Check,
warning: Notice,
};

export default function AlertModalLayout({
type,
iconType = "none",
message,
button,
}: Props) {
const Icon = iconType !== "none" ? ICONS[iconType] : null;
const { closeModal } = useModalStore();
return (
<div
className={`
bg-white rounded-lg text-center relative
${
type === "message"
? "w-[20.625rem] h-[13.75rem] md:w-[33.75rem] md:h-[15.625rem]"
: "w-[18.625rem] h-[11.5rem] md:w-[18.625rem] md:h-[11.5rem]"
}
p-6 flex flex-col justify-center items-center
`}
onClick={(e) => e.stopPropagation()}
>
{Icon && <Icon className="w-6 h-6 mb-6 mx-auto" />}{" "}
<p className="text-gray-900 text-base md:text-lg mb-[1rem]">{message}</p>
<div
className={
type === "message"
? "absolute bottom-6 right-6 md:right-6 md:bottom-6 mx-auto md:mx-0"
: ""
}
>
<div>
<Button
variant={type === "message" ? "primary" : "white"}
onClick={() => {
button.onClick?.();
closeModal();
}}
textSize="md"
className="w-[5rem] h-[2.375rem] cursor-pointer"
>
{button.label}
</Button>
</div>
</div>
</div>
);
}
49 changes: 49 additions & 0 deletions src/components/Modal/ConfirmModalLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Check, Notice } from "@/assets/icon";

const ICONS = {
check: Check,
warning: Notice,
};

interface ConfirmModalLayoutProps {
iconType?: "check" | "warning" | "none";
message: string;
onClose: () => void;
onConfirm: () => void;
confirmText?: string;
cancelText?: string;
}

export default function ConfirmModalLayout({
iconType = "none",
message,
onClose,
onConfirm,
confirmText = "예",
cancelText = "아니오",
}: ConfirmModalLayoutProps) {
const Icon = iconType !== "none" ? ICONS[iconType] : null;

return (
<div className="w-[18.625rem] md:w-[18.625rem] h-[11.5rem] bg-white rounded-lg p-6 text-center flex flex-col justify-center items-center">
{Icon && <Icon className="w-6 h-6 mb-3" />}
<p className="text-gray-900 text-base md:text-lg font-normal mb-6">
{message}
</p>
<div className="flex justify-end gap-3">
<button
onClick={onClose}
className="w-[5rem] h-[2.375rem] border border-red-500 text-red-500 rounded-md cursor-pointer"
>
{cancelText}
</button>
<button
onClick={onConfirm}
className="w-[5rem] h-[2.375rem] bg-red-500 text-white rounded-md cursor-pointer"
>
{confirmText}
</button>
</div>
</div>
);
}
45 changes: 45 additions & 0 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import AlertModalLayout from "./AlertModalLayout";
import ConfirmModalLayout from "./ConfirmModalLayout";

import { useModalStore } from "@/store/useModalStore";

export default function Modal() {
const { isOpen, options, closeModal } = useModalStore();

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

const handleClose = () => {
options.onClose?.();
closeModal();
};

return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50">
{options.type === "confirm" && (
<ConfirmModalLayout
message={options.message}
onClose={handleClose}
iconType={options.iconType ?? "none"}
onConfirm={() => {
options.onConfirm?.();
closeModal();
}}
confirmText={options.confirmText ?? "예"}
cancelText={options.cancelText ?? "아니오"}
/>
)}
{(options.type === "alert" || options.type === "message") && (
<AlertModalLayout
type={options.type}
message={options.message}
iconType={options.iconType ?? "none"}
button={{
label: "확인",
style: "primary",
onClick: handleClose,
}}
/>
)}
</div>
);
}
Empty file removed src/components/index.ts
Empty file.
34 changes: 34 additions & 0 deletions src/store/useModalStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { create } from "zustand";

export type ModalType = "alert" | "confirm" | "message";

export interface ModalButton {
label: string;
style: "primary" | "white";
onClick: () => void;
}

export interface ModalOptions {
type: "alert" | "confirm" | "message";
message: string;
iconType?: "check" | "warning" | "none";
buttons?: ModalButton[];
onClose?: () => void;
onConfirm?: () => void;
confirmText?: string;
cancelText?: string;
}

interface ModalState {
isOpen: boolean;
options: ModalOptions | null;
openModal: (options: ModalOptions) => void;
closeModal: () => void;
}

export const useModalStore = create<ModalState>((set) => ({
isOpen: false,
options: null,
openModal: (options) => set({ isOpen: true, options }),
closeModal: () => set({ isOpen: false, options: null }),
}));
10 changes: 10 additions & 0 deletions src/types/svg.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
declare module "*.svg?react" {
import * as React from "react";
const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
export default ReactComponent;
}

declare module "*.svg" {
const content: string;
export default content;
}
6 changes: 5 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
],
"compilerOptions": {
"typeRoots": ["./src/types", "./node_modules/@types"]
},
"includes": ["src", "src/types"]
}