frontend-plugin-api: make header actions a element[]

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-17 12:35:20 +01:00
parent c37e2bf6d4
commit b7ae1243e0
22 changed files with 47 additions and 46 deletions
+1 -1
View File
@@ -91,7 +91,7 @@ const examplePlugin: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};
+5 -5
View File
@@ -1722,7 +1722,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};
@@ -1735,7 +1735,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{
optional: true;
}
>
| ExtensionDataRef_2<JSX_2, 'core.reactElement', {}>
| ExtensionDataRef_2<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef_2<
string,
'core.title',
@@ -1752,7 +1752,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{
>;
inputs: {
pages: ExtensionInput_2<
| ConfigurableExtensionDataRef_2<JSX_2, 'core.reactElement', {}>
| ConfigurableExtensionDataRef_2<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef_2<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef_2<
RouteRef<AnyRouteRefParams_2>,
@@ -1804,7 +1804,7 @@ export interface PageLayoutProps {
// (undocumented)
children?: ReactNode;
// (undocumented)
headerActions?: ReactNode;
headerActions?: Array<JSX.Element | null>;
// (undocumented)
icon?: IconElement;
// (undocumented)
@@ -1853,7 +1853,7 @@ export const PluginHeaderActionBlueprint: ExtensionBlueprint_2<{
// @public
export type PluginHeaderActionsApi = {
getPluginHeaderActions(pluginId: string): ReactNode[];
getPluginHeaderActions(pluginId: string): Array<JSX_2.Element | null>;
};
// @public
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ReactNode } from 'react';
import { JSX } from 'react';
import { createApiRef } from '../system';
/**
@@ -32,7 +32,7 @@ export type PluginHeaderActionsApi = {
/**
* Returns the header actions for a given plugin.
*/
getPluginHeaderActions(pluginId: string): ReactNode[];
getPluginHeaderActions(pluginId: string): Array<JSX.Element | null>;
};
/**
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ReactNode } from 'react';
import { JSX } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { IconElement } from '../icons/types';
import { RouteRef } from '../routing';
@@ -27,15 +27,6 @@ import { ExtensionBoundary, PageLayout, PageTab } from '../components';
import { useApi } from '../apis/system';
import { pluginHeaderActionsApiRef } from '../apis/definitions/PluginHeaderActionsApi';
function usePluginHeaderActions(pluginId: string): ReactNode {
const pluginHeaderActionsApi = useApi(pluginHeaderActionsApiRef);
const actions = pluginHeaderActionsApi.getPluginHeaderActions(pluginId);
if (actions.length === 0) {
return undefined;
}
return <>{actions}</>;
}
/**
* Creates extensions that are routable React page components.
*
@@ -93,7 +84,9 @@ export const PageBlueprint = createExtensionBlueprint({
if (params.loader) {
const loader = params.loader;
const PageContent = () => {
const headerActions = usePluginHeaderActions(pluginId);
const headerActionsApi = useApi(pluginHeaderActionsApiRef);
const headerActions = headerActionsApi.getPluginHeaderActions(pluginId);
return (
<PageLayout
title={title}
@@ -123,7 +116,9 @@ export const PageBlueprint = createExtensionBlueprint({
const PageContent = () => {
const firstPagePath = inputs.pages[0]?.get(coreExtensionData.routePath);
const headerActions = usePluginHeaderActions(pluginId);
const headerActionsApi = useApi(pluginHeaderActionsApiRef);
const headerActions = headerActionsApi.getPluginHeaderActions(pluginId);
return (
<PageLayout
@@ -154,7 +149,8 @@ export const PageBlueprint = createExtensionBlueprint({
yield coreExtensionData.reactElement(<PageContent />);
} else {
const PageContent = () => {
const headerActions = usePluginHeaderActions(pluginId);
const headerActionsApi = useApi(pluginHeaderActionsApiRef);
const headerActions = headerActionsApi.getPluginHeaderActions(pluginId);
return (
<PageLayout title={title} icon={icon} headerActions={headerActions} />
);
@@ -38,7 +38,7 @@ export interface PageLayoutProps {
title?: string;
icon?: IconElement;
noHeader?: boolean;
headerActions?: ReactNode;
headerActions?: Array<JSX.Element | null>;
tabs?: PageTab[];
children?: ReactNode;
}
+1 -1
View File
@@ -567,7 +567,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -113,7 +113,7 @@ const visualizerPlugin: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};
@@ -14,9 +14,12 @@
* limitations under the License.
*/
import { ReactNode } from 'react';
import { JSX } from 'react';
import { type PluginHeaderActionsApi } from '@backstage/frontend-plugin-api';
// Stable reference
const EMPTY_ACTIONS = new Array<JSX.Element | null>();
type ActionInput = {
element: JSX.Element;
pluginId: string;
@@ -28,16 +31,18 @@ type ActionInput = {
* @internal
*/
export class DefaultPluginHeaderActionsApi implements PluginHeaderActionsApi {
constructor(private readonly actionsByPlugin: Map<string, ReactNode[]>) {}
constructor(
private readonly actionsByPlugin: Map<string, Array<JSX.Element | null>>,
) {}
getPluginHeaderActions(pluginId: string): ReactNode[] {
return this.actionsByPlugin.get(pluginId) ?? [];
getPluginHeaderActions(pluginId: string): Array<JSX.Element | null> {
return this.actionsByPlugin.get(pluginId) ?? EMPTY_ACTIONS;
}
static fromActions(
actions: Array<ActionInput>,
): DefaultPluginHeaderActionsApi {
const actionsByPlugin = new Map<string, ReactNode[]>();
const actionsByPlugin = new Map<string, Array<JSX.Element | null>>();
for (const action of actions) {
let pluginActions = actionsByPlugin.get(action.pluginId);
+1 -1
View File
@@ -92,7 +92,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};
+1 -1
View File
@@ -242,7 +242,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -185,7 +185,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
@@ -134,7 +134,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+2 -2
View File
@@ -1084,7 +1084,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
@@ -1266,7 +1266,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -150,7 +150,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -198,7 +198,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};
+1 -1
View File
@@ -232,7 +232,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -106,7 +106,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -112,7 +112,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -276,7 +276,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+2 -2
View File
@@ -185,7 +185,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};
@@ -355,7 +355,7 @@ export const searchPage: OverridableExtensionDefinition<{
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};
+2 -2
View File
@@ -352,7 +352,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
@@ -445,7 +445,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
+1 -1
View File
@@ -124,7 +124,7 @@ const _default: OverridableFrontendPlugin<
path: string;
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX.Element>;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
noHeader?: boolean;
};