From abd0a5ad52fa6ee55c679fc212a3640d4457c6be Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 11 Feb 2026 22:02:28 +0100 Subject: [PATCH] frontend-plugin-api: migration to IconElement + API reports Signed-off-by: Patrik Oldsberg --- packages/app-example-plugin/report.api.md | 6 +- .../IconsApi/DefaultIconsApi.test.ts | 122 ++++++++++++++++++ .../IconsApi/DefaultIconsApi.ts | 29 ++++- .../src/wiring/InternalFrontendPlugin.ts | 4 +- packages/frontend-plugin-api/report.api.md | 47 +++---- .../src/blueprints/PageBlueprint.tsx | 4 +- .../src/components/PageLayout.tsx | 8 +- .../src/wiring/coreExtensionData.ts | 4 +- .../src/wiring/createFrontendPlugin.ts | 8 +- plugins/api-docs/report-alpha.api.md | 5 +- plugins/app-react/report.api.md | 7 +- .../src/blueprints/IconBundleBlueprint.ts | 10 +- plugins/app-visualizer/report.api.md | 9 +- plugins/app-visualizer/src/plugin.tsx | 2 +- plugins/app/report.api.md | 3 +- plugins/app/src/extensions/components.tsx | 4 +- plugins/auth/report.api.md | 6 +- plugins/catalog-graph/report-alpha.api.md | 6 +- plugins/catalog-import/report-alpha.api.md | 6 +- .../report-alpha.api.md | 5 +- plugins/catalog/report-alpha.api.md | 9 +- plugins/devtools/report-alpha.api.md | 5 +- plugins/home/report-alpha.api.md | 5 +- plugins/kubernetes/report-alpha.api.md | 6 +- plugins/mui-to-bui/report.api.md | 6 +- plugins/notifications/report-alpha.api.md | 6 +- plugins/scaffolder/report-alpha.api.md | 5 +- plugins/search/report-alpha.api.md | 9 +- plugins/techdocs/report-alpha.api.md | 9 +- plugins/user-settings/report-alpha.api.md | 5 +- 30 files changed, 260 insertions(+), 100 deletions(-) create mode 100644 packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts diff --git a/packages/app-example-plugin/report.api.md b/packages/app-example-plugin/report.api.md index 6bc2228170..5fd5f36559 100644 --- a/packages/app-example-plugin/report.api.md +++ b/packages/app-example-plugin/report.api.md @@ -7,7 +7,7 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSX as JSX_3 } from 'react/jsx-runtime'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -48,7 +48,7 @@ const examplePlugin: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -83,7 +83,7 @@ const examplePlugin: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts new file mode 100644 index 0000000000..e9bbfc328e --- /dev/null +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2026 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 { createElement, type ReactNode } from 'react'; +import { DefaultIconsApi } from './DefaultIconsApi'; + +describe('DefaultIconsApi', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should return undefined for unknown keys', () => { + const api = new DefaultIconsApi({}); + expect(api.getIcon('missing')).toBeUndefined(); + }); + + it('should list all registered icon keys', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + const api = new DefaultIconsApi({ + a: createElement('span'), + b: () => createElement('span'), + c: null, + }); + expect(api.listIconKeys()).toEqual(['a', 'b', 'c']); + }); + + it('should support IconElement values and wrap them in a component', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const element = createElement('span', null, 'test-icon'); + const api = new DefaultIconsApi({ myIcon: element }); + + const Icon = api.getIcon('myIcon'); + expect(Icon).toBeDefined(); + expect(typeof Icon).toBe('function'); + expect((Icon! as (props: object) => ReactNode)({})).toBe(element); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('should support null IconElement values', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const api = new DefaultIconsApi({ empty: null }); + + const Icon = api.getIcon('empty'); + expect(Icon).toBeDefined(); + expect(typeof Icon).toBe('function'); + expect((Icon! as (props: object) => ReactNode)({})).toBeNull(); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('should support IconComponent values and return them directly', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const MyIcon = () => createElement('span', null, 'component-icon'); + const api = new DefaultIconsApi({ myIcon: MyIcon }); + + expect(api.getIcon('myIcon')).toBe(MyIcon); + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('myIcon')); + }); + + it('should log a single warning listing all IconComponent keys', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const A = () => createElement('span'); + const B = () => createElement('span'); + + const api = new DefaultIconsApi({ + a: A, + elem: createElement('span'), + b: B, + empty: null, + }); + expect(api.getIcon('a')).toBe(A); + expect(api.getIcon('b')).toBe(B); + expect(api.getIcon('elem')).toEqual(expect.any(Function)); + expect(api.getIcon('empty')).toEqual(expect.any(Function)); + + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(expect.stringMatching(/a, b$/)); + }); + + it('should handle a mix of IconComponent and IconElement values', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const element = createElement('span', null, 'elem'); + const Component = () => createElement('span', null, 'comp'); + + const api = new DefaultIconsApi({ + elem: element, + comp: Component, + empty: null, + }); + + // Element - wrapped in a component, returned via getIcon + const ElemIcon = api.getIcon('elem'); + expect(ElemIcon).toBeDefined(); + expect((ElemIcon! as (props: object) => ReactNode)({})).toBe(element); + + // Component - returned directly + expect(api.getIcon('comp')).toBe(Component); + + // Null element - wrapped in a component + const EmptyIcon = api.getIcon('empty'); + expect(EmptyIcon).toBeDefined(); + expect((EmptyIcon! as (props: object) => ReactNode)({})).toBeNull(); + + // Single warning mentioning only the component key + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('comp')); + expect(warnSpy).toHaveBeenCalledWith(expect.not.stringContaining('elem')); + }); +}); diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts index 53a7fa6421..43fef68cea 100644 --- a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { IconComponent, IconsApi } from '@backstage/frontend-plugin-api'; +import { + IconComponent, + IconElement, + IconsApi, +} from '@backstage/frontend-plugin-api'; /** * Implementation for the {@link IconsApi} @@ -24,8 +28,27 @@ import { IconComponent, IconsApi } from '@backstage/frontend-plugin-api'; export class DefaultIconsApi implements IconsApi { #icons: Map; - constructor(icons: { [key in string]: IconComponent }) { - this.#icons = new Map(Object.entries(icons)); + constructor(icons: { [key in string]: IconComponent | IconElement }) { + const deprecatedKeys: string[] = []; + + this.#icons = new Map( + Object.entries(icons).map(([key, icon]) => { + if (typeof icon === 'function') { + deprecatedKeys.push(key); + return [key, icon]; + } + return [key, () => icon]; + }), + ); + + if (deprecatedKeys.length > 0) { + const keys = deprecatedKeys.join(', '); + // eslint-disable-next-line no-console + console.warn( + `The following icons were registered as IconComponent, which is deprecated. ` + + `Use IconElement instead by passing rather than MyIcon: ${keys}`, + ); + } } getIcon(key: string): IconComponent | undefined { diff --git a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts index 923a6f1e5a..4564be2bb1 100644 --- a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts +++ b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts @@ -17,7 +17,7 @@ import { Extension, FeatureFlagConfig, - IconComponent, + IconElement, OverridableFrontendPlugin, } from '@backstage/frontend-plugin-api'; import { JsonObject } from '@backstage/types'; @@ -28,7 +28,7 @@ export const OpaqueFrontendPlugin = OpaqueType.create<{ versions: { readonly version: 'v1'; readonly title?: string; - readonly icon?: IconComponent; + readonly icon?: IconElement; readonly extensions: Extension[]; readonly featureFlags: FeatureFlagConfig[]; readonly infoOptions?: { diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 9d7856b98d..1757994205 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -16,8 +16,8 @@ import { ExtensionDataRef as ExtensionDataRef_2 } from '@backstage/frontend-plug import { ExtensionInput as ExtensionInput_2 } from '@backstage/frontend-plugin-api'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; -import { JSX as JSX_2 } from 'react/jsx-runtime'; -import { JSX as JSX_3 } from 'react'; +import { JSX as JSX_2 } from 'react'; +import { JSX as JSX_3 } from 'react/jsx-runtime'; import { Observable } from '@backstage/types'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; @@ -52,7 +52,7 @@ export const analyticsApiRef: ApiRef; export const AnalyticsContext: (options: { attributes: Partial; children: ReactNode; -}) => JSX_2.Element; +}) => JSX_3.Element; // @public export interface AnalyticsContextValue { @@ -263,7 +263,7 @@ export const AppRootElementBlueprint: ExtensionBlueprint_2<{ params: { element: JSX.Element; }; - output: ExtensionDataRef_2; + output: ExtensionDataRef_2; inputs: {}; config: {}; configInput: {}; @@ -389,9 +389,9 @@ export interface ConfigurableExtensionDataRef< // @public (undocumented) export const coreExtensionData: { title: ConfigurableExtensionDataRef_2; - icon: ConfigurableExtensionDataRef_2; + icon: ConfigurableExtensionDataRef_2; reactElement: ConfigurableExtensionDataRef_2< - JSX_3.Element, + JSX_2.Element, 'core.reactElement', {} >; @@ -1103,7 +1103,7 @@ export type ExtensionBlueprintParams = { }; // @public (undocumented) -export function ExtensionBoundary(props: ExtensionBoundaryProps): JSX_2.Element; +export function ExtensionBoundary(props: ExtensionBoundaryProps): JSX_3.Element; // @public (undocumented) export namespace ExtensionBoundary { @@ -1369,7 +1369,7 @@ export interface FrontendPlugin< readonly $$type: '@backstage/FrontendPlugin'; // (undocumented) readonly externalRoutes: TExternalRoutes; - readonly icon?: IconComponent; + readonly icon?: IconElement; // @deprecated readonly id: string; info(): Promise; @@ -1430,18 +1430,21 @@ export const HeaderActionBlueprint: ExtensionBlueprint_2<{ params: { loader: () => Promise; }; - output: ExtensionDataRef_2; + output: ExtensionDataRef_2; inputs: {}; config: {}; configInput: {}; dataRefs: never; }>; -// @public +// @public @deprecated export type IconComponent = ComponentType<{ fontSize?: 'medium' | 'large' | 'small' | 'inherit'; }>; +// @public +export type IconElement = JSX_2.Element | null; + // @public export interface IconsApi { // (undocumented) @@ -1716,9 +1719,9 @@ export interface OverridableFrontendPlugin< ): OverridableExtensionDefinition; // (undocumented) withOverrides(options: { - extensions: Array; + extensions?: Array; title?: string; - icon?: IconComponent; + icon?: IconElement; info?: FrontendPluginInfoOptions; }): OverridableFrontendPlugin; } @@ -1730,7 +1733,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{ defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; @@ -1743,7 +1746,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{ optional: true; } > - | ExtensionDataRef_2 + | ExtensionDataRef_2 | ExtensionDataRef_2< string, 'core.title', @@ -1752,7 +1755,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{ } > | ExtensionDataRef_2< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -1760,7 +1763,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{ >; inputs: { pages: ExtensionInput_2< - | ConfigurableExtensionDataRef_2 + | ConfigurableExtensionDataRef_2 | ConfigurableExtensionDataRef_2 | ConfigurableExtensionDataRef_2< RouteRef, @@ -1805,7 +1808,7 @@ export interface PageLayoutProps { // (undocumented) children?: ReactNode; // (undocumented) - icon?: IconComponent; + icon?: IconElement; // (undocumented) tabs?: PageTab[]; // (undocumented) @@ -1848,7 +1851,7 @@ export interface PluginOptions< externalRoutes?: TExternalRoutes; // (undocumented) featureFlags?: FeatureFlagConfig[]; - icon?: IconComponent; + icon?: IconElement; // (undocumented) info?: FrontendPluginInfoOptions; // (undocumented) @@ -2009,7 +2012,7 @@ export const SubPageBlueprint: ExtensionBlueprint_2<{ optional: true; } > - | ExtensionDataRef_2 + | ExtensionDataRef_2 | ExtensionDataRef_2; inputs: {}; config: { @@ -2105,9 +2108,9 @@ export type TranslationFunction< NestedMessageKeys, PluralKeys, IMessages, - string | JSX_3.Element + string | JSX_2.Element > - ): JSX_3.Element; + ): JSX_2.Element; } : never; @@ -2283,7 +2286,7 @@ export function withApis( ): ( WrappedComponent: ComponentType, ) => { - (props: PropsWithChildren>): JSX_2.Element; + (props: PropsWithChildren>): JSX_3.Element; displayName: string; }; ``` diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index c3e4921af7..73548f7916 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -15,7 +15,7 @@ */ import { Routes, Route, Navigate } from 'react-router-dom'; -import { IconComponent } from '../icons/types'; +import { IconElement } from '../icons/types'; import { RouteRef } from '../routing'; import { coreExtensionData, @@ -61,7 +61,7 @@ export const PageBlueprint = createExtensionBlueprint({ defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }, diff --git a/packages/frontend-plugin-api/src/components/PageLayout.tsx b/packages/frontend-plugin-api/src/components/PageLayout.tsx index f5b2082c90..fa7ae80a11 100644 --- a/packages/frontend-plugin-api/src/components/PageLayout.tsx +++ b/packages/frontend-plugin-api/src/components/PageLayout.tsx @@ -15,7 +15,7 @@ */ import { ReactNode } from 'react'; -import { IconComponent } from '../icons/types'; +import { IconElement } from '../icons/types'; import { createSwappableComponent } from './createSwappableComponent'; /** @@ -35,7 +35,7 @@ export interface PageTab { */ export interface PageLayoutProps { title?: string; - icon?: IconComponent; + icon?: IconElement; tabs?: PageTab[]; children?: ReactNode; } @@ -44,7 +44,7 @@ export interface PageLayoutProps { * Default implementation of PageLayout using plain HTML elements */ function DefaultPageLayout(props: PageLayoutProps): JSX.Element { - const { title, icon: Icon, tabs, children } = props; + const { title, icon, tabs, children } = props; return (
- {Icon && } + {icon} {title}
)} diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts index 6d0ca3c471..af2de8c4c2 100644 --- a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts +++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts @@ -15,14 +15,14 @@ */ import { JSX } from 'react'; -import { IconComponent } from '../icons/types'; +import { IconElement } from '../icons/types'; import { RouteRef } from '../routing/RouteRef'; import { createExtensionDataRef } from './createExtensionDataRef'; /** @public */ export const coreExtensionData = { title: createExtensionDataRef().with({ id: 'core.title' }), - icon: createExtensionDataRef().with({ id: 'core.icon' }), + icon: createExtensionDataRef().with({ id: 'core.icon' }), reactElement: createExtensionDataRef().with({ id: 'core.reactElement', }), diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 2a80824f70..08329b6fd4 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -29,7 +29,7 @@ import { import { FeatureFlagConfig } from './types'; import { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap'; import { JsonObject } from '@backstage/types'; -import { IconComponent } from '../icons/types'; +import { IconElement } from '../icons/types'; import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; import { ID_PATTERN } from './constants'; @@ -123,7 +123,7 @@ export interface OverridableFrontendPlugin< /** * Overrides the display icon of the plugin. */ - icon?: IconComponent; + icon?: IconElement; /** * Overrides the original info loaders of the plugin one by one. @@ -160,7 +160,7 @@ export interface FrontendPlugin< /** * The display icon of the plugin, used in page headers and navigation. */ - readonly icon?: IconComponent; + readonly icon?: IconElement; readonly routes: TRoutes; readonly externalRoutes: TExternalRoutes; @@ -186,7 +186,7 @@ export interface PluginOptions< /** * The display icon of the plugin, used in page headers and navigation. */ - icon?: IconComponent; + icon?: IconElement; routes?: TRoutes; externalRoutes?: TExternalRoutes; extensions?: TExtensions; diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md index 0273ad21da..391a0300e3 100644 --- a/plugins/api-docs/report-alpha.api.md +++ b/plugins/api-docs/report-alpha.api.md @@ -16,6 +16,7 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSXElementConstructor } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -521,7 +522,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -558,7 +559,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/app-react/report.api.md b/plugins/app-react/report.api.md index 78f4bd3bfb..c29ef32264 100644 --- a/plugins/app-react/report.api.md +++ b/plugins/app-react/report.api.md @@ -10,6 +10,7 @@ import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IdentityApi } from '@backstage/frontend-plugin-api'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -45,11 +46,11 @@ export const AppRootWrapperBlueprint: ExtensionBlueprint<{ export const IconBundleBlueprint: ExtensionBlueprint<{ kind: 'icon-bundle'; params: { - icons: { [key in string]: IconComponent }; + icons: { [key in string]: IconComponent | IconElement }; }; output: ExtensionDataRef< { - [x: string]: IconComponent; + [x: string]: IconComponent | IconElement; }, 'core.icons', {} @@ -60,7 +61,7 @@ export const IconBundleBlueprint: ExtensionBlueprint<{ dataRefs: { icons: ConfigurableExtensionDataRef< { - [x: string]: IconComponent; + [x: string]: IconComponent | IconElement; }, 'core.icons', {} diff --git a/plugins/app-react/src/blueprints/IconBundleBlueprint.ts b/plugins/app-react/src/blueprints/IconBundleBlueprint.ts index ce340c2aeb..99c6abcd72 100644 --- a/plugins/app-react/src/blueprints/IconBundleBlueprint.ts +++ b/plugins/app-react/src/blueprints/IconBundleBlueprint.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconComponent, IconElement } from '@backstage/frontend-plugin-api'; import { createExtensionBlueprint, createExtensionDataRef, } from '@backstage/frontend-plugin-api'; const iconsDataRef = createExtensionDataRef<{ - [key in string]: IconComponent; + [key in string]: IconComponent | IconElement; }>().with({ id: 'core.icons' }); /** @@ -33,9 +33,9 @@ export const IconBundleBlueprint = createExtensionBlueprint({ kind: 'icon-bundle', attachTo: { id: 'api:app/icons', input: 'icons' }, output: [iconsDataRef], - factory: (params: { icons: { [key in string]: IconComponent } }) => [ - iconsDataRef(params.icons), - ], + factory: (params: { + icons: { [key in string]: IconComponent | IconElement }; + }) => [iconsDataRef(params.icons)], dataRefs: { icons: iconsDataRef, }, diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index 5608546930..53ba5ace7c 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -8,6 +8,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -68,7 +69,7 @@ const visualizerPlugin: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -103,7 +104,7 @@ const visualizerPlugin: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; @@ -137,7 +138,7 @@ const visualizerPlugin: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -172,7 +173,7 @@ const visualizerPlugin: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index 1ffb17bba1..cdd429287a 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -125,7 +125,7 @@ export const appVisualizerNavItem = NavItemBlueprint.make({ export const visualizerPlugin = createFrontendPlugin({ pluginId: 'app-visualizer', title: 'App Visualizer', - icon: () => , + icon: , info: { packageJson: () => import('../package.json') }, extensions: [ appVisualizerPage, diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index 6a85a26f8c..fc3d467007 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -14,6 +14,7 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { NavContentComponent } from '@backstage/plugin-app-react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -476,7 +477,7 @@ const appPlugin: OverridableFrontendPlugin< icons: ExtensionInput< ConfigurableExtensionDataRef< { - [x: string]: IconComponent; + [x: string]: IconComponent | IconElement; }, 'core.icons', {} diff --git a/plugins/app/src/extensions/components.tsx b/plugins/app/src/extensions/components.tsx index 79f371d7d2..68df086935 100644 --- a/plugins/app/src/extensions/components.tsx +++ b/plugins/app/src/extensions/components.tsx @@ -73,13 +73,13 @@ export const PageLayout = SwappableComponentBlueprint.make({ define({ component: SwappablePageLayout, loader: () => (props: PageLayoutProps) => { - const { title, icon: Icon, tabs, children } = props; + const { title, icon, tabs, children } = props; return ( -
} tabs={tabs} /> +
{children} diff --git a/plugins/auth/report.api.md b/plugins/auth/report.api.md index 0671136f79..f3073415f5 100644 --- a/plugins/auth/report.api.md +++ b/plugins/auth/report.api.md @@ -7,7 +7,7 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -49,7 +49,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -84,7 +84,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/catalog-graph/report-alpha.api.md b/plugins/catalog-graph/report-alpha.api.md index 74415759ca..37c9f3f2df 100644 --- a/plugins/catalog-graph/report-alpha.api.md +++ b/plugins/catalog-graph/report-alpha.api.md @@ -14,7 +14,7 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -197,7 +197,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -234,7 +234,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index f4b58a335c..8e5aa0552e 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -10,7 +10,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -142,7 +142,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -177,7 +177,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 2a988eeee2..872248363a 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -12,6 +12,7 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -90,7 +91,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -125,7 +126,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index aa29c6cc1e..f8e289d10e 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -18,6 +18,7 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { JSXElementConstructor } from 'react'; @@ -1030,7 +1031,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -1075,7 +1076,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; @@ -1127,7 +1128,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -1249,7 +1250,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index 5a90b56cab..60302485b3 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -11,6 +11,7 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -87,7 +88,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -141,7 +142,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md index ef291b0972..0d2fc51884 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -14,6 +14,7 @@ import { HomePageLayoutProps } from '@backstage/plugin-home-react/alpha'; import { HomePageWidgetBlueprintParams } from '@backstage/plugin-home-react/alpha'; import { HomePageWidgetData } from '@backstage/plugin-home-react/alpha'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -128,7 +129,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -189,7 +190,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index b4dc80d439..b136f226a5 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -13,7 +13,7 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSXElementConstructor } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -189,7 +189,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -224,7 +224,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/mui-to-bui/report.api.md b/plugins/mui-to-bui/report.api.md index 2ba75b2777..0d95f91f26 100644 --- a/plugins/mui-to-bui/report.api.md +++ b/plugins/mui-to-bui/report.api.md @@ -8,7 +8,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSX as JSX_3 } from 'react/jsx-runtime'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -63,7 +63,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -98,7 +98,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index 40d43cff7c..7007249db6 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -10,7 +10,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -69,7 +69,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -104,7 +104,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 05e5cb05ec..80ee1077d1 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -20,6 +20,7 @@ import { FormField } from '@backstage/plugin-scaffolder-react/alpha'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; @@ -218,7 +219,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -267,7 +268,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index d9364b1fe7..f7d51fafa4 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -11,6 +11,7 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -93,7 +94,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -176,7 +177,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; @@ -255,7 +256,7 @@ export const searchPage: OverridableExtensionDefinition<{ } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -338,7 +339,7 @@ export const searchPage: OverridableExtensionDefinition<{ defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index 7f9dd28973..9b4646bf78 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -14,6 +14,7 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { JSXElementConstructor } from 'react'; @@ -308,7 +309,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -343,7 +344,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; @@ -379,7 +380,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -428,7 +429,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef_2; }; diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md index b4b8a11825..b2fae45653 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -8,6 +8,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -70,7 +71,7 @@ const _default: OverridableFrontendPlugin< } > | ExtensionDataRef< - IconComponent, + IconElement, 'core.icon', { optional: true; @@ -115,7 +116,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; - icon?: IconComponent; + icon?: IconElement; loader?: () => Promise; routeRef?: RouteRef; };