diff --git a/.changeset/deep-crabs-dig.md b/.changeset/deep-crabs-dig.md new file mode 100644 index 0000000000..0e0769c4ea --- /dev/null +++ b/.changeset/deep-crabs-dig.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed --bui-fg-success token in light mode to be more accessible. diff --git a/.changeset/deprecate-alert-api.md b/.changeset/deprecate-alert-api.md index 4041c9ad85..c9cd98a5a1 100644 --- a/.changeset/deprecate-alert-api.md +++ b/.changeset/deprecate-alert-api.md @@ -14,7 +14,7 @@ Deprecated `AlertApi` in favor of the new `ToastApi`. - **Title and Description**: Display a prominent title with optional description text - **Action Links**: Include clickable links within notifications -- **Custom Icons**: Override default icons or disable them entirely +- **Status Variants**: Support for neutral, info, success, warning, and danger statuses - **Per-toast Timeout**: Control auto-dismiss timing for each notification individually - **Programmatic Dismiss**: Close notifications via the key returned from `post()` diff --git a/.changeset/toast-api-introduction.md b/.changeset/toast-api-introduction.md index 24114797e6..b5fab6cf35 100644 --- a/.changeset/toast-api-introduction.md +++ b/.changeset/toast-api-introduction.md @@ -10,7 +10,7 @@ The new `ToastApi` provides enhanced notification capabilities compared to the e - **Title and Description**: Toasts support both a title and an optional description - **Custom Timeouts**: Each toast can specify its own timeout duration - **Links**: Toasts can include action links -- **Icons**: Support for custom icons or disabling the default icon +- **Status Variants**: Support for neutral, info, success, warning, and danger statuses - **Programmatic Dismiss**: Toasts can be dismissed programmatically using the key returned from `post()` **Usage:** diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 0373cea873..647533b3b2 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -19,7 +19,6 @@ import { JSX as JSX_2 } from 'react/jsx-runtime'; import { JSX as JSX_3 } from 'react'; import { Observable } from '@backstage/types'; import { PropsWithChildren } from 'react'; -import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { SwappableComponentRef as SwappableComponentRef_2 } from '@backstage/frontend-plugin-api'; import type { z } from 'zod'; @@ -1957,8 +1956,7 @@ export type ToastLink = { export type ToastMessage = { title: ReactNode; description?: ReactNode; - status?: 'info' | 'success' | 'warning' | 'danger'; - icon?: boolean | ReactElement; + status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger'; links?: ToastLink[]; timeout?: number; }; diff --git a/packages/frontend-plugin-api/src/apis/definitions/ToastApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ToastApi.ts index a5d1c9707e..761a62e83f 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ToastApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ToastApi.ts @@ -16,7 +16,7 @@ import { createApiRef, ApiRef } from '../system'; import { Observable } from '@backstage/types'; -import { ReactNode, ReactElement } from 'react'; +import { ReactNode } from 'react'; /** * Link item for toast notifications. @@ -41,9 +41,7 @@ export type ToastMessage = { /** Optional description text */ description?: ReactNode; /** Status variant of the toast - defaults to 'success' */ - status?: 'info' | 'success' | 'warning' | 'danger'; - /** Whether to show an icon, or a custom icon element */ - icon?: boolean | ReactElement; + status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger'; /** Optional array of links to display */ links?: ToastLink[]; /** Timeout in milliseconds before auto-dismiss. If not set, toast is permanent. */ @@ -65,7 +63,7 @@ export type ToastMessageWithKey = ToastMessage & { * * @remarks * This API provides richer notification capabilities than the AlertApi, - * including title/description, custom icons, links, and per-toast timeout control. + * including title/description, links, and per-toast timeout control. * * @example * ```tsx diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index c950aa4f3a..0e50b40e19 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -19,7 +19,6 @@ import { JSX as JSX_3 } from 'react/jsx-runtime'; import { NavContentComponent } from '@backstage/plugin-app-react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import type { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SignInPageProps } from '@backstage/plugin-app-react'; @@ -1024,9 +1023,8 @@ export default appPlugin; // @public export interface ToastContent { description?: ReactNode; - icon?: boolean | ReactElement; links?: ToastLink[]; - status?: 'info' | 'success' | 'warning' | 'danger'; + status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger'; title: ReactNode; } diff --git a/plugins/app/src/components/Toast/Toast.css b/plugins/app/src/components/Toast/Toast.css index d35079d74a..050b2c6fa2 100644 --- a/plugins/app/src/components/Toast/Toast.css +++ b/plugins/app/src/components/Toast/Toast.css @@ -47,16 +47,14 @@ box-sizing: border-box; display: flex; align-items: flex-start; - padding: 1rem; - gap: 0.75rem; + padding: var(--bui-space-4); + gap: var(--bui-space-3); /* Appearance */ - border-radius: 0.5rem; - background-color: #1f1f1f; - color: #ffffff; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif; - font-size: 0.875rem; + border-radius: var(--bui-radius-3); + background-color: var(--bui-bg-surface-1); + font-family: var(--bui-font-regular); + font-size: var(--bui-font-size-3); outline: none; cursor: default; user-select: none; @@ -68,18 +66,17 @@ transform-origin: bottom center; /* Shadow */ - box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.4); -} + box-shadow: 0 4px 12px -2px rgba(0 0 0 / 0.4); -/* Light mode toast (inverted from dark app theme) */ -[data-theme-mode='light'] .toast { - background-color: #ffffff; - color: #1f1f1f; - box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05); + /* Focus ring */ + &:focus-visible { + outline: 2px solid var(--bui-border-focus); + outline-offset: 2px; + } } .toast:focus-visible { - outline: 2px solid #6366f1; + outline: 2px solid var(--bui-border-focus); outline-offset: 2px; } @@ -97,30 +94,34 @@ } /* Status variants - color icon and title */ +.toast[data-status='neutral'] .toast-title { + color: var(--bui-fg-primary); +} + .toast[data-status='info'] .toast-icon, .toast[data-status='info'] .toast-title { - color: #3b82f6; + color: var(--bui-fg-info); } .toast[data-status='success'] .toast-icon, .toast[data-status='success'] .toast-title { - color: #22c55e; + color: var(--bui-fg-success); } .toast[data-status='warning'] .toast-icon, .toast[data-status='warning'] .toast-title { - color: #f59e0b; + color: var(--bui-fg-warning); } .toast[data-status='danger'] .toast-icon, .toast[data-status='danger'] .toast-title { - color: #ef4444; + color: var(--bui-fg-danger); } .toast-wrapper { display: flex; align-items: flex-start; - gap: 0.75rem; + gap: var(--bui-space-3); flex: 1; min-width: 0; } @@ -128,7 +129,7 @@ .toast-content { display: flex; flex-direction: column; - gap: 0.25rem; + gap: var(--bui-space-1); } /* Icon */ @@ -137,7 +138,7 @@ display: flex; align-items: center; justify-content: center; - margin-top: 0.125rem; + margin-top: var(--bui-space-0_5); } .toast-icon svg { @@ -147,29 +148,26 @@ /* Title */ .toast-title { - font-weight: 600; - font-size: 0.875rem; + font-weight: var(--bui-font-weight-bold); + font-size: var(--bui-font-size-3); word-wrap: break-word; - margin-top: 0.125rem; + margin-top: var(--bui-space-0_5); } /* Description */ .toast-description { - color: rgba(255, 255, 255, 0.7); - font-size: 0.875rem; + color: var(--bui-fg-secondary); + font-size: var(--bui-font-size-3); + opacity: 0.9; word-wrap: break-word; } -[data-theme-mode='light'] .toast-description { - color: rgba(0, 0, 0, 0.6); -} - /* Links */ .toast-links { display: flex; flex-wrap: wrap; - gap: 0.75rem; - margin-top: 0.25rem; + gap: var(--bui-space-3); + margin-top: var(--bui-space-1); } .toast-links a { @@ -205,11 +203,12 @@ margin: -0.25rem -0.25rem 0 0; padding: 0; border: none; - border-radius: 0.25rem; + border-radius: var(--bui-radius-2); background: transparent; color: inherit; cursor: pointer; transition: background-color 0.15s ease; + color: var(--bui-fg-primary); } .toast-close-button svg { @@ -218,22 +217,14 @@ } .toast-close-button:hover { - background-color: rgba(255, 255, 255, 0.1); -} - -[data-theme-mode='light'] .toast-close-button:hover { - background-color: rgba(0, 0, 0, 0.05); + background-color: var(--bui-bg-neutral-on-surface-1-hover); } .toast-close-button:focus-visible { - outline: 2px solid #6366f1; + outline: 2px solid var(--bui-border-focus); outline-offset: 1px; } .toast-close-button:active { - background-color: rgba(255, 255, 255, 0.15); -} - -[data-theme-mode='light'] .toast-close-button:active { - background-color: rgba(0, 0, 0, 0.1); + background-color: var(--bui-bg-neutral-on-surface-1-pressed); } diff --git a/plugins/app/src/components/Toast/Toast.stories.tsx b/plugins/app/src/components/Toast/Toast.stories.tsx index e74677e188..14a3dab126 100644 --- a/plugins/app/src/components/Toast/Toast.stories.tsx +++ b/plugins/app/src/components/Toast/Toast.stories.tsx @@ -35,10 +35,12 @@ const randomToasts = [ { title: 'Error', status: 'danger' as const }, { title: 'New notification', status: 'info' as const }, { title: 'Warning', status: 'warning' as const }, + { title: 'Task completed', status: 'neutral' as const }, // Title only - medium { title: 'Changes saved successfully', status: 'success' as const }, { title: 'Connection restored', status: 'info' as const }, { title: 'Action could not be completed', status: 'danger' as const }, + { title: 'Background sync in progress', status: 'neutral' as const }, // Title + short description { title: 'Files uploaded', @@ -60,6 +62,11 @@ const randomToasts = [ description: '90% used.', status: 'warning' as const, }, + { + title: 'Clipboard updated', + description: 'Text copied.', + status: 'neutral' as const, + }, // Title + medium description { title: 'Deployment complete', @@ -78,6 +85,11 @@ const randomToasts = [ description: 'You do not have access to perform this action.', status: 'danger' as const, }, + { + title: 'Preferences updated', + description: 'Your display settings have been saved to your profile.', + status: 'neutral' as const, + }, // Title + long description { title: 'Sync completed', @@ -106,6 +118,10 @@ const randomToasts = [ title: 'Multiple users are currently editing this document', status: 'info' as const, }, + { + title: 'Your workspace has been switched to the new project', + status: 'neutral' as const, + }, ]; export const Default = meta.story({ @@ -140,6 +156,17 @@ export const StatusVariants = meta.story({ <> + @@ -426,9 +452,9 @@ export const QueueManagement = meta.story({ toastQueue.add({ title: `Toast #${i}`, description: `This is toast number ${i}.`, - status: ['info', 'success', 'warning', 'danger'][ - (i - 1) % 4 - ] as 'info' | 'success' | 'warning' | 'danger', + status: ['neutral', 'info', 'success', 'warning', 'danger'][ + (i - 1) % 5 + ] as 'neutral' | 'info' | 'success' | 'warning' | 'danger', }); }, i * 200); } @@ -463,8 +489,21 @@ export const AlertApiIntegration = meta.story({ info → info (blue) warning → warning (orange) error → danger (red) + + (ToastApi also supports neutral - no icon, primary color) + +