@@ -24,8 +24,43 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
2424const logger = createLogger ( 'Notifications' )
2525
2626// Constants
27- const NOTIFICATION_TIMEOUT = 4000
28- const FADE_DURATION = 300
27+ const NOTIFICATION_TIMEOUT = 4000 // Show notification for 4 seconds
28+ const FADE_DURATION = 500 // Fade out over 500ms
29+
30+ // Define keyframes for the animations in a style tag
31+ const AnimationStyles = ( ) => (
32+ < style jsx global > { `
33+ @keyframes notification-slide {
34+ 0% {
35+ opacity: 0;
36+ transform: translateY(-100%);
37+ }
38+ 100% {
39+ opacity: 1;
40+ transform: translateY(0);
41+ }
42+ }
43+
44+ @keyframes notification-fade-out {
45+ 0% {
46+ opacity: 1;
47+ transform: translateY(0);
48+ }
49+ 100% {
50+ opacity: 0;
51+ transform: translateY(-10%);
52+ }
53+ }
54+
55+ .animate-notification-slide {
56+ animation: notification-slide 300ms ease forwards;
57+ }
58+
59+ .animate-notification-fade-out {
60+ animation: notification-fade-out ${ FADE_DURATION } ms ease forwards;
61+ }
62+ ` } </ style >
63+ )
2964
3065// Icon mapping for notification types
3166const NotificationIcon = {
@@ -92,15 +127,23 @@ function DeleteApiConfirmation({
92127 */
93128export function NotificationList ( ) {
94129 // Store access
95- const { notifications, hideNotification } = useNotificationStore ( )
130+ const { notifications, hideNotification, markAsRead , removeNotification } = useNotificationStore ( )
96131 const { activeWorkflowId } = useWorkflowRegistry ( )
97132
98133 // Local state
99134 const [ fadingNotifications , setFadingNotifications ] = useState < Set < string > > ( new Set ( ) )
135+ const [ removedIds , setRemovedIds ] = useState < Set < string > > ( new Set ( ) )
100136
101- // Filter to only show visible notifications for the current workflow
137+ // Filter to only show:
138+ // 1. Visible notifications for the current workflow
139+ // 2. That are either unread OR marked as persistent
140+ // 3. And have not been marked for removal
102141 const visibleNotifications = notifications . filter (
103- ( n ) => n . isVisible && n . workflowId === activeWorkflowId
142+ ( n ) =>
143+ n . isVisible &&
144+ n . workflowId === activeWorkflowId &&
145+ ( ! n . read || n . options ?. isPersistent ) &&
146+ ! removedIds . has ( n . id )
104147 )
105148
106149 // Handle auto-dismissal of non-persistent notifications
@@ -117,9 +160,14 @@ export function NotificationList() {
117160 setFadingNotifications ( ( prev ) => new Set ( [ ...prev , notification . id ] ) )
118161 } , NOTIFICATION_TIMEOUT )
119162
120- // Hide notification after fade completes
163+ // Hide notification after fade completes and mark for removal from DOM
121164 const hideTimer = setTimeout ( ( ) => {
122165 hideNotification ( notification . id )
166+ markAsRead ( notification . id )
167+
168+ // Mark this notification ID as removed to exclude it from rendering
169+ setRemovedIds ( ( prev ) => new Set ( [ ...prev , notification . id ] ) )
170+
123171 setFadingNotifications ( ( prev ) => {
124172 const next = new Set ( prev )
125173 next . delete ( notification . id )
@@ -132,28 +180,45 @@ export function NotificationList() {
132180
133181 // Cleanup timers on unmount or when notifications change
134182 return ( ) => timers . forEach ( clearTimeout )
135- } , [ visibleNotifications , hideNotification ] )
183+ } , [ visibleNotifications , hideNotification , markAsRead ] )
136184
137185 // Early return if no notifications to show
138186 if ( visibleNotifications . length === 0 ) return null
139187
140188 return (
141- < div
142- className = "absolute left-1/2 z-50 space-y-2 max-w-lg w-full"
143- style = { {
144- top : '30px' ,
145- transform : 'translateX(-50%)' ,
146- } }
147- >
148- { visibleNotifications . map ( ( notification ) => (
149- < NotificationAlert
150- key = { notification . id }
151- notification = { notification }
152- isFading = { fadingNotifications . has ( notification . id ) }
153- onHide = { hideNotification }
154- />
155- ) ) }
156- </ div >
189+ < >
190+ < AnimationStyles />
191+ < div
192+ className = "absolute left-1/2 z-50 space-y-2 max-w-lg w-full pointer-events-none"
193+ style = { {
194+ top : '30px' ,
195+ transform : 'translateX(-50%)' ,
196+ } }
197+ >
198+ { visibleNotifications . map ( ( notification ) => (
199+ < NotificationAlert
200+ key = { notification . id }
201+ notification = { notification }
202+ isFading = { fadingNotifications . has ( notification . id ) }
203+ onHide = { ( id ) => {
204+ hideNotification ( id )
205+ markAsRead ( id )
206+ // Start the fade out animation
207+ setFadingNotifications ( ( prev ) => new Set ( [ ...prev , id ] ) )
208+ // Remove from DOM after animation completes
209+ setTimeout ( ( ) => {
210+ setRemovedIds ( ( prev ) => new Set ( [ ...prev , id ] ) )
211+ setFadingNotifications ( ( prev ) => {
212+ const next = new Set ( prev )
213+ next . delete ( id )
214+ return next
215+ } )
216+ } , FADE_DURATION )
217+ } }
218+ />
219+ ) ) }
220+ </ div >
221+ </ >
157222 )
158223}
159224
@@ -168,13 +233,14 @@ interface NotificationAlertProps {
168233
169234function NotificationAlert ( { notification, isFading, onHide } : NotificationAlertProps ) {
170235 const { id, type, message, options, workflowId } = notification
171- const Icon = NotificationIcon [ type ]
172236 const [ isDeleteDialogOpen , setIsDeleteDialogOpen ] = useState ( false )
173237 const { setDeploymentStatus } = useWorkflowStore ( )
174238 const { isDeployed } = useWorkflowStore ( ( state ) => ( {
175239 isDeployed : state . isDeployed ,
176240 } ) )
177241
242+ const Icon = NotificationIcon [ type ]
243+
178244 const handleDeleteApi = async ( ) => {
179245 if ( ! workflowId ) return
180246
@@ -202,8 +268,10 @@ function NotificationAlert({ notification, isFading, onHide }: NotificationAlert
202268 < >
203269 < Alert
204270 className = { cn (
205- 'transition-all duration-300 ease-in-out opacity-0 translate-y-[-100%]' ,
206- isFading ? 'animate-notification-fade-out' : 'animate-notification-slide' ,
271+ 'transition-all duration-300 ease-in-out opacity-0 translate-y-[-100%] pointer-events-auto' ,
272+ isFading
273+ ? 'animate-notification-fade-out pointer-events-none'
274+ : 'animate-notification-slide' ,
207275 NotificationColors [ type ]
208276 ) }
209277 >
0 commit comments