feat: improve customization of notification snackbar

see changeset

Signed-off-by: Hellgren Heikki <heikki.hellgren@op.fi>
This commit is contained in:
Hellgren Heikki
2025-08-15 11:57:16 +03:00
parent d2d6d325f9
commit 8a24e0f24c
3 changed files with 119 additions and 22 deletions
+10
View File
@@ -0,0 +1,10 @@
---
'@backstage/plugin-notifications': patch
---
Improve customization of the notification snackbar.
Users can now customize the notification snackbar by providing custom components and icons
for different severity levels. Additionally, the location of the snackbar notifications
can be modified, the density of the snackbar can be changed, and the number of snacks can
be limited.
+26 -2
View File
@@ -13,6 +13,7 @@ import { Notification as Notification_2 } from '@backstage/plugin-notifications-
import { NotificationSettings } from '@backstage/plugin-notifications-common';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import { NotificationStatus } from '@backstage/plugin-notifications-common';
import * as React_2 from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { TableProps } from '@backstage/core-components';
@@ -100,6 +101,23 @@ export class NotificationsClient implements NotificationsApi {
): Promise<NotificationSettings>;
}
// @public (undocumented)
export type NotificationSnackbarProperties = {
enabled?: boolean;
autoHideDuration?: number | null;
anchorOrigin?: {
vertical: 'top' | 'bottom';
horizontal: 'left' | 'center' | 'right';
};
dense?: boolean;
maxSnack?: number;
snackStyle?: React_2.CSSProperties;
iconVariant?: Partial<Record<NotificationSeverity, React_2.ReactNode>>;
Components?: {
[key in NotificationSeverity]: React_2.JSXElementConstructor<any>;
};
};
// @public (undocumented)
export const NotificationsPage: (
props?: NotificationsPageProps,
@@ -125,17 +143,23 @@ export const notificationsPlugin: BackstagePlugin<
>;
// @public (undocumented)
export const NotificationsSidebarItem: (props?: {
export const NotificationsSidebarItem: (
props?: NotificationsSideBarItemProps,
) => JSX_2.Element;
// @public (undocumented)
export type NotificationsSideBarItemProps = {
webNotificationsEnabled?: boolean;
titleCounterEnabled?: boolean;
snackbarEnabled?: boolean;
snackbarAutoHideDuration?: number | null;
snackbarProps?: NotificationSnackbarProperties;
className?: string;
icon?: IconComponent;
text?: string;
disableHighlight?: boolean;
noTrack?: boolean;
}) => JSX_2.Element;
};
// @public (undocumented)
export const NotificationsTable: ({
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState, useCallback, useEffect } from 'react';
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNotificationsApi } from '../../hooks';
import { Link, SidebarItem } from '@backstage/core-components';
import NotificationsIcon from '@material-ui/icons/Notifications';
@@ -27,6 +28,7 @@ import { rootRouteRef } from '../../routes';
import { useSignal } from '@backstage/plugin-signals-react';
import {
Notification,
NotificationSeverity,
NotificationSignal,
} from '@backstage/plugin-notifications-common';
import { useWebNotifications } from '../../hooks/useWebNotifications';
@@ -79,18 +81,51 @@ declare module 'notistack' {
}
}
/** @public */
export const NotificationsSidebarItem = (props?: {
/**
* @public
*/
export type NotificationSnackbarProperties = {
enabled?: boolean;
autoHideDuration?: number | null;
anchorOrigin?: {
vertical: 'top' | 'bottom';
horizontal: 'left' | 'center' | 'right';
};
dense?: boolean;
maxSnack?: number;
snackStyle?: React.CSSProperties;
iconVariant?: Partial<Record<NotificationSeverity, React.ReactNode>>;
Components?: {
[key in NotificationSeverity]: React.JSXElementConstructor<any>;
};
};
/**
* @public
*/
export type NotificationsSideBarItemProps = {
webNotificationsEnabled?: boolean;
titleCounterEnabled?: boolean;
/**
* @deprecated Use `snackbarProps` instead.
*/
snackbarEnabled?: boolean;
/**
* @deprecated Use `snackbarProps` instead.
*/
snackbarAutoHideDuration?: number | null;
snackbarProps?: NotificationSnackbarProperties;
className?: string;
icon?: IconComponent;
text?: string;
disableHighlight?: boolean;
noTrack?: boolean;
}) => {
};
/** @public */
export const NotificationsSidebarItem = (
props?: NotificationsSideBarItemProps,
) => {
const {
webNotificationsEnabled = false,
titleCounterEnabled = true,
@@ -102,10 +137,21 @@ export const NotificationsSidebarItem = (props?: {
} = props ?? {
webNotificationsEnabled: false,
titleCounterEnabled: true,
snackbarEnabled: true,
snackbarAutoHideDuration: 10000,
snackbarProps: {
enabled: true,
autoHideDuration: 10000,
},
};
const snackbarProps = useMemo(
() =>
props?.snackbarProps ?? {
enabled: snackbarEnabled,
autoHideDuration: snackbarAutoHideDuration,
},
[props?.snackbarProps, snackbarAutoHideDuration, snackbarEnabled],
);
const { loading, error, value, retry } = useNotificationsApi(api =>
api.getStatus(),
);
@@ -185,7 +231,7 @@ export const NotificationsSidebarItem = (props?: {
useEffect(() => {
const handleNotificationSignal = (signal: NotificationSignal) => {
if (
(!webNotificationsEnabled && !snackbarEnabled) ||
(!webNotificationsEnabled && !snackbarProps.enabled) ||
signal.action !== 'new_notification'
) {
return;
@@ -204,7 +250,7 @@ export const NotificationsSidebarItem = (props?: {
link: notification.payload.link,
});
}
if (snackbarEnabled) {
if (snackbarProps.enabled) {
const { action } = getSnackbarProperties(notification);
const snackBarText =
notification.payload.title.length > 50
@@ -212,10 +258,14 @@ export const NotificationsSidebarItem = (props?: {
: notification.payload.title;
enqueueSnackbar(snackBarText, {
key: notification.id,
style: snackbarProps.snackStyle,
variant: notification.payload.severity,
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
anchorOrigin: snackbarProps.anchorOrigin ?? {
vertical: 'bottom',
horizontal: 'right',
},
action,
autoHideDuration: snackbarAutoHideDuration,
autoHideDuration: snackbarProps.autoHideDuration,
} as OptionsWithExtraProps<VariantType>);
}
})
@@ -235,11 +285,10 @@ export const NotificationsSidebarItem = (props?: {
lastSignal,
sendWebNotification,
webNotificationsEnabled,
snackbarEnabled,
snackbarAutoHideDuration,
notificationsApi,
alertApi,
getSnackbarProperties,
snackbarProps,
]);
useEffect(() => {
@@ -261,16 +310,30 @@ export const NotificationsSidebarItem = (props?: {
{snackbarEnabled && (
<SnackbarProvider
iconVariant={{
normal: <SeverityIcon severity="normal" />,
critical: <SeverityIcon severity="critical" />,
high: <SeverityIcon severity="high" />,
low: <SeverityIcon severity="low" />,
normal: snackbarProps?.iconVariant?.normal ?? (
<SeverityIcon severity="normal" />
),
critical: snackbarProps?.iconVariant?.critical ?? (
<SeverityIcon severity="critical" />
),
high: snackbarProps?.iconVariant?.high ?? (
<SeverityIcon severity="high" />
),
low: snackbarProps?.iconVariant?.low ?? (
<SeverityIcon severity="low" />
),
}}
dense={snackbarProps?.dense}
maxSnack={snackbarProps?.maxSnack}
Components={{
normal: StyledMaterialDesignContent,
critical: StyledMaterialDesignContent,
high: StyledMaterialDesignContent,
low: StyledMaterialDesignContent,
normal:
snackbarProps?.Components?.normal ?? StyledMaterialDesignContent,
critical:
snackbarProps?.Components?.critical ??
StyledMaterialDesignContent,
high:
snackbarProps?.Components?.high ?? StyledMaterialDesignContent,
low: snackbarProps?.Components?.low ?? StyledMaterialDesignContent,
}}
/>
)}