From c4e5a4616db87b39df5e16b75f1ba3c18407468f Mon Sep 17 00:00:00 2001 From: Belbin-GK <150322972+Belbin-GK@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:36:32 +0530 Subject: [PATCH] feat(notification): add custom notification component support (#5605) * feat: add custom notification component support * update the documentation of UINotificationService to support custom component integration --------- Co-authored-by: trenser-belbin --- .../services/UINotificationService/index.ts | 20 +++++++++- .../services/ui/ui-notification-service.md | 2 + .../contextProviders/NotificationProvider.tsx | 38 +++++++++++++++---- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/platform/core/src/services/UINotificationService/index.ts b/platform/core/src/services/UINotificationService/index.ts index 8e88374a3..8bfd5b3ff 100644 --- a/platform/core/src/services/UINotificationService/index.ts +++ b/platform/core/src/services/UINotificationService/index.ts @@ -4,6 +4,7 @@ const serviceImplementation = { console.debug('show() NOT IMPLEMENTED'); return null; }, + _customComponent: null, }; type ToastType = 'success' | 'error' | 'info' | 'warning' | 'loading'; @@ -17,21 +18,38 @@ class UINotificationService { }, }; + /** + * This provides flexibility in customizing the Notification default component + * + * @returns {React.Component} + */ + getCustomComponent() { + return serviceImplementation._customComponent; + } + /** * * * @param {*} { * hide: hideImplementation, * show: showImplementation, + * component: componentImplementation * } */ - public setServiceImplementation({ hide: hideImplementation, show: showImplementation }): void { + public setServiceImplementation({ + hide: hideImplementation, + show: showImplementation, + customComponent: customComponentImplementation, + }): void { if (hideImplementation) { serviceImplementation._hide = hideImplementation; } if (showImplementation) { serviceImplementation._show = showImplementation; } + if (customComponentImplementation) { + serviceImplementation._customComponent = customComponentImplementation; + } } /** diff --git a/platform/docs/docs/platform/services/ui/ui-notification-service.md b/platform/docs/docs/platform/services/ui/ui-notification-service.md index 5f700b58f..f05ffc56f 100644 --- a/platform/docs/docs/platform/services/ui/ui-notification-service.md +++ b/platform/docs/docs/platform/services/ui/ui-notification-service.md @@ -34,12 +34,14 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] | ---------- | --------------------------------------- | | `hide()` | Hides the specified notification | | `show()` | Creates and displays a new notification | +| `customComponent()` | Overrides the default Notification component | ## Implementations | Implementation | Consumer | | ---------------------------------------- | ----------------------------------------- | | [Snackbar Provider][snackbar-provider]\* | [SnackbarContainer][snackbar-container]\* | +| customComponent | user extensions via `setServiceImplementation({customComponent: Snackbar})` | `*` - Denotes maintained by OHIF diff --git a/platform/ui-next/src/contextProviders/NotificationProvider.tsx b/platform/ui-next/src/contextProviders/NotificationProvider.tsx index e824a4036..ed842462c 100644 --- a/platform/ui-next/src/contextProviders/NotificationProvider.tsx +++ b/platform/ui-next/src/contextProviders/NotificationProvider.tsx @@ -1,4 +1,12 @@ -import React, { createContext, useContext, useCallback, useEffect, ReactNode, useRef } from 'react'; +import React, { + createContext, + useContext, + useCallback, + useEffect, + ReactNode, + useState, + useRef, +} from 'react'; import PropTypes from 'prop-types'; import { Toaster, toast } from '../components'; @@ -27,17 +35,26 @@ const NotificationProvider = ({ title: '', message: '', duration: 5000, - position: 'bottom-right', // Aligning to Sonner's positioning system - type: 'info', // info, success, error + position: 'bottom-right', + type: 'info', + visible: true, }; + const [options, setOptions] = useState([]); // Cache for recent notifications to prevent duplicates // Structure: { [title_message_type]: { timestamp, id } } const recentNotificationsRef = useRef>({}); + const CustomNotification = service?.getCustomComponent(); + // Use the configurable deduplication interval from props const show = useCallback(options => { + const newNotification = { + ...DEFAULT_OPTIONS, + ...options, + }; + const { title, message, @@ -48,10 +65,7 @@ const NotificationProvider = ({ allowDuplicates = false, deduplicationInterval: optionsDeduplicationInterval, action, - } = { - ...DEFAULT_OPTIONS, - ...options, - }; + } = newNotification; // Use the provider's deduplicationInterval by default, but allow it to be overridden per notification const notificationDeduplicationInterval = optionsDeduplicationInterval || deduplicationInterval; @@ -143,10 +157,13 @@ const NotificationProvider = ({ // The entry will be checked against the deduplication interval } + setOptions(prev => [...prev, { ...newNotification, id: id }]); + return id; }, []); const hide = useCallback(id => { + setOptions(state => [...state.filter(item => item.id !== id)]); toast.dismiss(id); // Remove from cache if present @@ -160,6 +177,7 @@ const NotificationProvider = ({ }, []); const hideAll = useCallback(() => { + setOptions([]); toast.dismiss(); // Clear notification cache recentNotificationsRef.current = {}; @@ -198,7 +216,11 @@ const NotificationProvider = ({ return ( - + {CustomNotification ? ( + + ) : ( + + )} {children} );