"use client" import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { X, CheckCircle, AlertCircle, AlertTriangle, Info, Loader2 } from "lucide-react" import { cn } from "@/lib/utils" const notificationVariants = cva( "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-lg border p-4 pe-8 shadow-lg transition-all duration-300 ease-out", { variants: { variant: { default: "border-border bg-card text-foreground", destructive: "border-destructive bg-destructive text-destructive-foreground", success: "border-success bg-success text-success-foreground", warning: "border-warning bg-warning text-warning-foreground", info: "border-primary bg-primary text-primary-foreground", glass: "glass text-foreground border-border/20", }, size: { default: "max-w-md", sm: "max-w-sm", lg: "max-w-lg", xl: "max-w-xl", full: "max-w-full", }, position: { "top-right": "fixed top-4 right-4 z-50", "top-left": "fixed top-4 left-4 z-50", "bottom-right": "fixed bottom-4 right-4 z-50", "bottom-left": "fixed bottom-4 left-4 z-50", "top-center": "fixed top-4 left-1/2 transform -translate-x-1/2 z-50", "bottom-center": "fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50", "center": "fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50", }, }, defaultVariants: { variant: "default", size: "default", position: "top-right", }, } ) export interface NotificationProps extends React.HTMLAttributes, VariantProps { title?: string description?: string action?: React.ReactNode icon?: React.ReactNode loading?: boolean closable?: boolean autoClose?: boolean duration?: number onClose?: () => void } const Notification = React.forwardRef( ({ className, variant, size, position, title, description, action, icon, loading = false, closable = true, autoClose = true, duration = 5000, onClose, children, ...props }, ref) => { const [visible, setVisible] = React.useState(true) const [progress, setProgress] = React.useState(100) React.useEffect(() => { if (autoClose && !loading) { const startTime = Date.now() const interval = setInterval(() => { const elapsed = Date.now() - startTime const remaining = Math.max(0, 100 - (elapsed / duration) * 100) setProgress(remaining) if (remaining === 0) { clearInterval(interval) setVisible(false) onClose?.() } }, 50) return () => clearInterval(interval) } }, [autoClose, loading, duration, onClose]) const handleClose = () => { setVisible(false) onClose?.() } const defaultIcons = { default: , destructive: , success: , warning: , info: , glass: , } const displayIcon = icon || defaultIcons[variant as keyof typeof defaultIcons] || defaultIcons.default if (!visible) return null return (
{/* Progress Bar for Auto-close */} {autoClose && !loading && (
)}
{/* Icon */}
{loading ? ( ) : ( displayIcon )}
{/* Content */}
{title && (
{title}
)} {description && (
{description}
)} {children}
{/* Action */} {action && (
{action}
)}
{/* Close Button */} {closable && ( )}
) } ) Notification.displayName = "Notification" // Notification Context type NotificationItem = { id: string title?: string description?: string variant?: VariantProps["variant"] duration?: number action?: React.ReactNode icon?: React.ReactNode closable?: boolean autoClose?: boolean } interface NotificationContextType { notifications: NotificationItem[] notify: (notification: Omit) => void success: (notification: Omit) => void error: (notification: Omit) => void warning: (notification: Omit) => void info: (notification: Omit) => void dismiss: (id: string) => void dismissAll: () => void } const NotificationContext = React.createContext(undefined) export function NotificationProvider({ children }: { children: React.ReactNode }) { const [notifications, setNotifications] = React.useState([]) const notify = React.useCallback( (notification: Omit) => { const id = Math.random().toString(36).substr(2, 9) setNotifications(prev => [...prev, { ...notification, id }]) }, [] ) const success = React.useCallback( (notification: Omit) => notify({ ...notification, variant: "success" }), [notify] ) const error = React.useCallback( (notification: Omit) => notify({ ...notification, variant: "destructive" }), [notify] ) const warning = React.useCallback( (notification: Omit) => notify({ ...notification, variant: "warning" }), [notify] ) const info = React.useCallback( (notification: Omit) => notify({ ...notification, variant: "info" }), [notify] ) const dismiss = React.useCallback((id: string) => { setNotifications(prev => prev.filter(n => n.id !== id)) }, []) const dismissAll = React.useCallback(() => { setNotifications([]) }, []) const value = React.useMemo(() => ({ notifications, notify, success, error, warning, info, dismiss, dismissAll, }), [notifications, notify, success, error, warning, info, dismiss, dismissAll]) return ( {children} ) } export function useNotification() { const context = React.useContext(NotificationContext) if (context === undefined) { throw new Error("useNotification must be used within a NotificationProvider") } return context } // Notification Container function NotificationContainer() { const { notifications, dismiss } = useNotification() return (
{notifications.map((notification) => ( dismiss(notification.id)} className="pointer-events-auto animate-in slide-in-from-top-2" /> ))}
) } // Skeleton Notification for Loading States export const NotificationSkeleton = () => (
) export { Notification, notificationVariants }