"use client";
import { cva } from "class-variance-authority";
import {
AlertCircle,
AlertTriangle,
CheckCircle2,
Info,
X,
} from "lucide-react";
import * as React from "react";
import { cn } from "../lib/utils";
const toastVariants = cva(
"pointer-events-auto relative flex w-full items-center justify-between gap-3 overflow-hidden rounded-lg border p-4 shadow-lg transition-all",
{
variants: {
variant: {
default: "border-border bg-background text-foreground",
success: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
error: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]",
warning: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]",
info: "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]",
},
},
defaultVariants: {
variant: "default",
},
},
);
const icons = {
success: CheckCircle2,
error: AlertCircle,
warning: AlertTriangle,
info: Info,
default: Info,
};
export interface Toast {
id: string;
title: string;
description?: string;
variant?: "default" | "success" | "error" | "warning" | "info";
duration?: number;
}
interface ToastProps extends Toast {
onDismiss: (id: string) => void;
}
function ToastComponent({
id,
title,
description,
variant = "default",
onDismiss,
}: ToastProps) {
const Icon = icons[variant];
return (
{title}
{description && (
{description}
)}
);
}
interface ToastContainerProps {
toasts: Toast[];
onDismiss: (id: string) => void;
}
export function ToastContainer({ toasts, onDismiss }: ToastContainerProps) {
return (
{toasts.map((toast) => (
))}
);
}
// Toast Context and Hook
type ToastInput = Omit;
interface ToastContextValue {
toasts: Toast[];
toast: (input: ToastInput) => void;
success: (title: string, description?: string) => void;
error: (title: string, description?: string) => void;
warning: (title: string, description?: string) => void;
info: (title: string, description?: string) => void;
dismiss: (id: string) => void;
}
const ToastContext = React.createContext(null);
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = React.useState([]);
const dismiss = React.useCallback((id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
const toast = React.useCallback(
(input: ToastInput) => {
const id = Math.random().toString(36).slice(2);
const newToast: Toast = { id, ...input };
setToasts((prev) => [...prev, newToast]);
// Auto-dismiss after duration
const duration = input.duration ?? 5000;
if (duration > 0) {
setTimeout(() => dismiss(id), duration);
}
},
[dismiss],
);
const success = React.useCallback(
(title: string, description?: string) => {
toast({ title, description, variant: "success" });
},
[toast],
);
const error = React.useCallback(
(title: string, description?: string) => {
toast({ title, description, variant: "error" });
},
[toast],
);
const warning = React.useCallback(
(title: string, description?: string) => {
toast({ title, description, variant: "warning" });
},
[toast],
);
const info = React.useCallback(
(title: string, description?: string) => {
toast({ title, description, variant: "info" });
},
[toast],
);
const value = React.useMemo(
() => ({ toasts, toast, success, error, warning, info, dismiss }),
[toasts, toast, success, error, warning, info, dismiss],
);
return (
{children}
);
}
export function useToast() {
const context = React.useContext(ToastContext);
if (!context) {
throw new Error("useToast must be used within a ToastProvider");
}
return context;
}