| 'use client' |
|
|
| import { createContext, useContext, useState, useCallback, ReactNode } from 'react' |
|
|
| type ToastType = 'success' | 'error' | 'warning' | 'info' |
|
|
| interface Toast { |
| id: number |
| type: ToastType |
| message: string |
| } |
|
|
| interface ToastContextValue { |
| toast: (message: string, type?: ToastType) => void |
| success: (message: string) => void |
| error: (message: string) => void |
| warning: (message: string) => void |
| info: (message: string) => void |
| } |
|
|
| const ToastContext = createContext<ToastContextValue | null>(null) |
|
|
| let nextId = 0 |
|
|
| export function ToastProvider({ children }: { children: ReactNode }) { |
| const [toasts, setToasts] = useState<Toast[]>([]) |
|
|
| const removeToast = useCallback((id: number) => { |
| setToasts((prev) => prev.filter((t) => t.id !== id)) |
| }, []) |
|
|
| const addToast = useCallback( |
| (message: string, type: ToastType = 'info') => { |
| const id = ++nextId |
| setToasts((prev) => [...prev, { id, type, message }]) |
| setTimeout(() => removeToast(id), 4000) |
| }, |
| [removeToast] |
| ) |
|
|
| const value: ToastContextValue = { |
| toast: addToast, |
| success: (msg) => addToast(msg, 'success'), |
| error: (msg) => addToast(msg, 'error'), |
| warning: (msg) => addToast(msg, 'warning'), |
| info: (msg) => addToast(msg, 'info'), |
| } |
|
|
| return ( |
| <ToastContext.Provider value={value}> |
| {children} |
| {/* Toast container */} |
| <div |
| className="fixed bottom-4 right-4 z-[100] flex flex-col gap-2 pointer-events-none" |
| aria-live="polite" |
| aria-label="Bildirimler" |
| > |
| {toasts.map((t) => ( |
| <div |
| key={t.id} |
| className={`pointer-events-auto flex items-center gap-3 px-4 py-3 rounded-lg shadow-lg text-sm font-medium animate-slide-in-right ${typeStyles[t.type]}`} |
| role="alert" |
| > |
| <span className="shrink-0">{typeIcons[t.type]}</span> |
| <span className="flex-1">{t.message}</span> |
| <button |
| onClick={() => removeToast(t.id)} |
| className="shrink-0 opacity-60 hover:opacity-100 transition-opacity" |
| aria-label="Kapat" |
| > |
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> |
| </svg> |
| </button> |
| </div> |
| ))} |
| </div> |
| </ToastContext.Provider> |
| ) |
| } |
|
|
| export function useToast(): ToastContextValue { |
| const ctx = useContext(ToastContext) |
| if (!ctx) throw new Error('useToast must be used within ToastProvider') |
| return ctx |
| } |
|
|
| |
|
|
| const typeStyles: Record<ToastType, string> = { |
| success: 'bg-green-600 text-white', |
| error: 'bg-red-600 text-white', |
| warning: 'bg-yellow-500 text-white', |
| info: 'bg-blue-600 text-white', |
| } |
|
|
| const typeIcons: Record<ToastType, string> = { |
| success: 'β', |
| error: 'β', |
| warning: 'β ', |
| info: 'βΉ', |
| } |
|
|