feat(home): add new frontend system support

Migrates home plugin to support the new frontend system architecture by
introducing extension blueprints for composable homepage functionality.
Key changes:
- Add CustomHomepageWidgetBlueprint for creating installable homepage widgets
- Add CustomHomepageBlueprint for composing pages from widget extensions
- Introduce titleExtensionDataRef for NFS title handling
This attempts to bring  the home plugin up to par with other core plugins that have migrated
to the new frontend system
Signed-off-by: Adam Kunicki <kunickiaj@gmail.com>
This commit is contained in:
Adam Kunicki
2025-09-22 10:00:11 -07:00
parent 2218061089
commit 90956a61bd
22 changed files with 1085 additions and 60 deletions
+17
View File
@@ -13,4 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* React components and utilities for the home plugin's new frontend system.
*
* @remarks
* This package provides React components, blueprints, and utilities for building
* customizable home pages with the new Backstage frontend system.
*
* @packageDocumentation
*/
export { homeReactTranslationRef } from './translation';
export { titleExtensionDataRef } from './alpha/dataRefs';
export {
HomepageWidgetBlueprint,
widgetMetadataRef,
type HomepageWidgetBlueprintParams,
} from './alpha/blueprints/HomepageWidgetBlueprint';
export type { ComponentParts, CardLayout, CardSettings } from './extensions';
@@ -0,0 +1,128 @@
/*
* 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 { lazy, ReactElement } from 'react';
import { compatWrapper } from '@backstage/core-compat-api';
import {
coreExtensionData,
createExtensionBlueprint,
createExtensionDataRef,
ExtensionBoundary,
} from '@backstage/frontend-plugin-api';
import {
CardExtension,
CardExtensionProps,
CardLayout,
CardSettings,
ComponentParts,
} from '../../extensions';
/** @alpha */
export interface HomepageWidgetBlueprintParams {
/**
* Optional name for the widget. If not provided, the extension will use only its kind
* in the extension ID.
*/
name?: string;
/**
* Optional title displayed for the widget, used as the default card heading.
*/
title?: string;
/**
* Optional description shown in the widget catalog when adding new cards.
*/
description?: string;
/**
* Component parts rendered within the card.
*/
components: () => Promise<ComponentParts>;
/**
* Layout hints used by the customizable grid.
*/
layout?: CardLayout;
/**
* Schema used to configure widget settings.
*/
settings?: CardSettings;
/**
* Default props forwarded to the rendered widget component.
*/
componentProps?: Record<string, unknown>;
}
const DEFAULT_WIDGET_ATTACH_POINT = {
id: 'page:home',
input: 'widgets',
} as const;
/**
* Extension data ref for widget metadata.
* @alpha
*/
export const widgetMetadataRef = createExtensionDataRef<{
name?: string;
title?: string;
description?: string;
layout?: CardLayout;
settings?: CardSettings;
}>().with({ id: 'home.widget.metadata' });
/**
* Creates widgets that can be installed into the home page grid.
*
* @alpha
*/
export const HomepageWidgetBlueprint = createExtensionBlueprint({
kind: 'home-widget',
attachTo: DEFAULT_WIDGET_ATTACH_POINT,
output: [coreExtensionData.reactElement, widgetMetadataRef],
*factory(params: HomepageWidgetBlueprintParams, { node }) {
const isCustomizable = params.settings?.schema !== undefined;
const LazyCard = lazy(() =>
params.components().then(parts => ({
default: (props: CardExtensionProps<Record<string, unknown>>) => (
<CardExtension
{...props}
{...parts}
title={props.title || params.title}
isCustomizable={isCustomizable}
/>
),
})),
);
const Widget = (
props: CardExtensionProps<Record<string, unknown>>,
): ReactElement =>
compatWrapper(
<ExtensionBoundary node={node}>
<LazyCard {...props} />
</ExtensionBoundary>,
);
yield coreExtensionData.reactElement(
<Widget {...(params.componentProps ?? {})} />,
);
yield widgetMetadataRef({
name: params.name,
title: params.title,
description: params.description,
layout: params.layout,
settings: params.settings,
});
},
});
+26
View File
@@ -0,0 +1,26 @@
/*
* 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 { createExtensionDataRef } from '@backstage/frontend-plugin-api';
/**
* Title data supplied to the home page extension when composing via the new frontend system.
*
* @alpha
*/
export const titleExtensionDataRef = createExtensionDataRef<string>().with({
id: 'title',
});
+1 -1
View File
@@ -121,7 +121,7 @@ type CardExtensionComponentProps<T> = CardExtensionProps<T> &
overrideTitle?: string;
};
function CardExtension<T>(props: CardExtensionComponentProps<T>) {
export function CardExtension<T>(props: CardExtensionComponentProps<T>) {
const {
Renderer,
Content,
+3
View File
@@ -16,6 +16,9 @@
import { createTranslationRef } from '@backstage/frontend-plugin-api';
/**
* Translation reference for the home-react plugin.
* Contains localized text strings for home page components and settings modals.
*
* @alpha
*/
export const homeReactTranslationRef = createTranslationRef({