Add Kelola Data
This commit is contained in:
135
components/ui/toast-provider.tsx
Normal file
135
components/ui/toast-provider.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback } from "react";
|
||||
import { Toast, ToastTitle, ToastDescription, ToastClose } from "./toast";
|
||||
import { CheckCircle, AlertCircle, AlertTriangle, Info, X } from "lucide-react";
|
||||
|
||||
interface ToastMessage {
|
||||
id: string;
|
||||
type: "success" | "error" | "warning" | "info";
|
||||
title: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
interface ToastContextType {
|
||||
showToast: (message: Omit<ToastMessage, "id">) => void;
|
||||
showSuccess: (title: string, description?: string) => void;
|
||||
showError: (title: string, description?: string) => void;
|
||||
showWarning: (title: string, description?: string) => void;
|
||||
showInfo: (title: string, description?: string) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
||||
|
||||
export const useToast = () => {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error("useToast must be used within a ToastProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface ToastProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
|
||||
const [toasts, setToasts] = useState<ToastMessage[]>([]);
|
||||
|
||||
const removeToast = useCallback((id: string) => {
|
||||
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
||||
}, []);
|
||||
|
||||
const showToast = useCallback((message: Omit<ToastMessage, "id">) => {
|
||||
const id = Math.random().toString(36).substr(2, 9);
|
||||
const newToast: ToastMessage = {
|
||||
...message,
|
||||
id,
|
||||
duration: message.duration || 5000,
|
||||
};
|
||||
|
||||
setToasts((prev) => [...prev, newToast]);
|
||||
|
||||
// Auto remove toast after duration
|
||||
setTimeout(() => {
|
||||
removeToast(id);
|
||||
}, newToast.duration);
|
||||
}, [removeToast]);
|
||||
|
||||
const showSuccess = useCallback((title: string, description?: string) => {
|
||||
showToast({ type: "success", title, description });
|
||||
}, [showToast]);
|
||||
|
||||
const showError = useCallback((title: string, description?: string) => {
|
||||
showToast({ type: "error", title, description });
|
||||
}, [showToast]);
|
||||
|
||||
const showWarning = useCallback((title: string, description?: string) => {
|
||||
showToast({ type: "warning", title, description });
|
||||
}, [showToast]);
|
||||
|
||||
const showInfo = useCallback((title: string, description?: string) => {
|
||||
showToast({ type: "info", title, description });
|
||||
}, [showToast]);
|
||||
|
||||
const getToastVariant = (type: ToastMessage["type"]) => {
|
||||
switch (type) {
|
||||
case "success":
|
||||
return "success";
|
||||
case "error":
|
||||
return "destructive";
|
||||
case "warning":
|
||||
return "warning";
|
||||
case "info":
|
||||
return "info";
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
};
|
||||
|
||||
const getToastIcon = (type: ToastMessage["type"]) => {
|
||||
switch (type) {
|
||||
case "success":
|
||||
return <CheckCircle className="h-5 w-5 text-green-600" />;
|
||||
case "error":
|
||||
return <AlertCircle className="h-5 w-5 text-red-600" />;
|
||||
case "warning":
|
||||
return <AlertTriangle className="h-5 w-5 text-yellow-600" />;
|
||||
case "info":
|
||||
return <Info className="h-5 w-5 text-blue-600" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ToastContext.Provider
|
||||
value={{ showToast, showSuccess, showError, showWarning, showInfo }}
|
||||
>
|
||||
{children}
|
||||
|
||||
{/* Toast Container */}
|
||||
<div className="fixed top-4 right-4 z-50 space-y-2">
|
||||
{toasts.map((toast) => (
|
||||
<Toast
|
||||
key={toast.id}
|
||||
variant={getToastVariant(toast.type)}
|
||||
className="min-w-[300px]"
|
||||
>
|
||||
<div className="flex items-start space-x-3">
|
||||
{getToastIcon(toast.type)}
|
||||
<div className="flex-1">
|
||||
<ToastTitle>{toast.title}</ToastTitle>
|
||||
{toast.description && (
|
||||
<ToastDescription>{toast.description}</ToastDescription>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ToastClose onClick={() => removeToast(toast.id)} />
|
||||
</Toast>
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user