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
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</head>
<body>
<div id="root"></div>
<div id="toast-root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
8 changes: 7 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { RouterProvider } from "react-router-dom";

import { router } from "./Router";
import ToastContainer from "@/components/Toast/ToastContainer";

function App() {
return <RouterProvider router={router} />;
return (
<>
<RouterProvider router={router} />
<ToastContainer />
</>
);
}
export default App;
19 changes: 19 additions & 0 deletions src/components/Toast/Toast.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/comopnents/Toast/Toast.tsx가 있고
src/components/Toast.tsx 두 가지 파일이 존재해보입니다!
필요하지 않은 하나는 제거 해주세요! 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 넵 수정하겠습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cn } from "@/utils/cn";

interface ToastProps {
label: string;
className?: string;
}

export default function Toast({ label, className }: ToastProps) {
return (
<div
className={cn(
"relative inline-block rounded-md bg-red-30 px-4 py-[10px] text-white body1-regular",
className,
)}
>
{label}
</div>
);
}
23 changes: 23 additions & 0 deletions src/components/Toast/ToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useToast } from "@/hooks/useToast";
import ToastPortal from "@/components/Toast/ToastPortal";
import Toast from "@/components/Toast/Toast";

export default function ToastContainer() {
const { toasts } = useToast();

return (
<ToastPortal>
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center gap-y-2 pointer-events-none">
{toasts.map((toast) => (
<Toast
key={toast.id}
label={toast.label}
className={`transition-opacity duration-500 ${
toast.isVisible ? "opacity-100" : "opacity-0"
}`}
/>
))}
</div>
</ToastPortal>
);
}
21 changes: 21 additions & 0 deletions src/components/Toast/ToastPortal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createPortal } from "react-dom";
import { ReactNode, useEffect, useState } from "react";

interface ToastPortalProps {
children: ReactNode;
}

export default function ToastPortal({ children }: ToastPortalProps) {
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (typeof window === "undefined") return null;

const el = document.getElementById("toast-root");
if (!el || !mounted) return null;

return createPortal(children, el);
}
41 changes: 41 additions & 0 deletions src/hooks/useToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { create } from "zustand";

interface ToastItem {
id: number;
label: string;
isVisible: boolean;
}

interface ToastState {
toasts: ToastItem[];
showToast: (label: string) => Promise<void>;
removeToast: (id: number) => void;
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export const useToast = create<ToastState>((set) => ({
toasts: [],
showToast: async (label) => {
const id = Date.now();
set((state) => ({
toasts: [...state.toasts, { id, label, isVisible: true }],
}));

await delay(1000);
set((state) => ({
toasts: state.toasts.map((toast) =>
toast.id === id ? { ...toast, isVisible: false } : toast,
),
}));

await delay(500);
set((state) => ({
toasts: state.toasts.filter((toast) => toast.id !== id),
}));
},
removeToast: (id) =>
set((state) => ({
toasts: state.toasts.filter((toast) => toast.id !== id),
})),
}));