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 <belbin.kunjumon@prenuvo.com>
This commit is contained in:
Belbin-GK 2025-12-05 18:36:32 +05:30 committed by GitHub
parent fd86a9e3e8
commit c4e5a4616d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 9 deletions

View File

@ -4,6 +4,7 @@ const serviceImplementation = {
console.debug('show() NOT IMPLEMENTED'); console.debug('show() NOT IMPLEMENTED');
return null; return null;
}, },
_customComponent: null,
}; };
type ToastType = 'success' | 'error' | 'info' | 'warning' | 'loading'; 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 {*} { * @param {*} {
* hide: hideImplementation, * hide: hideImplementation,
* show: showImplementation, * show: showImplementation,
* component: componentImplementation
* } * }
*/ */
public setServiceImplementation({ hide: hideImplementation, show: showImplementation }): void { public setServiceImplementation({
hide: hideImplementation,
show: showImplementation,
customComponent: customComponentImplementation,
}): void {
if (hideImplementation) { if (hideImplementation) {
serviceImplementation._hide = hideImplementation; serviceImplementation._hide = hideImplementation;
} }
if (showImplementation) { if (showImplementation) {
serviceImplementation._show = showImplementation; serviceImplementation._show = showImplementation;
} }
if (customComponentImplementation) {
serviceImplementation._customComponent = customComponentImplementation;
}
} }
/** /**

View File

@ -34,12 +34,14 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
| ---------- | --------------------------------------- | | ---------- | --------------------------------------- |
| `hide()` | Hides the specified notification | | `hide()` | Hides the specified notification |
| `show()` | Creates and displays a new notification | | `show()` | Creates and displays a new notification |
| `customComponent()` | Overrides the default Notification component |
## Implementations ## Implementations
| Implementation | Consumer | | Implementation | Consumer |
| ---------------------------------------- | ----------------------------------------- | | ---------------------------------------- | ----------------------------------------- |
| [Snackbar Provider][snackbar-provider]\* | [SnackbarContainer][snackbar-container]\* | | [Snackbar Provider][snackbar-provider]\* | [SnackbarContainer][snackbar-container]\* |
| customComponent | user extensions via `setServiceImplementation({customComponent: Snackbar})` |
`*` - Denotes maintained by OHIF `*` - Denotes maintained by OHIF

View File

@ -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 PropTypes from 'prop-types';
import { Toaster, toast } from '../components'; import { Toaster, toast } from '../components';
@ -27,17 +35,26 @@ const NotificationProvider = ({
title: '', title: '',
message: '', message: '',
duration: 5000, duration: 5000,
position: 'bottom-right', // Aligning to Sonner's positioning system position: 'bottom-right',
type: 'info', // info, success, error type: 'info',
visible: true,
}; };
const [options, setOptions] = useState([]);
// Cache for recent notifications to prevent duplicates // Cache for recent notifications to prevent duplicates
// Structure: { [title_message_type]: { timestamp, id } } // Structure: { [title_message_type]: { timestamp, id } }
const recentNotificationsRef = useRef<Record<string, NotificationCacheEntry>>({}); const recentNotificationsRef = useRef<Record<string, NotificationCacheEntry>>({});
const CustomNotification = service?.getCustomComponent();
// Use the configurable deduplication interval from props // Use the configurable deduplication interval from props
const show = useCallback(options => { const show = useCallback(options => {
const newNotification = {
...DEFAULT_OPTIONS,
...options,
};
const { const {
title, title,
message, message,
@ -48,10 +65,7 @@ const NotificationProvider = ({
allowDuplicates = false, allowDuplicates = false,
deduplicationInterval: optionsDeduplicationInterval, deduplicationInterval: optionsDeduplicationInterval,
action, action,
} = { } = newNotification;
...DEFAULT_OPTIONS,
...options,
};
// Use the provider's deduplicationInterval by default, but allow it to be overridden per notification // Use the provider's deduplicationInterval by default, but allow it to be overridden per notification
const notificationDeduplicationInterval = optionsDeduplicationInterval || deduplicationInterval; const notificationDeduplicationInterval = optionsDeduplicationInterval || deduplicationInterval;
@ -143,10 +157,13 @@ const NotificationProvider = ({
// The entry will be checked against the deduplication interval // The entry will be checked against the deduplication interval
} }
setOptions(prev => [...prev, { ...newNotification, id: id }]);
return id; return id;
}, []); }, []);
const hide = useCallback(id => { const hide = useCallback(id => {
setOptions(state => [...state.filter(item => item.id !== id)]);
toast.dismiss(id); toast.dismiss(id);
// Remove from cache if present // Remove from cache if present
@ -160,6 +177,7 @@ const NotificationProvider = ({
}, []); }, []);
const hideAll = useCallback(() => { const hideAll = useCallback(() => {
setOptions([]);
toast.dismiss(); toast.dismiss();
// Clear notification cache // Clear notification cache
recentNotificationsRef.current = {}; recentNotificationsRef.current = {};
@ -198,7 +216,11 @@ const NotificationProvider = ({
return ( return (
<NotificationContext.Provider value={{ show, hide, hideAll, getNotificationCache }}> <NotificationContext.Provider value={{ show, hide, hideAll, getNotificationCache }}>
<Toaster position="bottom-right" /> {CustomNotification ? (
<CustomNotification options={options} />
) : (
<Toaster position="bottom-right" />
)}
{children} {children}
</NotificationContext.Provider> </NotificationContext.Provider>
); );