Merge pull request #30750 from backstage/mob/component-refs-wth
`nfs(componentRefs)`: Reworking how Component Refs are defined and used
This commit is contained in:
@@ -350,21 +350,6 @@ export { bitbucketAuthApiRef };
|
||||
|
||||
export { bitbucketServerAuthApiRef };
|
||||
|
||||
// @public (undocumented)
|
||||
export type ComponentRef<T extends {} = {}> = {
|
||||
id: string;
|
||||
T: T;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface ComponentsApi {
|
||||
// (undocumented)
|
||||
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const componentsApiRef: ApiRef<ComponentsApi>;
|
||||
|
||||
export { ConfigApi };
|
||||
|
||||
export { configApiRef };
|
||||
@@ -389,20 +374,6 @@ export interface ConfigurableExtensionDataRef<
|
||||
>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const coreComponentRefs: {
|
||||
progress: ComponentRef<CoreProgressProps>;
|
||||
notFoundErrorPage: ComponentRef<CoreNotFoundErrorPageProps>;
|
||||
errorBoundaryFallback: ComponentRef<CoreErrorBoundaryFallbackProps>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CoreErrorBoundaryFallbackProps = {
|
||||
plugin?: FrontendPlugin;
|
||||
error: Error;
|
||||
resetError: () => void;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const coreExtensionData: {
|
||||
reactElement: ConfigurableExtensionDataRef<
|
||||
@@ -418,73 +389,10 @@ export const coreExtensionData: {
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CoreNotFoundErrorPageProps = {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CoreProgressProps = {};
|
||||
|
||||
export { createApiFactory };
|
||||
|
||||
export { createApiRef };
|
||||
|
||||
// @public (undocumented)
|
||||
export function createComponentExtension<TProps extends {}>(options: {
|
||||
ref: ComponentRef<TProps>;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
loader:
|
||||
| {
|
||||
lazy: () => Promise<ComponentType<TProps>>;
|
||||
}
|
||||
| {
|
||||
sync: () => ComponentType<TProps>;
|
||||
};
|
||||
}): ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
impl: ComponentType;
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
[x: string]: ExtensionInput<
|
||||
ExtensionDataRef,
|
||||
{
|
||||
optional: boolean;
|
||||
singleton: boolean;
|
||||
}
|
||||
>;
|
||||
};
|
||||
params: never;
|
||||
kind: 'component';
|
||||
name: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export namespace createComponentExtension {
|
||||
const // (undocumented)
|
||||
componentDataRef: ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
impl: ComponentType;
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function createComponentRef<T extends {} = {}>(options: {
|
||||
id: string;
|
||||
}): ComponentRef<T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createExtension<
|
||||
UOutput extends ExtensionDataRef,
|
||||
@@ -849,6 +757,31 @@ export function createSubRouteRef<
|
||||
parent: RouteRef<ParentParams>;
|
||||
}): MakeSubRouteRef<PathParams<Path>, ParentParams>;
|
||||
|
||||
// @public
|
||||
export function createSwappableComponent<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
>(
|
||||
options: CreateSwappableComponentOptions<
|
||||
TInnerComponentProps,
|
||||
TExternalComponentProps
|
||||
>,
|
||||
): ((props: TExternalComponentProps) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CreateSwappableComponentOptions<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
> = {
|
||||
id: string;
|
||||
loader?:
|
||||
| (() => (props: TInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>);
|
||||
transformProps?: (props: TExternalComponentProps) => TInnerComponentProps;
|
||||
};
|
||||
|
||||
export { createTranslationMessages };
|
||||
|
||||
export { createTranslationRef };
|
||||
@@ -899,6 +832,20 @@ export { ErrorApiErrorContext };
|
||||
|
||||
export { errorApiRef };
|
||||
|
||||
// @public (undocumented)
|
||||
export const ErrorDisplay: ((
|
||||
props: ErrorDisplayProps,
|
||||
) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<ErrorDisplayProps, ErrorDisplayProps>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ErrorDisplayProps = {
|
||||
plugin?: FrontendPlugin;
|
||||
error: Error;
|
||||
resetError: () => void;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Extension<TConfig, TConfigInput = TConfig> {
|
||||
// (undocumented)
|
||||
@@ -1554,6 +1501,18 @@ export const NavItemBlueprint: ExtensionBlueprint<{
|
||||
};
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const NotFoundErrorPage: ((
|
||||
props: NotFoundErrorPageProps,
|
||||
) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<NotFoundErrorPageProps, NotFoundErrorPageProps>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type NotFoundErrorPageProps = {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export { OAuthApi };
|
||||
|
||||
export { OAuthRequestApi };
|
||||
@@ -1638,6 +1597,14 @@ export { ProfileInfo };
|
||||
|
||||
export { ProfileInfoApi };
|
||||
|
||||
// @public (undocumented)
|
||||
export const Progress: ((props: ProgressProps) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<ProgressProps, ProgressProps>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ProgressProps = {};
|
||||
|
||||
// @public
|
||||
export type ResolvedExtensionInput<
|
||||
TExtensionInput extends ExtensionInput<any, any>,
|
||||
@@ -1830,6 +1797,90 @@ export interface SubRouteRef<
|
||||
readonly T: TParams;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const SwappableComponentBlueprint: ExtensionBlueprint<{
|
||||
kind: 'component';
|
||||
params: <Ref extends SwappableComponentRef<any>>(params: {
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
|
||||
: never;
|
||||
}) => ExtensionBlueprintParams<{
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
|
||||
: never;
|
||||
}>;
|
||||
output: ExtensionDataRef<
|
||||
{
|
||||
ref: SwappableComponentRef;
|
||||
loader:
|
||||
| (() => (props: {}) => JSX.Element | null)
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
},
|
||||
'core.swappableComponent',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
config: {};
|
||||
configInput: {};
|
||||
dataRefs: {
|
||||
component: ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: SwappableComponentRef;
|
||||
loader:
|
||||
| (() => (props: {}) => JSX.Element | null)
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
},
|
||||
'core.swappableComponent',
|
||||
{}
|
||||
>;
|
||||
};
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SwappableComponentRef<
|
||||
TInnerComponentProps extends {} = {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
> = {
|
||||
id: string;
|
||||
TProps: TInnerComponentProps;
|
||||
TExternalProps: TExternalComponentProps;
|
||||
$$type: '@backstage/SwappableComponentRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface SwappableComponentsApi {
|
||||
// (undocumented)
|
||||
getComponent<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
>(
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,
|
||||
): (props: TInnerComponentProps) => JSX.Element | null;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const swappableComponentsApiRef: ApiRef<SwappableComponentsApi>;
|
||||
|
||||
// @public
|
||||
export const ThemeBlueprint: ExtensionBlueprint<{
|
||||
kind: 'theme';
|
||||
@@ -1906,11 +1957,6 @@ export { useApiHolder };
|
||||
// @public
|
||||
export function useAppNode(): AppNode | undefined;
|
||||
|
||||
// @public
|
||||
export function useComponentRef<T extends {}>(
|
||||
ref: ComponentRef<T>,
|
||||
): ComponentType<T>;
|
||||
|
||||
// @public
|
||||
export function useRouteRef<TParams extends AnyRouteRefParams>(
|
||||
routeRef:
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 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 { ComponentType } from 'react';
|
||||
import { createApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { ComponentRef } from '../../components';
|
||||
|
||||
/**
|
||||
* API for looking up components based on component refs.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ComponentsApi {
|
||||
// TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component?
|
||||
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ApiRef` of {@link ComponentsApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const componentsApiRef = createApiRef<ComponentsApi>({
|
||||
id: 'core.components',
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Returns the component associated with the given ref.
|
||||
*/
|
||||
export function useComponentRef<T extends {}>(
|
||||
ref: ComponentRef<T>,
|
||||
): ComponentType<T> {
|
||||
const componentsApi = useApi(componentsApiRef);
|
||||
return componentsApi.getComponent<T>(ref);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { SwappableComponentRef } from '../../components';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* API for looking up components based on component refs.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SwappableComponentsApi {
|
||||
getComponent<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
>(
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,
|
||||
): (props: TInnerComponentProps) => JSX.Element | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ApiRef` of {@link SwappableComponentsApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const swappableComponentsApiRef = createApiRef<SwappableComponentsApi>({
|
||||
id: 'core.swappable-components',
|
||||
});
|
||||
@@ -34,7 +34,7 @@ export * from './auth';
|
||||
|
||||
export * from './AlertApi';
|
||||
export * from './AppThemeApi';
|
||||
export * from './ComponentsApi';
|
||||
export * from './SwappableComponentsApi';
|
||||
export * from './ConfigApi';
|
||||
export * from './DiscoveryApi';
|
||||
export * from './ErrorApi';
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 { renderInTestApp } from '@backstage/frontend-test-utils';
|
||||
import { createSwappableComponent } from '../components';
|
||||
import { SwappableComponentBlueprint } from './SwappableComponentBlueprint';
|
||||
import { PageBlueprint } from './PageBlueprint';
|
||||
import { waitFor, screen } from '@testing-library/react';
|
||||
|
||||
describe('SwappableComponentBlueprint', () => {
|
||||
it('should allow defining a component override for a component ref', () => {
|
||||
const Component = createSwappableComponent({
|
||||
id: 'test.component',
|
||||
loader: () => (props: { hello: string }) => <div>{props.hello}</div>,
|
||||
});
|
||||
|
||||
const extension = SwappableComponentBlueprint.make({
|
||||
params: define =>
|
||||
define({
|
||||
component: Component,
|
||||
loader: () => props => {
|
||||
// @ts-expect-error
|
||||
const t: number = props.hello;
|
||||
|
||||
return <div>Override {props.hello}</div>;
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(extension).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render default component refs in the app', async () => {
|
||||
const TestComponent = createSwappableComponent({
|
||||
id: 'test.component',
|
||||
loader: () => (props: { hello: string }) => <div>{props.hello}</div>,
|
||||
});
|
||||
|
||||
renderInTestApp(<div />, {
|
||||
extensions: [
|
||||
PageBlueprint.make({
|
||||
params: define =>
|
||||
define({
|
||||
// todo(blam): there's a bug that this path cannot be `/`?
|
||||
path: '/test',
|
||||
loader: async () => <TestComponent hello="test!" />,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
initialRouteEntries: ['/test'],
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByText('test!')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should render a component ref without a default implementation', async () => {
|
||||
const TestComponent = createSwappableComponent({
|
||||
id: 'test.component',
|
||||
});
|
||||
|
||||
renderInTestApp(<div />, {
|
||||
extensions: [
|
||||
PageBlueprint.make({
|
||||
params: define =>
|
||||
define({
|
||||
path: '/test',
|
||||
loader: async () => <TestComponent />,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
initialRouteEntries: ['/test'],
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('test.component')).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a component ref with an async loader implementation', async () => {
|
||||
const TestComponent = createSwappableComponent({
|
||||
id: 'test.component',
|
||||
loader: async () => (props: { hello: string }) =>
|
||||
<div>{props.hello}</div>,
|
||||
});
|
||||
|
||||
renderInTestApp(<div />, {
|
||||
extensions: [
|
||||
PageBlueprint.make({
|
||||
params: define =>
|
||||
define({
|
||||
// todo(blam): there's a bug that this path cannot be `/`?
|
||||
path: '/test',
|
||||
loader: async () => <TestComponent hello="test!" />,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
initialRouteEntries: ['/test'],
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByText('test!')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should render a component ref with an async loader implementation and prop transform', async () => {
|
||||
const TestComponent = createSwappableComponent({
|
||||
id: 'test.component',
|
||||
loader: async () => (props: { hello: string }) =>
|
||||
<div>{props.hello}</div>,
|
||||
transformProps: ({ hello }) => ({ hello: `tr ${hello}` }),
|
||||
});
|
||||
|
||||
renderInTestApp(<div />, {
|
||||
extensions: [
|
||||
PageBlueprint.make({
|
||||
params: define =>
|
||||
define({
|
||||
// todo(blam): there's a bug that this path cannot be `/`?
|
||||
path: '/test',
|
||||
loader: async () => <TestComponent hello="test!" />,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
initialRouteEntries: ['/test'],
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('tr test!')).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 { SwappableComponentRef } from '../components';
|
||||
import {
|
||||
createExtensionBlueprint,
|
||||
createExtensionBlueprintParams,
|
||||
createExtensionDataRef,
|
||||
} from '../wiring';
|
||||
|
||||
export const componentDataRef = createExtensionDataRef<{
|
||||
ref: SwappableComponentRef;
|
||||
loader:
|
||||
| (() => (props: {}) => JSX.Element | null)
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
}>().with({ id: 'core.swappableComponent' });
|
||||
|
||||
/**
|
||||
* Blueprint for creating swappable components from a SwappableComponentRef and a loader
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SwappableComponentBlueprint = createExtensionBlueprint({
|
||||
kind: 'component',
|
||||
attachTo: { id: 'api:app/swappable-components', input: 'components' },
|
||||
output: [componentDataRef],
|
||||
dataRefs: {
|
||||
component: componentDataRef,
|
||||
},
|
||||
defineParams<Ref extends SwappableComponentRef<any>>(params: {
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
|
||||
: never;
|
||||
}) {
|
||||
return createExtensionBlueprintParams(params);
|
||||
},
|
||||
factory: params => [
|
||||
componentDataRef({
|
||||
ref: params.component.ref,
|
||||
loader: params.loader,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -33,3 +33,4 @@ export { RouterBlueprint } from './RouterBlueprint';
|
||||
export { SignInPageBlueprint } from './SignInPageBlueprint';
|
||||
export { ThemeBlueprint } from './ThemeBlueprint';
|
||||
export { TranslationBlueprint } from './TranslationBlueprint';
|
||||
export { SwappableComponentBlueprint } from './SwappableComponentBlueprint';
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 {
|
||||
ErrorDisplayProps,
|
||||
NotFoundErrorPageProps,
|
||||
ProgressProps,
|
||||
} from '../types';
|
||||
import { createSwappableComponent } from './createSwappableComponent';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const Progress = createSwappableComponent<ProgressProps>({
|
||||
id: 'core.components.progress',
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const NotFoundErrorPage =
|
||||
createSwappableComponent<NotFoundErrorPageProps>({
|
||||
id: 'core.components.notFoundErrorPage',
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ErrorDisplay = createSwappableComponent<ErrorDisplayProps>({
|
||||
id: 'core.components.errorDisplay',
|
||||
loader: () => props =>
|
||||
<div data-testid="core.components.errorDisplay">{props.error.message}</div>,
|
||||
});
|
||||
@@ -14,13 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ComponentType, PropsWithChildren } from 'react';
|
||||
import { Component, PropsWithChildren } from 'react';
|
||||
import { FrontendPlugin } from '../wiring';
|
||||
import { CoreErrorBoundaryFallbackProps } from '../types';
|
||||
import { ErrorDisplay } from './DefaultSwappableComponents';
|
||||
|
||||
type ErrorBoundaryProps = PropsWithChildren<{
|
||||
plugin?: FrontendPlugin;
|
||||
Fallback: ComponentType<CoreErrorBoundaryFallbackProps>;
|
||||
}>;
|
||||
type ErrorBoundaryState = { error?: Error };
|
||||
|
||||
@@ -41,13 +40,15 @@ export class ErrorBoundary extends Component<
|
||||
|
||||
render() {
|
||||
const { error } = this.state;
|
||||
const { plugin, children, Fallback } = this.props;
|
||||
const { plugin, children } = this.props;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Fallback
|
||||
<ErrorDisplay
|
||||
// todo: do we want to just use useAppNode hook in the ErrorDisplay instead?
|
||||
plugin={plugin}
|
||||
error={error}
|
||||
// todo: probably change this to onResetError
|
||||
resetError={this.handleErrorReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -25,10 +25,10 @@ import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';
|
||||
import { AppNode, useComponentRef } from '../apis';
|
||||
import { coreComponentRefs } from './coreComponentRefs';
|
||||
import { AppNode } from '../apis';
|
||||
import { coreExtensionData } from '../wiring';
|
||||
import { AppNodeProvider } from './AppNodeProvider';
|
||||
import { Progress } from './DefaultSwappableComponents';
|
||||
|
||||
type RouteTrackerProps = PropsWithChildren<{
|
||||
enabled?: boolean;
|
||||
@@ -66,8 +66,6 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) {
|
||||
);
|
||||
|
||||
const plugin = node.spec.plugin;
|
||||
const Progress = useComponentRef(coreComponentRefs.progress);
|
||||
const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);
|
||||
|
||||
// Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight
|
||||
const attributes = {
|
||||
@@ -78,7 +76,7 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) {
|
||||
return (
|
||||
<AppNodeProvider node={node}>
|
||||
<Suspense fallback={<Progress />}>
|
||||
<ErrorBoundary plugin={plugin} Fallback={fallback}>
|
||||
<ErrorBoundary plugin={plugin}>
|
||||
<AnalyticsContext attributes={attributes}>
|
||||
<RouteTracker enabled={hasRoutePathOutput}>{children}</RouteTracker>
|
||||
</AnalyticsContext>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
CoreErrorBoundaryFallbackProps,
|
||||
CoreNotFoundErrorPageProps,
|
||||
CoreProgressProps,
|
||||
} from '../types';
|
||||
import { createComponentRef } from './createComponentRef';
|
||||
|
||||
const coreProgressComponentRef = createComponentRef<CoreProgressProps>({
|
||||
id: 'core.components.progress',
|
||||
});
|
||||
|
||||
const coreNotFoundErrorPageComponentRef =
|
||||
createComponentRef<CoreNotFoundErrorPageProps>({
|
||||
id: 'core.components.notFoundErrorPage',
|
||||
});
|
||||
|
||||
const coreErrorBoundaryFallbackComponentRef =
|
||||
createComponentRef<CoreErrorBoundaryFallbackProps>({
|
||||
id: 'core.components.errorBoundaryFallback',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const coreComponentRefs = {
|
||||
progress: coreProgressComponentRef,
|
||||
notFoundErrorPage: coreNotFoundErrorPageComponentRef,
|
||||
errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef,
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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 { createComponentRef } from './createComponentRef';
|
||||
|
||||
describe('createComponentRef', () => {
|
||||
it('can be created and read', () => {
|
||||
const ref = createComponentRef({ id: 'foo' });
|
||||
expect(ref.id).toBe('foo');
|
||||
expect(String(ref)).toBe('ComponentRef{id=foo}');
|
||||
});
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export type ComponentRef<T extends {} = {}> = {
|
||||
id: string;
|
||||
T: T;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export function createComponentRef<T extends {} = {}>(options: {
|
||||
id: string;
|
||||
}): ComponentRef<T> {
|
||||
const { id } = options;
|
||||
return {
|
||||
id,
|
||||
toString() {
|
||||
return `ComponentRef{id=${id}}`;
|
||||
},
|
||||
} as ComponentRef<T>;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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 { render, screen } from '@testing-library/react';
|
||||
import { createSwappableComponent } from './createSwappableComponent';
|
||||
|
||||
describe('createSwappableComponent', () => {
|
||||
it('can be created and read', () => {
|
||||
const { ref } = createSwappableComponent({ id: 'foo' });
|
||||
expect(ref.id).toBe('foo');
|
||||
expect(String(ref)).toBe('SwappableComponentRef{id=foo}');
|
||||
});
|
||||
|
||||
it('should allow defining a default component implementation', () => {
|
||||
const Test = () => <div>test</div>;
|
||||
|
||||
createSwappableComponent<{ foo: string }, { bar: string }>({
|
||||
id: 'foo',
|
||||
loader:
|
||||
() =>
|
||||
({ foo }) =>
|
||||
<Test key={foo} />,
|
||||
});
|
||||
|
||||
createSwappableComponent<{ foo: string }, { bar: string }>({
|
||||
id: 'foo',
|
||||
loader:
|
||||
async () =>
|
||||
({ foo }) =>
|
||||
<Test key={foo} />,
|
||||
});
|
||||
|
||||
createSwappableComponent<{ foo: string }, { bar: string }>({
|
||||
id: 'foo',
|
||||
});
|
||||
|
||||
expect(Test).toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow transformings props', () => {
|
||||
createSwappableComponent<{ foo: string }, { bar: string }>({
|
||||
id: 'foo',
|
||||
transformProps: props => ({ foo: props.bar }),
|
||||
});
|
||||
|
||||
createSwappableComponent<{ foo: string }, { bar: string }>({
|
||||
id: 'foo',
|
||||
// @ts-expect-error - this should be an error as foo is not a string
|
||||
transformProps: props => ({ foo: 1 }),
|
||||
});
|
||||
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
describe('sync', () => {
|
||||
it('should create a component from a ref for sync component', async () => {
|
||||
const Component = createSwappableComponent({
|
||||
id: 'random',
|
||||
loader: () => (props: { name: string }) => {
|
||||
return <div data-testid="test">{props.name}</div>;
|
||||
},
|
||||
transformProps: (props: { id: string }) => ({
|
||||
name: props.id,
|
||||
}),
|
||||
});
|
||||
|
||||
render(<Component id="test" />);
|
||||
|
||||
await expect(screen.findByTestId('test')).resolves.toHaveTextContent(
|
||||
'test',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a fallback when theres no default implementation provided', async () => {
|
||||
const Component = createSwappableComponent({
|
||||
id: 'random',
|
||||
});
|
||||
|
||||
render(<Component />);
|
||||
await expect(screen.findByTestId('random')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should map props from external to internal', async () => {
|
||||
const Component = createSwappableComponent({
|
||||
id: 'random',
|
||||
transformProps: (props: { name: string }) => ({
|
||||
uppercase: props.name.toUpperCase(),
|
||||
}),
|
||||
loader: () => props => {
|
||||
// @ts-expect-error as uppercase is types as a string
|
||||
const test: number = props.uppercase;
|
||||
|
||||
return <div data-testid="test">{props.uppercase}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
render(<Component name="test" />);
|
||||
|
||||
await expect(screen.findByTestId('test')).resolves.toHaveTextContent(
|
||||
'TEST',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('async', () => {
|
||||
it('should create a component from a ref for async component', async () => {
|
||||
const Component = createSwappableComponent({
|
||||
id: 'random',
|
||||
loader: async () => (props: { name: string }) => {
|
||||
return <div data-testid="test">{props.name}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
render(<Component name="test" />);
|
||||
|
||||
await expect(screen.findByTestId('test')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render a fallback when theres no default implementation provided', async () => {
|
||||
const Component = createSwappableComponent({
|
||||
id: 'random',
|
||||
});
|
||||
|
||||
render(<Component />);
|
||||
|
||||
await expect(screen.findByTestId('random')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should map props from external to internal', async () => {
|
||||
const Component = createSwappableComponent({
|
||||
id: 'random',
|
||||
transformProps: (props: { name: string }) => ({
|
||||
uppercase: props.name.toUpperCase(),
|
||||
}),
|
||||
loader: async () => props => {
|
||||
// @ts-expect-error as uppercase is types as a string
|
||||
const test: number = props.uppercase;
|
||||
|
||||
return <div data-testid="test">{props.uppercase}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
render(<Component name="test" />);
|
||||
|
||||
await expect(screen.findByTestId('test')).resolves.toHaveTextContent(
|
||||
'TEST',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 { OpaqueSwappableComponentRef } from '@internal/frontend';
|
||||
import { swappableComponentsApiRef, useApi } from '../apis';
|
||||
import { lazy } from 'react';
|
||||
|
||||
/** @public */
|
||||
export type SwappableComponentRef<
|
||||
TInnerComponentProps extends {} = {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
> = {
|
||||
id: string;
|
||||
TProps: TInnerComponentProps;
|
||||
TExternalProps: TExternalComponentProps;
|
||||
$$type: '@backstage/SwappableComponentRef';
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for creating an SwappableComponent.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CreateSwappableComponentOptions<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
> = {
|
||||
id: string;
|
||||
loader?:
|
||||
| (() => (props: TInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>);
|
||||
transformProps?: (props: TExternalComponentProps) => TInnerComponentProps;
|
||||
};
|
||||
|
||||
const useComponentRefApi = () => {
|
||||
try {
|
||||
return useApi(swappableComponentsApiRef);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SwappableComponent that can be used to render the component, optionally overridden by the app.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createSwappableComponent<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
>(
|
||||
options: CreateSwappableComponentOptions<
|
||||
TInnerComponentProps,
|
||||
TExternalComponentProps
|
||||
>,
|
||||
): ((props: TExternalComponentProps) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
} {
|
||||
const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
|
||||
<div data-testid={options.id} {...p} />
|
||||
);
|
||||
|
||||
const ref = OpaqueSwappableComponentRef.createInstance('v1', {
|
||||
id: options.id,
|
||||
TProps: null as unknown as TInnerComponentProps,
|
||||
TExternalProps: null as unknown as TExternalComponentProps,
|
||||
toString() {
|
||||
return `SwappableComponentRef{id=${options.id}}`;
|
||||
},
|
||||
defaultComponent: lazy(async () => {
|
||||
const Component = (await options.loader?.()) ?? FallbackComponent;
|
||||
return { default: Component };
|
||||
}) as (typeof OpaqueSwappableComponentRef.TInternal)['defaultComponent'],
|
||||
transformProps:
|
||||
options.transformProps as (typeof OpaqueSwappableComponentRef.TInternal)['transformProps'],
|
||||
});
|
||||
|
||||
const ComponentRefImpl = (props: TExternalComponentProps) => {
|
||||
const api = useComponentRefApi();
|
||||
|
||||
if (!api) {
|
||||
const internalRef = OpaqueSwappableComponentRef.toInternal(ref);
|
||||
const Component = internalRef.defaultComponent;
|
||||
const innerProps = internalRef.transformProps?.(props) ?? props;
|
||||
return <Component {...innerProps} />;
|
||||
}
|
||||
|
||||
const Component = api.getComponent<any>(ref);
|
||||
return <Component {...props} />;
|
||||
};
|
||||
|
||||
Object.assign(ComponentRefImpl, { ref });
|
||||
|
||||
return ComponentRefImpl as {
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
} & ((props: TExternalComponentProps) => JSX.Element | null);
|
||||
}
|
||||
@@ -18,6 +18,10 @@ export {
|
||||
ExtensionBoundary,
|
||||
type ExtensionBoundaryProps,
|
||||
} from './ExtensionBoundary';
|
||||
export { coreComponentRefs } from './coreComponentRefs';
|
||||
export { createComponentRef, type ComponentRef } from './createComponentRef';
|
||||
export {
|
||||
createSwappableComponent,
|
||||
type CreateSwappableComponentOptions,
|
||||
type SwappableComponentRef,
|
||||
} from './createSwappableComponent';
|
||||
export { useAppNode } from './AppNodeProvider';
|
||||
export * from './DefaultSwappableComponents';
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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 { lazy, ComponentType } from 'react';
|
||||
import { createExtension, createExtensionDataRef } from '../wiring';
|
||||
import { ComponentRef } from '../components';
|
||||
|
||||
/** @public */
|
||||
export function createComponentExtension<TProps extends {}>(options: {
|
||||
ref: ComponentRef<TProps>;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
loader:
|
||||
| {
|
||||
lazy: () => Promise<ComponentType<TProps>>;
|
||||
}
|
||||
| {
|
||||
sync: () => ComponentType<TProps>;
|
||||
};
|
||||
}) {
|
||||
return createExtension({
|
||||
kind: 'component',
|
||||
name: options.name ?? options.ref.id,
|
||||
attachTo: { id: 'api:app/components', input: 'components' },
|
||||
disabled: options.disabled,
|
||||
output: [createComponentExtension.componentDataRef],
|
||||
factory() {
|
||||
if ('sync' in options.loader) {
|
||||
return [
|
||||
createComponentExtension.componentDataRef({
|
||||
ref: options.ref,
|
||||
impl: options.loader.sync() as ComponentType,
|
||||
}),
|
||||
];
|
||||
}
|
||||
const lazyLoader = options.loader.lazy;
|
||||
const ExtensionComponent = lazy(() =>
|
||||
lazyLoader().then(Component => ({
|
||||
default: Component,
|
||||
})),
|
||||
) as unknown as ComponentType;
|
||||
|
||||
return [
|
||||
createComponentExtension.componentDataRef({
|
||||
ref: options.ref,
|
||||
impl: ExtensionComponent,
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export namespace createComponentExtension {
|
||||
export const componentDataRef = createExtensionDataRef<{
|
||||
ref: ComponentRef;
|
||||
impl: ComponentType;
|
||||
}>().with({ id: 'core.component.component' });
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { createComponentExtension } from './createComponentExtension';
|
||||
@@ -24,7 +24,6 @@ export * from './analytics';
|
||||
export * from './apis';
|
||||
export * from './blueprints';
|
||||
export * from './components';
|
||||
export * from './extensions';
|
||||
export * from './icons';
|
||||
export * from './routing';
|
||||
export * from './schema';
|
||||
@@ -33,7 +32,7 @@ export * from './translation';
|
||||
export * from './wiring';
|
||||
|
||||
export type {
|
||||
CoreProgressProps,
|
||||
CoreNotFoundErrorPageProps,
|
||||
CoreErrorBoundaryFallbackProps,
|
||||
ProgressProps,
|
||||
NotFoundErrorPageProps,
|
||||
ErrorDisplayProps,
|
||||
} from './types';
|
||||
|
||||
@@ -18,15 +18,15 @@ import { ReactNode } from 'react';
|
||||
import { FrontendPlugin } from './wiring';
|
||||
|
||||
/** @public */
|
||||
export type CoreProgressProps = {};
|
||||
export type ProgressProps = {};
|
||||
|
||||
/** @public */
|
||||
export type CoreNotFoundErrorPageProps = {
|
||||
export type NotFoundErrorPageProps = {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type CoreErrorBoundaryFallbackProps = {
|
||||
export type ErrorDisplayProps = {
|
||||
plugin?: FrontendPlugin;
|
||||
error: Error;
|
||||
resetError: () => void;
|
||||
|
||||
@@ -214,7 +214,6 @@ type AnyParamsInput<TParams extends object | ExtensionBlueprintDefineParams> =
|
||||
* @public
|
||||
*/
|
||||
export interface ExtensionBlueprint<
|
||||
// TParamsMapper extends (params: any) => object,
|
||||
T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters,
|
||||
> {
|
||||
dataRefs: T['dataRefs'];
|
||||
|
||||
Reference in New Issue
Block a user