Merge pull request #32542 from backstage/bui-toast
feat: Introduce ToastApi for rich notifications in the new frontend system
This commit is contained in:
@@ -70,7 +70,7 @@ import { StorageValueSnapshot } from '@backstage/core-plugin-api';
|
||||
import { SubRouteRef } from '@backstage/core-plugin-api';
|
||||
import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class AlertApiForwarder implements AlertApi {
|
||||
// (undocumented)
|
||||
alert$(): Observable<AlertMessage>;
|
||||
|
||||
@@ -26,13 +26,25 @@ import ObservableImpl from 'zen-observable';
|
||||
* missing alerts that were posted before subscription.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use ToastApi instead. AlertApi will be removed in a future release.
|
||||
*/
|
||||
export class AlertApiForwarder implements AlertApi {
|
||||
private readonly subject = new PublishSubject<AlertMessage>();
|
||||
private readonly recentAlerts: AlertMessage[] = [];
|
||||
private readonly maxBufferSize = 10;
|
||||
private hasWarnedDeprecation = false;
|
||||
|
||||
post(alert: AlertMessage) {
|
||||
if (!this.hasWarnedDeprecation) {
|
||||
this.hasWarnedDeprecation = true;
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'AlertApi is deprecated and will be removed in a future release. ' +
|
||||
'Please migrate to ToastApi from @backstage/frontend-plugin-api. ' +
|
||||
'ToastApi provides richer features including title/description, links, icons, and per-toast timeouts. ' +
|
||||
'Example: toastApi.post({ title: "Saved!", status: "success", timeout: 5000 })',
|
||||
);
|
||||
}
|
||||
this.recentAlerts.push(alert);
|
||||
if (this.recentAlerts.length > this.maxBufferSize) {
|
||||
this.recentAlerts.shift();
|
||||
|
||||
@@ -14,6 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated AlertApi is deprecated. Use ToastApi from `@backstage/frontend-plugin-api` instead.
|
||||
*
|
||||
* ToastApi provides richer notification features including title/description,
|
||||
* action links, custom icons, per-toast timeout control, and programmatic dismiss.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Before (AlertApi)
|
||||
* import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
* alertApi.post({ message: 'Saved!', severity: 'success', display: 'transient' });
|
||||
*
|
||||
* // After (ToastApi)
|
||||
* import { toastApiRef } from '@backstage/frontend-plugin-api';
|
||||
* toastApi.post({ title: 'Saved!', status: 'success', timeout: 5000 });
|
||||
* ```
|
||||
*/
|
||||
export {
|
||||
type AlertApi,
|
||||
type AlertMessage,
|
||||
|
||||
@@ -1026,6 +1026,8 @@ describe('createApp', () => {
|
||||
<api:app/dialog out=[core.api.factory] />
|
||||
<api:app/discovery out=[core.api.factory] />
|
||||
<api:app/alert out=[core.api.factory] />
|
||||
<api:app/toast-forwarder out=[core.api.factory] />
|
||||
<api:app/toast out=[core.api.factory] />
|
||||
<api:app/analytics out=[core.api.factory] />
|
||||
<api:app/error out=[core.api.factory] />
|
||||
<api:app/storage out=[core.api.factory] />
|
||||
|
||||
@@ -25,18 +25,18 @@ import { ReactNode } from 'react';
|
||||
import { SwappableComponentRef as SwappableComponentRef_2 } from '@backstage/frontend-plugin-api';
|
||||
import type { z } from 'zod';
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type AlertApi = {
|
||||
post(alert: AlertMessage): void;
|
||||
alert$(): Observable<AlertMessage>;
|
||||
};
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export const alertApiRef: ApiRef_2<AlertApi, 'core.alert'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type AlertMessage = {
|
||||
message: string;
|
||||
severity?: 'success' | 'info' | 'warning' | 'error';
|
||||
@@ -2241,6 +2241,34 @@ export const swappableComponentsApiRef: ApiRef_2<
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export type ToastApi = {
|
||||
post(toast: ToastApiMessage): ToastApiPostResult;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type ToastApiMessage = {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
||||
links?: ToastApiMessageLink[];
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type ToastApiMessageLink = {
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type ToastApiPostResult = {
|
||||
close(): void;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const toastApiRef: ApiRef<ToastApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TranslationApi = {
|
||||
getTranslation<
|
||||
|
||||
@@ -21,6 +21,14 @@ import { Observable } from '@backstage/types';
|
||||
* Message handled by the {@link AlertApi}.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link ToastApiMessage} from {@link ToastApi} instead. AlertApi will be removed in a future release.
|
||||
*
|
||||
* Migration guide:
|
||||
* - `message` becomes `title`
|
||||
* - `severity: 'error'` becomes `status: 'danger'`
|
||||
* - `severity: 'success' | 'info' | 'warning'` becomes `status: 'success' | 'info' | 'warning'`
|
||||
* - `display: 'transient'` becomes `timeout: 5000` (or custom milliseconds)
|
||||
* - `display: 'permanent'` means omitting `timeout`
|
||||
*/
|
||||
export type AlertMessage = {
|
||||
message: string;
|
||||
@@ -33,6 +41,23 @@ export type AlertMessage = {
|
||||
* The alert API is used to report alerts to the app, and display them to the user.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link ToastApi} instead. AlertApi will be removed in a future release.
|
||||
*
|
||||
* ToastApi provides richer notification features including:
|
||||
* - Title and optional description
|
||||
* - Action links
|
||||
* - Custom icons
|
||||
* - Per-toast timeout control
|
||||
* - Programmatic dismiss via returned key
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Before (AlertApi)
|
||||
* alertApi.post({ message: 'Saved!', severity: 'success', display: 'transient' });
|
||||
*
|
||||
* // After (ToastApi)
|
||||
* toastApi.post({ title: 'Saved!', status: 'success', timeout: 5000 });
|
||||
* ```
|
||||
*/
|
||||
export type AlertApi = {
|
||||
/**
|
||||
@@ -50,6 +75,7 @@ export type AlertApi = {
|
||||
* The {@link ApiRef} of {@link AlertApi}.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link toastApiRef} instead. AlertApi will be removed in a future release.
|
||||
*/
|
||||
export const alertApiRef = createApiRef<AlertApi>().with({
|
||||
id: 'core.alert',
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 { createApiRef, ApiRef } from '../system';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* Link item for toast notifications.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ToastApiMessageLink = {
|
||||
/** Display text for the link */
|
||||
label: string;
|
||||
/** URL the link points to */
|
||||
href: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Message handled by the {@link ToastApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ToastApiMessage = {
|
||||
/** Title of the toast (required) */
|
||||
title: ReactNode;
|
||||
/** Optional description text */
|
||||
description?: ReactNode;
|
||||
/** Status variant of the toast - defaults to 'success' */
|
||||
status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
||||
/** Optional array of links to display */
|
||||
links?: ToastApiMessageLink[];
|
||||
/** Timeout in milliseconds before auto-dismiss. If not set, toast is permanent. */
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle returned by {@link ToastApi.post} that allows programmatic control
|
||||
* of the posted toast.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ToastApiPostResult = {
|
||||
/** Dismiss the toast. */
|
||||
close(): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The toast API is used to display toast notifications to the user.
|
||||
*
|
||||
* @remarks
|
||||
* This API provides richer notification capabilities than the AlertApi,
|
||||
* including title/description, links, and per-toast timeout control.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const toastApi = useApi(toastApiRef);
|
||||
*
|
||||
* // Full-featured toast
|
||||
* toastApi.post({
|
||||
* title: 'Entity saved',
|
||||
* description: 'Your changes have been saved successfully.',
|
||||
* status: 'success',
|
||||
* timeout: 5000,
|
||||
* links: [{ label: 'View entity', href: '/catalog/default/component/my-service' }],
|
||||
* });
|
||||
*
|
||||
* // Simple toast
|
||||
* toastApi.post({ title: 'Processing...', status: 'info' });
|
||||
*
|
||||
* // Programmatic dismiss
|
||||
* const { close } = toastApi.post({ title: 'Uploading...', status: 'info' });
|
||||
* // Later...
|
||||
* close();
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ToastApi = {
|
||||
/**
|
||||
* Post a toast notification for display to the user.
|
||||
*
|
||||
* @param toast - The toast message to display
|
||||
* @returns A handle with a `close()` method to programmatically dismiss the toast
|
||||
*/
|
||||
post(toast: ToastApiMessage): ToastApiPostResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* The {@link ApiRef} of {@link ToastApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const toastApiRef: ApiRef<ToastApi> = createApiRef({
|
||||
id: 'core.toast',
|
||||
});
|
||||
@@ -48,6 +48,7 @@ export * from './OAuthRequestApi';
|
||||
export * from './RouteResolutionApi';
|
||||
export * from './StorageApi';
|
||||
export * from './AnalyticsApi';
|
||||
export * from './ToastApi';
|
||||
export * from './TranslationApi';
|
||||
export * from './PluginHeaderActionsApi';
|
||||
export * from './PluginWrapperApi';
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
|
||||
@layer tokens {
|
||||
/* Light theme tokens */
|
||||
:root {
|
||||
/* Light theme tokens (also used for nested light theme switching) */
|
||||
:root,
|
||||
[data-theme-mode='light'] {
|
||||
/* Font families */
|
||||
--bui-font-regular: system-ui;
|
||||
--bui-font-monospace: ui-monospace, 'Menlo', 'Monaco', 'Consolas',
|
||||
@@ -119,7 +120,7 @@
|
||||
--bui-fg-info-on-bg: #173da6;
|
||||
--bui-fg-danger: #ec3b18;
|
||||
--bui-fg-warning: #ef7a32;
|
||||
--bui-fg-success: #1ed760;
|
||||
--bui-fg-success: #1aaf4f;
|
||||
--bui-fg-info: #0d74ce;
|
||||
|
||||
/* Border colors */
|
||||
|
||||
Reference in New Issue
Block a user