repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
certificates
stars
latest
clone command
git clone gitlawb://did:key:z6Mkqhmm...XL9c/certificatesgit clone gitlawb://did:key:z6Mkqhmm.../certificates019974a8sync from playground15h ago| #1 | import { createContext, useContext, useState, useCallback, type ReactNode } from "react"; |
| #2 | |
| #3 | interface Toast { |
| #4 | id: number; |
| #5 | message: string; |
| #6 | type: "success" | "error" | "info"; |
| #7 | } |
| #8 | |
| #9 | interface ToastContextType { |
| #10 | toasts: Toast[]; |
| #11 | addToast: (message: string, type?: Toast["type"]) => void; |
| #12 | removeToast: (id: number) => void; |
| #13 | } |
| #14 | |
| #15 | const ToastContext = createContext<ToastContextType | null>(null); |
| #16 | |
| #17 | let nextId = 0; |
| #18 | |
| #19 | export function ToastProvider({ children }: { children: ReactNode }) { |
| #20 | const [toasts, setToasts] = useState<Toast[]>([]); |
| #21 | |
| #22 | const addToast = useCallback((message: string, type: Toast["type"] = "success") => { |
| #23 | const id = nextId++; |
| #24 | setToasts((prev) => [...prev, { id, message, type }]); |
| #25 | setTimeout(() => { |
| #26 | setToasts((prev) => prev.filter((t) => t.id !== id)); |
| #27 | }, 4000); |
| #28 | }, []); |
| #29 | |
| #30 | const removeToast = useCallback((id: number) => { |
| #31 | setToasts((prev) => prev.filter((t) => t.id !== id)); |
| #32 | }, []); |
| #33 | |
| #34 | return ( |
| #35 | <ToastContext.Provider value={{ toasts, addToast, removeToast }}> |
| #36 | {children} |
| #37 | </ToastContext.Provider> |
| #38 | ); |
| #39 | } |
| #40 | |
| #41 | export function useToast() { |
| #42 | const ctx = useContext(ToastContext); |
| #43 | if (!ctx) throw new Error("useToast must be used within ToastProvider"); |
| #44 | return ctx; |
| #45 | } |
| #46 | |
| #47 | export function ToastContainer() { |
| #48 | const { toasts, removeToast } = useToast(); |
| #49 | |
| #50 | return ( |
| #51 | <div className="fixed bottom-4 right-4 z-[9999] flex flex-col gap-2"> |
| #52 | {toasts.map((toast) => ( |
| #53 | <div |
| #54 | key={toast.id} |
| #55 | className={`flex items-center gap-3 px-4 py-3 rounded-lg shadow-lg text-sm font-medium text-white animate-slide-in ${ |
| #56 | toast.type === "success" |
| #57 | ? "bg-emerald-600" |
| #58 | : toast.type === "error" |
| #59 | ? "bg-red-600" |
| #60 | : "bg-blue-600" |
| #61 | }`} |
| #62 | onClick={() => removeToast(toast.id)} |
| #63 | > |
| #64 | <span>{toast.message}</span> |
| #65 | <button className="ml-2 opacity-70 hover:opacity-100">×</button> |
| #66 | </div> |
| #67 | ))} |
| #68 | </div> |
| #69 | ); |
| #70 | } |
| #71 |