toasts;
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user