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
+1
View File
@@ -55,6 +55,7 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/core-compat-api": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
+87 -1
View File
@@ -3,9 +3,77 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionBlueprint } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { RJSFSchema } from '@rjsf/utils';
import { TranslationRef } from '@backstage/frontend-plugin-api';
import { UiSchema } from '@rjsf/utils';
// @public (undocumented)
export type CardLayout = {
width?: {
minColumns?: number;
maxColumns?: number;
defaultColumns?: number;
};
height?: {
minRows?: number;
maxRows?: number;
defaultRows?: number;
};
};
// @public (undocumented)
export type CardSettings = {
schema?: RJSFSchema;
uiSchema?: UiSchema;
};
// @public (undocumented)
export type ComponentParts = {
Content: (props?: any) => JSX.Element;
Actions?: () => JSX.Element;
Settings?: () => JSX.Element;
ContextProvider?: (props: any) => JSX.Element;
};
// @alpha
export const HomepageWidgetBlueprint: ExtensionBlueprint<{
kind: 'home-widget';
params: HomepageWidgetBlueprintParams;
output:
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef<
{
name?: string;
title?: string;
description?: string;
layout?: CardLayout;
settings?: CardSettings;
},
'home.widget.metadata',
{}
>;
inputs: {};
config: {};
configInput: {};
dataRefs: never;
}>;
// @alpha (undocumented)
export interface HomepageWidgetBlueprintParams {
componentProps?: Record<string, unknown>;
components: () => Promise<ComponentParts>;
description?: string;
layout?: CardLayout;
name?: string;
settings?: CardSettings;
title?: string;
}
// @alpha
export const homeReactTranslationRef: TranslationRef<
'home-react',
{
@@ -15,5 +83,23 @@ export const homeReactTranslationRef: TranslationRef<
}
>;
// (No @packageDocumentation comment for this package)
// @alpha
export const titleExtensionDataRef: ConfigurableExtensionDataRef<
string,
'title',
{}
>;
// @alpha
export const widgetMetadataRef: ConfigurableExtensionDataRef<
{
name?: string;
title?: string;
description?: string;
layout?: CardLayout;
settings?: CardSettings;
},
'home.widget.metadata',
{}
>;
```
+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({