This commit is contained in:
2025-10-20 17:08:35 +05:00
parent 4f0e5b9898
commit 56ed2221c8
5 changed files with 145 additions and 0 deletions
@@ -0,0 +1,61 @@
import clsx from "clsx";
import React from "react";
import Button from "../ui/Button";
import useToastsStore from "../../store/toastsStore";
export interface ToastLayoutProps {
id: string;
type: "notification" | "warning";
title: string;
message: string;
onDeny: () => void;
onAllow: () => void;
image?: string;
icon?: React.ReactNode;
}
export default function ToastLayout({
id,
type,
title,
message,
onDeny,
onAllow,
}: ToastLayoutProps) {
const { removeToast } = useToastsStore();
function handleDeny() {
onDeny();
removeToast(id);
}
function handleAllow() {
onAllow();
removeToast(id);
}
return (
<div className="flex w-[20.833vw] rounded-[1.944vw] p-[0.556vw] bg-white shadow-[0_4px_40px_0_rgba(15,16,17,0.1)]">
<div className="flex flex-col gap-[0.556vw]">
<div
className={clsx(
"button-m",
type === "notification" ? "text-[#7D7D7D]" : "text-[#FF4517]"
)}
>
{title}
</div>
<div className="text-m">{message}</div>
<div className="flex gap-[0.556vw]">
<Button variant="critical" size="small" onClick={handleDeny}>
Отклонить
</Button>
<Button variant="primary" size="small" onClick={handleAllow}>
Разрешить
</Button>
</div>
</div>
</div>
);
}
@@ -0,0 +1,31 @@
import useToastsStore from "../../store/toastsStore";
import ToastLayout from "./ToastLayout";
import { AnimatePresence, motion } from "motion/react";
export default function ToastsContainer() {
const { toasts } = useToastsStore();
return (
<div className="fixed w-full flex flex-col-reverse justify-center items-center top-[1.111vw] left-1/2 -translate-x-1/2 z-50 gap-[0.278vw] ">
<AnimatePresence mode="popLayout">
{toasts.map((toast) => (
<motion.div
key={toast.id}
layout
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{
duration: 0.5,
type: "spring",
damping: 20,
stiffness: 300,
}}
>
<ToastLayout {...toast} />
</motion.div>
))}
</AnimatePresence>
</div>
);
}
+2
View File
@@ -11,6 +11,7 @@ import ProtectedRoute from "./components/ProtectedRoute";
import PublicRoute from "./components/PublicRoute";
import ModalContainer from "./components/ModalContainer";
import PopupContainer from "./components/PopupContainer";
import ToastsContainer from "./components/toasts/ToastsContainer";
// import NewSessionPage from "./pages/NewSessionPage";
import TestPage from "./pages/TestPage";
import NewSessionPage from "./pages/NewSessionPage";
@@ -55,5 +56,6 @@ createRoot(document.getElementById("root")!).render(
<RouterProvider router={router} />
<ModalContainer />
<PopupContainer />
<ToastsContainer />
</QueryClientProvider>
);
+27
View File
@@ -9,6 +9,8 @@ import useModalStore from "../store/modalStore";
import CogFilledIcon from "../components/icons/CogFilledIcon";
import SessionUsersPanel from "../components/SessionUsersPanel";
import SharePopup from "../components/popups/SharePopup";
import { useEffect } from "react";
import useToastsStore from "../store/toastsStore";
function HomePage() {
const { data: user } = useMe();
@@ -23,6 +25,31 @@ function HomePage() {
const { setPopup } = usePopupStore();
const { setModal } = useModalStore();
// -------------------------------- Toasts test --------------------------------
const { addToast } = useToastsStore();
useEffect(() => {
addToast({
id: crypto.randomUUID(),
type: "warning",
title: "Тестовое предупреждение",
message: "Это тестовое предупреждение",
onDeny: () => {},
onAllow: () => {},
});
const timer = setTimeout(() => {
addToast({
id: crypto.randomUUID(),
type: "notification",
title: "Тестовое уведомление",
message: "Это тестовое уведомление",
onDeny: () => {},
onAllow: () => {},
});
}, 1000);
return () => clearTimeout(timer);
}, []);
return (
<div className="py-8 min-h-screen bg-gray-50">
<div className="px-4 mx-auto max-w-4xl">
+24
View File
@@ -0,0 +1,24 @@
import { create } from "zustand";
import type { ToastLayoutProps } from "../components/toasts/ToastLayout";
interface ToastState {
toasts: ToastLayoutProps[];
addToast: (toast: ToastLayoutProps) => void;
removeToast: (id: ToastLayoutProps["id"]) => void;
clearToasts: () => void;
}
const useToastsStore = create<ToastState>((set) => ({
toasts: [],
addToast: (toast) =>
set((state) => ({
toasts: [...state.toasts, toast],
})),
removeToast: (id) =>
set((state) => ({
toasts: state.toasts.filter((toast) => toast.id !== id),
})),
clearToasts: () => set({ toasts: [] }),
}));
export default useToastsStore;