feat: create core component extension

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-10-27 08:45:39 +02:00
parent 6ac3653851
commit 7c86ff2b6b
4 changed files with 140 additions and 3 deletions
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import {
coreExtensionData,
createExtension,
@@ -30,6 +32,12 @@ export const Core = createExtension({
themes: createExtensionInput({
theme: coreExtensionData.theme,
}),
components: createExtensionInput(
{
provider: coreExtensionData.reactComponent,
},
{ singleton: true },
),
root: createExtensionInput(
{
element: coreExtensionData.reactElement,
@@ -42,7 +50,11 @@ export const Core = createExtension({
},
factory({ inputs }) {
return {
root: inputs.root.element,
root: (
<inputs.components.provider>
{inputs.root.element}
</inputs.components.provider>
),
};
},
});
@@ -0,0 +1,98 @@
/*
* Copyright 2023 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 React, { PropsWithChildren, useMemo } from 'react';
import {
createExtension,
coreExtensionData,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppContextProvider } from '../../../core-app-api/src/app/AppContext';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { useApp } from '../../../core-plugin-api/src/app/useApp';
export const CoreComponents = createExtension({
id: 'core.components',
attachTo: { id: 'core', input: 'components' },
inputs: {
progress: createExtensionInput(
{
component: coreExtensionData.components.progress,
},
{
singleton: true,
},
),
bootErrorPage: createExtensionInput(
{
component: coreExtensionData.components.bootErrorPage,
},
{
singleton: true,
},
),
notFoundErrorPage: createExtensionInput(
{
component: coreExtensionData.components.notFoundErrorPage,
},
{
singleton: true,
},
),
errorBoundaryFallback: createExtensionInput(
{
component: coreExtensionData.components.errorBoundaryFallback,
},
{
singleton: true,
},
),
},
output: {
provider: coreExtensionData.reactComponent,
},
factory({ bind, inputs }) {
bind({
provider: function Provider(props: PropsWithChildren<{}>) {
const { children } = props;
const parentContext = useApp();
const appContext = useMemo(
() => ({
...parentContext,
// Only override components
getComponents: () => ({
// Skipping Router and SignInPage
...parentContext.getComponents(),
Progress: inputs.progress.component,
BootErrorPage: inputs.bootErrorPage.component,
NotFoundErrorPage: inputs.notFoundErrorPage.component,
ErrorBoundaryFallback: inputs.errorBoundaryFallback.component,
}),
}),
[parentContext],
);
return (
<AppContextProvider appContext={appContext}>
{children}
</AppContextProvider>
);
},
});
},
});
@@ -89,6 +89,7 @@ import { RoutingProvider } from '../routing/RoutingProvider';
import { resolveRouteBindings } from '../routing/resolveRouteBindings';
import { collectRouteIds } from '../routing/collectRouteIds';
import { createAppTree } from '../tree';
import { CoreComponents } from '../extensions/CoreComponents';
import { AppNode } from '@backstage/frontend-plugin-api';
import { toLegacyPlugin } from '../routing/toLegacyPlugin';
import { InternalAppContext } from './InternalAppContext';
@@ -104,6 +105,7 @@ const builtinExtensions = [
CoreRoutes,
CoreNav,
CoreLayout,
CoreComponents,
LightTheme,
DarkTheme,
];
@@ -376,7 +378,13 @@ function createLegacyAppContext(plugins: BackstagePlugin[]): AppContext {
},
getComponents(): AppComponents {
return defaultComponents;
return {
...defaultComponents,
Progress: () => null,
BootErrorPage: () => null,
NotFoundErrorPage: () => null,
ErrorBoundaryFallback: () => null,
};
},
};
}
@@ -20,8 +20,10 @@ import {
AppTheme,
IconComponent,
} from '@backstage/core-plugin-api';
import { createExtensionDataRef } from './createExtensionDataRef';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppComponents } from '../../../core-app-api/src/app/types';
import { RouteRef } from '../routing';
import { createExtensionDataRef } from './createExtensionDataRef';
/** @public */
export type NavTarget = {
@@ -38,6 +40,9 @@ export type LogoElements = {
/** @public */
export const coreExtensionData = {
reactComponent: createExtensionDataRef<
<T extends {}>(props: T) => JSX.Element
>('core.reactComponent'),
reactElement: createExtensionDataRef<JSX.Element>('core.reactElement'),
routePath: createExtensionDataRef<string>('core.routing.path'),
apiFactory: createExtensionDataRef<AnyApiFactory>('core.api.factory'),
@@ -45,4 +50,18 @@ export const coreExtensionData = {
navTarget: createExtensionDataRef<NavTarget>('core.nav.target'),
theme: createExtensionDataRef<AppTheme>('core.theme'),
logoElements: createExtensionDataRef<LogoElements>('core.logos'),
components: {
progress: createExtensionDataRef<AppComponents['Progress']>(
'core.components.progress',
),
bootErrorPage: createExtensionDataRef<AppComponents['BootErrorPage']>(
'core.components.bootErrorPage',
),
notFoundErrorPage: createExtensionDataRef<
AppComponents['NotFoundErrorPage']
>('core.components.notFoundErrorPage'),
errorBoundaryFallback: createExtensionDataRef<
AppComponents['ErrorBoundaryFallback']
>('core.components.errorBoundary'),
},
};