From e21dd5edc4351c708e83656ec17eb9d75ebdd274 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 30 Mar 2026 11:49:34 +0100 Subject: [PATCH 1/5] fix(storybook): register appThemeApiRef in Storybook API providers Add AppThemeSelector as an implementation for appThemeApiRef in the shared Storybook API registry. Without this, any story that renders a ToastContainer fails with NotImplementedError for core.apptheme. Signed-off-by: Charles de Dreuille Made-with: Cursor --- .storybook/support/apis.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.storybook/support/apis.js b/.storybook/support/apis.js index 905c5f2dc8..38bb2817e2 100644 --- a/.storybook/support/apis.js +++ b/.storybook/support/apis.js @@ -1,5 +1,6 @@ import { AlertApiForwarder, + AppThemeSelector, ErrorAlerter, ErrorApiForwarder, GithubAuth, @@ -13,6 +14,7 @@ import { import { alertApiRef, + appThemeApiRef, errorApiRef, githubAuthApiRef, gitlabAuthApiRef, @@ -24,10 +26,16 @@ import { featureFlagsApiRef, } from '@backstage/core-plugin-api'; +import { themes } from '@backstage/theme'; + import { translationApiRef } from '@backstage/core-plugin-api/alpha'; import { MockTranslationApi } from '@backstage/test-utils/alpha'; const configApi = new ConfigReader({}); +const appThemeApi = AppThemeSelector.createWithStorage([ + { id: 'light', title: 'Light', variant: 'light', theme: themes.light }, + { id: 'dark', title: 'Dark', variant: 'dark', theme: themes.dark }, +]); const featureFlagsApi = new LocalStorageFeatureFlags(); const alertApi = new AlertApiForwarder(); const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder()); @@ -62,6 +70,7 @@ const translationApi = MockTranslationApi.create(); export const apis = [ [configApiRef, configApi], + [appThemeApiRef, appThemeApi], [featureFlagsApiRef, featureFlagsApi], [alertApiRef, alertApi], [errorApiRef, errorApi], From b651807e562485ac0d09b8540a4ad6684a5e3aae Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 30 Mar 2026 11:55:27 +0100 Subject: [PATCH 2/5] fix(storybook): replace require() with ESM import in Toast stories The RealAlertApiStory component used require('@backstage/core-plugin-api') inside the function body. Vite bundles Storybook as ESM, where require is not available at runtime, causing a ReferenceError. Replace with a standard top-level import. Signed-off-by: Charles de Dreuille Made-with: Cursor --- plugins/app/src/components/Toast/Toast.stories.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/app/src/components/Toast/Toast.stories.tsx b/plugins/app/src/components/Toast/Toast.stories.tsx index f008bc2550..6cd895a5f8 100644 --- a/plugins/app/src/components/Toast/Toast.stories.tsx +++ b/plugins/app/src/components/Toast/Toast.stories.tsx @@ -19,6 +19,7 @@ import preview from '../../../../../.storybook/preview'; import { Button, Flex, Text } from '../../../../../packages/ui/src'; /* eslint-enable @backstage/no-relative-monorepo-imports */ import { ToastQueue } from '@react-stately/toast'; +import { useApi, alertApiRef } from '@backstage/core-plugin-api'; import { ToastContainer } from './index'; import type { ToastApiMessageContent } from './types'; import { MemoryRouter } from 'react-router-dom'; @@ -567,8 +568,6 @@ export const AlertApiIntegration = meta.story({ * Backstage app where the app plugin's elements.tsx is used. */ function RealAlertApiStory() { - // eslint-disable-next-line @backstage/no-relative-monorepo-imports - const { useApi, alertApiRef } = require('@backstage/core-plugin-api'); const alertApi = useApi(alertApiRef); return ( From cf2de9c8b83e77fca2335d4d39eb512ed57782a0 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 30 Mar 2026 11:56:42 +0100 Subject: [PATCH 3/5] Update Toast.stories.tsx Signed-off-by: Charles de Dreuille --- .../src/components/Toast/Toast.stories.tsx | 107 ------------------ 1 file changed, 107 deletions(-) diff --git a/plugins/app/src/components/Toast/Toast.stories.tsx b/plugins/app/src/components/Toast/Toast.stories.tsx index 6cd895a5f8..482bd0e5d8 100644 --- a/plugins/app/src/components/Toast/Toast.stories.tsx +++ b/plugins/app/src/components/Toast/Toast.stories.tsx @@ -558,111 +558,4 @@ export const AlertApiIntegration = meta.story({ }, }); -/** - * This story tests the real AlertApi integration. - * It uses the alertApi from the TestApiProvider (set up in storybook preview) - * and shows how alerts posted via alertApi.post() appear. - * - * Note: The storybook preview.tsx renders AlertDisplay from core-components, - * which still uses Material UI. To test the new ToastDisplay, run the actual - * Backstage app where the app plugin's elements.tsx is used. - */ -function RealAlertApiStory() { - const alertApi = useApi(alertApiRef); - - return ( - - - - Real AlertApi Test - - - These buttons call alertApi.post() directly. Alerts appear in the OLD - AlertDisplay (top of screen) because Storybook uses core-components. - - - To test the NEW ToastDisplay, run: yarn start - - - - - - - - - - - - - - ); -} - -export const RealAlertApi = meta.story({ - name: 'Real AlertApi Test', - render: () => , -}); - export default meta; From 4dab043bd5dabae67641831ddbcb0b5805e48bb8 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 30 Mar 2026 12:05:12 +0100 Subject: [PATCH 4/5] Update Toast.stories.tsx Signed-off-by: Charles de Dreuille --- .../src/components/Toast/Toast.stories.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/plugins/app/src/components/Toast/Toast.stories.tsx b/plugins/app/src/components/Toast/Toast.stories.tsx index 482bd0e5d8..84d75aed0b 100644 --- a/plugins/app/src/components/Toast/Toast.stories.tsx +++ b/plugins/app/src/components/Toast/Toast.stories.tsx @@ -158,6 +158,35 @@ export const Default = meta.story({ ), }); +const previewQueue = new ToastQueue({ + maxVisibleToasts: 4, +}); +previewQueue.add({ + title: 'Changes saved successfully', + description: 'Your changes have been saved.', + status: 'success', +}); + +export const PreviewToast = meta.story({ + render: () => , +}); + +const timerQueue = new ToastQueue({ + maxVisibleToasts: 4, +}); +timerQueue.add( + { + title: 'Auto-dismissing in 5 seconds', + description: 'This toast will disappear automatically.', + status: 'info', + }, + { timeout: 5000 }, +); + +export const PreviewToastWithTimer = meta.story({ + render: () => , +}); + export const StatusVariants = meta.story({ render: () => ( <> From 3bd5a0c7b81401344a435d6c792eff73adb703a1 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 30 Mar 2026 13:13:23 +0100 Subject: [PATCH 5/5] Improve Toast setup Signed-off-by: Charles de Dreuille --- .storybook/main.ts | 2 +- .storybook/preview.tsx | 6 +++++- .storybook/support/apis.js | 2 +- plugins/app/src/components/Toast/Toast.stories.tsx | 3 +-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index a62960db85..865ae90848 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -13,7 +13,7 @@ const isChromatic = process.env.STORYBOOK_STORY_SET === 'chromatic'; // All stories for full development const allStories = isChromatic - ? ['packages/ui'] + ? ['packages/ui', 'plugins/app'] : [ 'packages/ui', 'packages/core-components', diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 022b6123e2..916c48951d 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -6,7 +6,7 @@ import { definePreview } from '@storybook/react-vite'; import React, { useEffect } from 'react'; import { TestApiProvider } from '@backstage/test-utils'; import { AlertDisplay } from '@backstage/core-components'; -import { apis } from './support/apis'; +import { apis, appThemeApi } from './support/apis'; import { useGlobals } from 'storybook/preview-api'; import { UnifiedThemeProvider, themes } from '@backstage/theme'; import { allModes } from './modes'; @@ -157,6 +157,10 @@ export default definePreview({ }; }, [selectedTheme, selectedThemeName]); + useEffect(() => { + appThemeApi.setActiveThemeId(selectedThemeMode); + }, [selectedThemeMode]); + document.body.style.backgroundColor = 'var(--bui-bg-app)'; document.body.style.padding = isFullscreen && selectedBackground !== 'app' ? '1rem' : ''; diff --git a/.storybook/support/apis.js b/.storybook/support/apis.js index 38bb2817e2..42085591c0 100644 --- a/.storybook/support/apis.js +++ b/.storybook/support/apis.js @@ -32,7 +32,7 @@ import { translationApiRef } from '@backstage/core-plugin-api/alpha'; import { MockTranslationApi } from '@backstage/test-utils/alpha'; const configApi = new ConfigReader({}); -const appThemeApi = AppThemeSelector.createWithStorage([ +export const appThemeApi = AppThemeSelector.createWithStorage([ { id: 'light', title: 'Light', variant: 'light', theme: themes.light }, { id: 'dark', title: 'Dark', variant: 'dark', theme: themes.dark }, ]); diff --git a/plugins/app/src/components/Toast/Toast.stories.tsx b/plugins/app/src/components/Toast/Toast.stories.tsx index 84d75aed0b..10eb05476d 100644 --- a/plugins/app/src/components/Toast/Toast.stories.tsx +++ b/plugins/app/src/components/Toast/Toast.stories.tsx @@ -19,7 +19,6 @@ import preview from '../../../../../.storybook/preview'; import { Button, Flex, Text } from '../../../../../packages/ui/src'; /* eslint-enable @backstage/no-relative-monorepo-imports */ import { ToastQueue } from '@react-stately/toast'; -import { useApi, alertApiRef } from '@backstage/core-plugin-api'; import { ToastContainer } from './index'; import type { ToastApiMessageContent } from './types'; import { MemoryRouter } from 'react-router-dom'; @@ -29,7 +28,7 @@ const toastQueue = new ToastQueue({ }); const meta = preview.meta({ - title: 'Plugins/App/Toast', + title: 'App/Toast', component: ToastContainer, parameters: { layout: 'centered',