From 03311e33da9a8bb75d6b27efe4f816972b5bbb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 Apr 2026 16:35:29 +0200 Subject: [PATCH 1/4] Make NotificationDescription a swappable component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Fredrik Adelöw --- .../swappable-notification-description.md | 5 ++++ plugins/notifications/report.api.md | 16 +++++++++++ .../NotificationDescription.tsx | 27 ++++++++++++++++++- .../components/NotificationsTable/index.ts | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .changeset/swappable-notification-description.md diff --git a/.changeset/swappable-notification-description.md b/.changeset/swappable-notification-description.md new file mode 100644 index 0000000000..abfa470035 --- /dev/null +++ b/.changeset/swappable-notification-description.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +The notification description used in the notifications table is now a swappable component, so that apps can replace its rendering with a custom implementation. diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index ff39a03a8a..74dd1b137b 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -14,6 +14,7 @@ import { NotificationSettings } from '@backstage/plugin-notifications-common'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; import { NotificationStatus } from '@backstage/plugin-notifications-common'; import { RouteRef } from '@backstage/core-plugin-api'; +import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { TableProps } from '@backstage/core-components'; import { TranslationRef } from '@backstage/frontend-plugin-api'; @@ -49,6 +50,21 @@ export type GetTopicsResponse = { topics: string[]; }; +// @public +export const NotificationDescription: { + (props: NotificationDescriptionProps): JSX.Element | null; + ref: SwappableComponentRef< + NotificationDescriptionProps, + NotificationDescriptionProps + >; +}; + +// @public +export interface NotificationDescriptionProps { + // (undocumented) + description: string; +} + // @public (undocumented) export interface NotificationsApi { // (undocumented) diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx index 760473d064..dc023031a3 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx @@ -15,10 +15,22 @@ */ import { Text, Button } from '@backstage/ui'; import { useState } from 'react'; +import { createSwappableComponent } from '@backstage/frontend-plugin-api'; + +/** + * Props for the {@link NotificationDescription} swappable component. + * + * @public + */ +export interface NotificationDescriptionProps { + description: string; +} const MAX_LENGTH = 100; -export const NotificationDescription = (props: { description: string }) => { +const DefaultNotificationDescription = ( + props: NotificationDescriptionProps, +) => { const { description } = props; const [shown, setShown] = useState(false); const isLong = description.length > MAX_LENGTH; @@ -56,3 +68,16 @@ export const NotificationDescription = (props: { description: string }) => { ); }; + +/** + * Swappable component that renders the description of a notification in the + * notifications table. Apps can override this to customize how descriptions + * are displayed. + * + * @public + */ +export const NotificationDescription = + createSwappableComponent({ + id: 'notifications.notification-description', + loader: () => DefaultNotificationDescription, + }); diff --git a/plugins/notifications/src/components/NotificationsTable/index.ts b/plugins/notifications/src/components/NotificationsTable/index.ts index a1fb7a25c6..81d6cacce9 100644 --- a/plugins/notifications/src/components/NotificationsTable/index.ts +++ b/plugins/notifications/src/components/NotificationsTable/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './NotificationsTable'; +export * from './NotificationDescription'; From 4f7e5219de7895bb9bdef3c437a1455e036b4d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 Apr 2026 16:44:48 +0200 Subject: [PATCH 2/4] Load the default NotificationDescription implementation lazily MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Fredrik Adelöw --- .../DefaultNotificationDescription.tsx | 61 +++++++++++++++++++ .../NotificationDescription.tsx | 50 ++------------- 2 files changed, 65 insertions(+), 46 deletions(-) create mode 100644 plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx diff --git a/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx new file mode 100644 index 0000000000..d2f93038ee --- /dev/null +++ b/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Text, Button } from '@backstage/ui'; +import { useState } from 'react'; +import { NotificationDescriptionProps } from './NotificationDescription'; + +const MAX_LENGTH = 100; + +export const DefaultNotificationDescription = ( + props: NotificationDescriptionProps, +) => { + const { description } = props; + const [shown, setShown] = useState(false); + const isLong = description.length > MAX_LENGTH; + + if (!isLong) { + return {description}; + } + + if (shown) { + return ( + + {description}{' '} + + + ); + } + return ( + + {description.substring(0, MAX_LENGTH)}...{' '} + + + ); +}; diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx index dc023031a3..a88d62b9c5 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Text, Button } from '@backstage/ui'; -import { useState } from 'react'; import { createSwappableComponent } from '@backstage/frontend-plugin-api'; /** @@ -26,49 +24,6 @@ export interface NotificationDescriptionProps { description: string; } -const MAX_LENGTH = 100; - -const DefaultNotificationDescription = ( - props: NotificationDescriptionProps, -) => { - const { description } = props; - const [shown, setShown] = useState(false); - const isLong = description.length > MAX_LENGTH; - - if (!isLong) { - return {description}; - } - - if (shown) { - return ( - - {description}{' '} - - - ); - } - return ( - - {description.substring(0, MAX_LENGTH)}...{' '} - - - ); -}; - /** * Swappable component that renders the description of a notification in the * notifications table. Apps can override this to customize how descriptions @@ -79,5 +34,8 @@ const DefaultNotificationDescription = ( export const NotificationDescription = createSwappableComponent({ id: 'notifications.notification-description', - loader: () => DefaultNotificationDescription, + loader: () => + import('./DefaultNotificationDescription').then( + m => m.DefaultNotificationDescription, + ), }); From de9fc68cf12e20e864ca504b0dc83e330adeb286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 Apr 2026 17:10:29 +0200 Subject: [PATCH 3/4] Document the description prop on NotificationDescription MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Fredrik Adelöw --- plugins/notifications/report.api.md | 1 - .../components/NotificationsTable/NotificationDescription.tsx | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index 74dd1b137b..627ea51e82 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -61,7 +61,6 @@ export const NotificationDescription: { // @public export interface NotificationDescriptionProps { - // (undocumented) description: string; } diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx index a88d62b9c5..c284e1e85a 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx @@ -21,6 +21,9 @@ import { createSwappableComponent } from '@backstage/frontend-plugin-api'; * @public */ export interface NotificationDescriptionProps { + /** + * The plain-text description of the notification. + */ description: string; } From 94c1cf55c7fbfdc26c61dca7f50aff7b1b1fb8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 Apr 2026 18:14:54 +0200 Subject: [PATCH 4/4] Use a type-only import for NotificationDescriptionProps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Fredrik Adelöw --- .../NotificationsTable/DefaultNotificationDescription.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx index d2f93038ee..9524937380 100644 --- a/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx +++ b/plugins/notifications/src/components/NotificationsTable/DefaultNotificationDescription.tsx @@ -15,7 +15,7 @@ */ import { Text, Button } from '@backstage/ui'; import { useState } from 'react'; -import { NotificationDescriptionProps } from './NotificationDescription'; +import type { NotificationDescriptionProps } from './NotificationDescription'; const MAX_LENGTH = 100;