1
+ import React , { useEffect , useState } from 'react' ;
2
+ import { X , CheckCircle , AlertCircle , AlertTriangle , Info , Trophy } from 'lucide-react' ;
3
+ import { useNotifications } from '../../contexts/NotificationContext' ;
4
+ import { Notification } from '../../types' ;
5
+
6
+ interface NotificationToastProps {
7
+ notification : Notification ;
8
+ }
9
+
10
+ export const NotificationToast : React . FC < NotificationToastProps > = ( { notification } ) => {
11
+ const { removeNotification } = useNotifications ( ) ;
12
+ const [ isVisible , setIsVisible ] = useState ( false ) ;
13
+ const [ isLeaving , setIsLeaving ] = useState ( false ) ;
14
+
15
+ useEffect ( ( ) => {
16
+ // Trigger entrance animation
17
+ const timer = setTimeout ( ( ) => setIsVisible ( true ) , 10 ) ;
18
+ return ( ) => clearTimeout ( timer ) ;
19
+ } , [ ] ) ;
20
+
21
+ const handleClose = ( ) => {
22
+ setIsLeaving ( true ) ;
23
+ setTimeout ( ( ) => {
24
+ removeNotification ( notification . id ) ;
25
+ } , 300 ) ;
26
+ } ;
27
+
28
+ const getIcon = ( ) => {
29
+ switch ( notification . type ) {
30
+ case 'success' :
31
+ return < CheckCircle className = "w-5 h-5 text-emerald-500" /> ;
32
+ case 'error' :
33
+ return < AlertCircle className = "w-5 h-5 text-red-500" /> ;
34
+ case 'warning' :
35
+ return < AlertTriangle className = "w-5 h-5 text-amber-500" /> ;
36
+ case 'achievement' :
37
+ return < Trophy className = "w-5 h-5 text-yellow-500" /> ;
38
+ default :
39
+ return < Info className = "w-5 h-5 text-blue-500" /> ;
40
+ }
41
+ } ;
42
+
43
+ const getBackgroundColor = ( ) => {
44
+ switch ( notification . type ) {
45
+ case 'success' :
46
+ return 'bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-800' ;
47
+ case 'error' :
48
+ return 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800' ;
49
+ case 'warning' :
50
+ return 'bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800' ;
51
+ case 'achievement' :
52
+ return 'bg-gradient-to-r from-yellow-50 to-orange-50 dark:from-yellow-900/20 dark:to-orange-900/20 border-yellow-200 dark:border-yellow-800' ;
53
+ default :
54
+ return 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800' ;
55
+ }
56
+ } ;
57
+
58
+ return (
59
+ < div
60
+ className = { `
61
+ transform transition-all duration-300 ease-in-out
62
+ ${ isVisible && ! isLeaving ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0' }
63
+ ${ getBackgroundColor ( ) }
64
+ border rounded-lg shadow-lg p-4 max-w-sm w-full
65
+ backdrop-blur-sm
66
+ ` }
67
+ >
68
+ < div className = "flex items-start space-x-3" >
69
+ < div className = "flex-shrink-0 mt-0.5" >
70
+ { getIcon ( ) }
71
+ </ div >
72
+
73
+ < div className = "flex-1 min-w-0" >
74
+ < h4 className = "text-sm font-semibold text-gray-900 dark:text-gray-100" >
75
+ { notification . title }
76
+ </ h4 >
77
+ < p className = "text-sm text-gray-700 dark:text-gray-300 mt-1" >
78
+ { notification . message }
79
+ </ p >
80
+
81
+ { notification . action && (
82
+ < button
83
+ onClick = { notification . action . onClick }
84
+ className = "mt-2 text-sm font-medium text-indigo-600 dark:text-indigo-400 hover:text-indigo-500 dark:hover:text-indigo-300 transition-colors"
85
+ >
86
+ { notification . action . label }
87
+ </ button >
88
+ ) }
89
+ </ div >
90
+
91
+ < button
92
+ onClick = { handleClose }
93
+ className = "flex-shrink-0 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition-colors"
94
+ >
95
+ < X className = "w-4 h-4" />
96
+ </ button >
97
+ </ div >
98
+ </ div >
99
+ ) ;
100
+ } ;
0 commit comments