chore: make more things swoppable

Signed-off-by: benjdlambert <ben@blam.sh>

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2025-08-06 14:24:17 +02:00
parent d996f05ae5
commit 4b3ba2b048
17 changed files with 228 additions and 172 deletions
@@ -22,8 +22,8 @@ import {
} from '@backstage/core-plugin-api';
import {
AnyRouteRefParams,
ComponentRef,
ComponentsApi,
SwappableComponentRef,
SwappableComponentsApi,
CoreErrorBoundaryFallbackProps,
CoreNotFoundErrorPageProps,
CoreProgressProps,
@@ -34,7 +34,7 @@ import {
RouteRef,
RouteResolutionApi,
SubRouteRef,
componentsApiRef,
swappableComponentsApiRef,
iconsApiRef,
routeResolutionApiRef,
Progress,
@@ -51,7 +51,7 @@ import { useVersionedContext } from '@backstage/version-bridge';
import { type RouteResolver } from '../../../core-plugin-api/src/routing/useRouteRef';
import { convertLegacyRouteRef } from '../convertLegacyRouteRef';
class CompatComponentsApi implements ComponentsApi {
class CompatComponentsApi implements SwappableComponentsApi {
readonly #Progress: ComponentType<CoreProgressProps>;
readonly #NotFoundErrorPage: ComponentType<CoreNotFoundErrorPageProps>;
readonly #ErrorBoundaryFallback: ComponentType<CoreErrorBoundaryFallbackProps>;
@@ -69,11 +69,11 @@ class CompatComponentsApi implements ComponentsApi {
this.#ErrorBoundaryFallback = ErrorBoundaryFallback;
}
getComponent<
getComponentLoader<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
>(
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>,
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,
):
| (() => (props: TInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
@@ -135,7 +135,7 @@ class CompatRouteResolutionApi implements RouteResolutionApi {
}
class ForwardsCompatApis implements ApiHolder {
readonly #componentsApi: ComponentsApi;
readonly #componentsApi: SwappableComponentsApi;
readonly #iconsApi: IconsApi;
readonly #routeResolutionApi: RouteResolutionApi;
@@ -146,7 +146,7 @@ class ForwardsCompatApis implements ApiHolder {
}
get<T>(ref: ApiRef<any>): T | undefined {
if (ref.id === componentsApiRef.id) {
if (ref.id === swappableComponentsApiRef.id) {
return this.#componentsApi as T;
} else if (ref.id === iconsApiRef.id) {
return this.#iconsApi as T;
@@ -15,7 +15,7 @@
*/
import {
componentsApiRef,
swappableComponentsApiRef,
coreExtensionData,
createExtension,
iconsApiRef,
@@ -106,7 +106,7 @@ describe('ForwardsCompatProvider', () => {
};
function Component() {
const components = useApi(componentsApiRef);
const components = useApi(swappableComponentsApiRef);
const icons = useApi(iconsApiRef);
return (
<div data-testid="ctx">
@@ -114,7 +114,7 @@ describe('ForwardsCompatProvider', () => {
{Object.entries(defaultComponentRefs)
.map(
([name, ref]) =>
`${name}=${Boolean(components.getComponent(ref))}`,
`${name}=${Boolean(components.getComponentLoader(ref))}`,
)
.join(', ')}
{'\n'}
@@ -15,7 +15,7 @@
*/
import { createSwappableComponent } from '@backstage/frontend-plugin-api';
import { DefaultComponentsApi } from './DefaultComponentsApi';
import { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi';
import { render, screen } from '@testing-library/react';
const { ref: testRefA } = createSwappableComponent({ id: 'test.a' });
@@ -24,14 +24,16 @@ const { ref: testRefB2 } = createSwappableComponent({ id: 'test.b' });
describe('DefaultComponentsApi', () => {
it('should provide components', () => {
const api = DefaultComponentsApi.fromComponents([
const api = DefaultSwappableComponentsApi.fromComponents([
{
ref: testRefA,
loader: () => () => <div>test.a</div>,
},
]);
const ComponentA = api.getComponent(testRefA)?.() as () => JSX.Element;
const ComponentA = api.getComponentLoader(
testRefA,
)?.() as () => JSX.Element;
render(<ComponentA />);
@@ -40,15 +42,20 @@ describe('DefaultComponentsApi', () => {
it('should key extension refs by ID', () => {
const mockLoader = jest.fn(() => <div>test.b</div>);
const api = DefaultComponentsApi.fromComponents([
const api = DefaultSwappableComponentsApi.fromComponents([
{
ref: testRefB1,
loader: () => mockLoader,
},
]);
const ComponentB2 = api.getComponent(testRefB2)?.() as () => JSX.Element;
const ComponentB1 = api.getComponent(testRefB1)?.() as () => JSX.Element;
const ComponentB2 = api.getComponentLoader(
testRefB2,
)?.() as () => JSX.Element;
const ComponentB1 = api.getComponentLoader(
testRefB1,
)?.() as () => JSX.Element;
expect(ComponentB1).toBe(ComponentB2);
@@ -15,8 +15,8 @@
*/
import {
ComponentRef,
ComponentsApi,
SwappableComponentRef,
SwappableComponentsApi,
SwappableComponentBlueprint,
} from '@backstage/frontend-plugin-api';
@@ -25,7 +25,7 @@ import {
*
* @internal
*/
export class DefaultComponentsApi implements ComponentsApi {
export class DefaultSwappableComponentsApi implements SwappableComponentsApi {
#components: Map<
string,
| (() => (props: object) => JSX.Element | null)
@@ -36,7 +36,7 @@ export class DefaultComponentsApi implements ComponentsApi {
static fromComponents(
components: Array<typeof SwappableComponentBlueprint.dataRefs.component.T>,
) {
return new DefaultComponentsApi(
return new DefaultSwappableComponentsApi(
new Map(components.map(entry => [entry.ref.id, entry.loader])),
);
}
@@ -45,8 +45,8 @@ export class DefaultComponentsApi implements ComponentsApi {
this.#components = components;
}
getComponent(
ref: ComponentRef<any>,
getComponentLoader(
ref: SwappableComponentRef<any>,
):
| (() => (props: object) => JSX.Element | null)
| (() => Promise<(props: object) => JSX.Element | null>)
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { DefaultComponentsApi } from './DefaultComponentsApi';
export { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi';
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import { ComponentRef } from '@backstage/frontend-plugin-api';
import { SwappableComponentRef } from '@backstage/frontend-plugin-api';
import { OpaqueType } from '@internal/opaque';
export const OpaqueComponentRef = OpaqueType.create<{
public: ComponentRef;
export const OpaqueSwappableComponentRef = OpaqueType.create<{
public: SwappableComponentRef;
versions: {
readonly version: 'v1';
readonly transformProps?: (props: object) => object;
@@ -28,5 +28,5 @@ export const OpaqueComponentRef = OpaqueType.create<{
};
}>({
versions: ['v1'],
type: '@backstage/ComponentRef',
type: '@backstage/SwappableComponentRef',
});
@@ -15,6 +15,6 @@
*/
export { createExtensionDataContainer } from './createExtensionDataContainer';
export { OpaqueComponentRef } from './InternalComponentRef';
export { OpaqueSwappableComponentRef } from './InternalSwappableComponentRef';
export { OpaqueExtensionDefinition } from './InternalExtensionDefinition';
export { OpaqueFrontendPlugin } from './InternalFrontendPlugin';
+54 -45
View File
@@ -350,34 +350,6 @@ export { bitbucketAuthApiRef };
export { bitbucketServerAuthApiRef };
// @public (undocumented)
export type ComponentRef<
TInnerComponentProps extends {} = {},
TExternalComponentProps extends {} = TInnerComponentProps,
> = {
id: string;
TProps: TInnerComponentProps;
TExternalProps: TExternalComponentProps;
$$type: '@backstage/ComponentRef';
};
// @public
export interface ComponentsApi {
// (undocumented)
getComponent<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
>(
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>,
):
| (() => (props: TInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
| undefined;
}
// @public
export const componentsApiRef: ApiRef<ComponentsApi>;
export { ConfigApi };
export { configApiRef };
@@ -832,8 +804,8 @@ export function createSwappableComponent<
TInnerComponentProps,
TExternalComponentProps
>,
): ((props: TExternalComponentProps) => JSX.Element) & {
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
): ((props: TExternalComponentProps) => JSX.Element | null) & {
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
};
// @public
@@ -901,8 +873,8 @@ export { errorApiRef };
// @public (undocumented)
export const ErrorBoundary: ((
props: CoreErrorBoundaryFallbackProps,
) => JSX.Element) & {
ref: ComponentRef<
) => JSX.Element | null) & {
ref: SwappableComponentRef<
CoreErrorBoundaryFallbackProps,
CoreErrorBoundaryFallbackProps
>;
@@ -1566,8 +1538,11 @@ export const NavItemBlueprint: ExtensionBlueprint<{
// @public (undocumented)
export const NotFoundErrorPage: ((
props: CoreNotFoundErrorPageProps,
) => JSX.Element) & {
ref: ComponentRef<CoreNotFoundErrorPageProps, CoreNotFoundErrorPageProps>;
) => JSX.Element | null) & {
ref: SwappableComponentRef<
CoreNotFoundErrorPageProps,
CoreNotFoundErrorPageProps
>;
};
export { OAuthApi };
@@ -1655,8 +1630,8 @@ export { ProfileInfo };
export { ProfileInfoApi };
// @public (undocumented)
export const Progress: ((props: CoreProgressProps) => JSX.Element) & {
ref: ComponentRef<CoreProgressProps, CoreProgressProps>;
export const Progress: ((props: CoreProgressProps) => JSX.Element | null) & {
ref: SwappableComponentRef<CoreProgressProps, CoreProgressProps>;
};
// @public
@@ -1854,24 +1829,30 @@ export interface SubRouteRef<
// @public
export const SwappableComponentBlueprint: ExtensionBlueprint<{
kind: 'component';
params: <Ref extends ComponentRef<any>>(params: {
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
params: <Ref extends SwappableComponentRef<any>>(params: {
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
: never;
}) => ExtensionBlueprintParams<{
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
@@ -1879,7 +1860,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{
}>;
output: ExtensionDataRef<
{
ref: ComponentRef;
ref: SwappableComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
@@ -1893,7 +1874,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{
dataRefs: {
component: ConfigurableExtensionDataRef<
{
ref: ComponentRef;
ref: SwappableComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
@@ -1904,6 +1885,34 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{
};
}>;
// @public (undocumented)
export type SwappableComponentRef<
TInnerComponentProps extends {} = {},
TExternalComponentProps extends {} = TInnerComponentProps,
> = {
id: string;
TProps: TInnerComponentProps;
TExternalProps: TExternalComponentProps;
$$type: '@backstage/SwappableComponentRef';
};
// @public
export interface SwappableComponentsApi {
// (undocumented)
getComponentLoader<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
>(
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,
):
| (() => (props: TInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
| undefined;
}
// @public
export const swappableComponentsApiRef: ApiRef<SwappableComponentsApi>;
// @public
export const ThemeBlueprint: ExtensionBlueprint<{
kind: 'theme';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ComponentRef } from '../../components';
import { SwappableComponentRef } from '../../components';
import { createApiRef } from '@backstage/core-plugin-api';
/**
@@ -22,12 +22,12 @@ import { createApiRef } from '@backstage/core-plugin-api';
*
* @public
*/
export interface ComponentsApi {
getComponent<
export interface SwappableComponentsApi {
getComponentLoader<
TInnerComponentProps extends {},
TExternalComponentProps extends {} = TInnerComponentProps,
>(
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>,
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,
):
| (() => (props: TInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
@@ -35,10 +35,10 @@ export interface ComponentsApi {
}
/**
* The `ApiRef` of {@link ComponentsApi}.
* The `ApiRef` of {@link SwappableComponentsApi}.
*
* @public
*/
export const componentsApiRef = createApiRef<ComponentsApi>({
id: 'core.components',
export const swappableComponentsApiRef = createApiRef<SwappableComponentsApi>({
id: 'core.swappableComponents',
});
@@ -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';
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentRef } from '../components';
import { SwappableComponentRef } from '../components';
import {
createExtensionBlueprint,
createExtensionBlueprintParams,
@@ -21,14 +21,14 @@ import {
} from '../wiring';
export const componentDataRef = createExtensionDataRef<{
ref: ComponentRef;
ref: SwappableComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
}>().with({ id: 'core.component.component' });
/**
* Blueprint for creating swappable components from a componentRef and a loader
* Blueprint for creating swappable components from a SwappableComponentRef and a loader
*
* @public
*/
@@ -39,11 +39,14 @@ export const SwappableComponentBlueprint = createExtensionBlueprint({
dataRefs: {
component: componentDataRef,
},
defineParams<Ref extends ComponentRef<any>>(params: {
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element)
defineParams<Ref extends SwappableComponentRef<any>>(params: {
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
@@ -14,19 +14,19 @@
* limitations under the License.
*/
import { OpaqueComponentRef } from '@internal/frontend';
import { componentsApiRef, useApi } from '../apis';
import { OpaqueSwappableComponentRef } from '@internal/frontend';
import { swappableComponentsApiRef, useApi } from '../apis';
import { lazy, Suspense } from 'react';
/** @public */
export type ComponentRef<
export type SwappableComponentRef<
TInnerComponentProps extends {} = {},
TExternalComponentProps extends {} = TInnerComponentProps,
> = {
id: string;
TProps: TInnerComponentProps;
TExternalProps: TExternalComponentProps;
$$type: '@backstage/ComponentRef';
$$type: '@backstage/SwappableComponentRef';
};
/**
@@ -47,7 +47,7 @@ export type CreateSwappableComponentOptions<
const useComponentRefApi = () => {
try {
return useApi(componentsApiRef);
return useApi(swappableComponentsApiRef);
} catch (e) {
return undefined;
}
@@ -59,9 +59,9 @@ function makeComponentFromRef<
>({
ref,
}: {
ref: ComponentRef<InternalComponentProps, ExternalComponentProps>;
ref: SwappableComponentRef<InternalComponentProps, ExternalComponentProps>;
}): (props: ExternalComponentProps) => JSX.Element {
const internalRef = OpaqueComponentRef.toInternal(ref);
const internalRef = OpaqueSwappableComponentRef.toInternal(ref);
const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
<div data-testid={ref.id} {...p} />
);
@@ -69,7 +69,7 @@ function makeComponentFromRef<
const ComponentRefImpl = (props: ExternalComponentProps) => {
const api = useComponentRefApi();
const ComponentOrPromise =
api?.getComponent<any>(ref)?.() ??
api?.getComponentLoader<any>(ref)?.() ??
internalRef.loader?.() ??
FallbackComponent;
@@ -108,25 +108,26 @@ export function createSwappableComponent<
TInnerComponentProps,
TExternalComponentProps
>,
): ((props: TExternalComponentProps) => JSX.Element) & {
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
): ((props: TExternalComponentProps) => JSX.Element | null) & {
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
} {
const ref = OpaqueComponentRef.createInstance('v1', {
const ref = OpaqueSwappableComponentRef.createInstance('v1', {
id: options.id,
TProps: null as unknown as TInnerComponentProps,
TExternalProps: null as unknown as TExternalComponentProps,
toString() {
return `ComponentRef{id=${options.id}}`;
return `SwappableComponentRef{id=${options.id}}`;
},
loader: options.loader as (typeof OpaqueComponentRef.TInternal)['loader'],
loader:
options.loader as (typeof OpaqueSwappableComponentRef.TInternal)['loader'],
transformProps:
options.transformProps as (typeof OpaqueComponentRef.TInternal)['transformProps'],
options.transformProps as (typeof OpaqueSwappableComponentRef.TInternal)['transformProps'],
});
const component = makeComponentFromRef({ ref });
Object.assign(component, { ref });
return component as {
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
} & ((props: object) => JSX.Element);
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
} & ((props: object) => JSX.Element | null);
}
@@ -21,7 +21,7 @@ export {
export {
createSwappableComponent,
type CreateSwappableComponentOptions,
type ComponentRef,
type SwappableComponentRef,
} from './createSwappableComponent';
export { useAppNode } from './AppNodeProvider';
export * from './DefaultSwappableComponents';
+93 -57
View File
@@ -8,7 +8,6 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api';
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
import { ApiFactory } from '@backstage/frontend-plugin-api';
import { AppTheme } from '@backstage/frontend-plugin-api';
import { ComponentRef } from '@backstage/frontend-plugin-api';
import { ComponentType } from 'react';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
@@ -23,6 +22,7 @@ import { NavContentComponent } from '@backstage/frontend-plugin-api';
import { ReactNode } from 'react';
import { RouteRef } from '@backstage/frontend-plugin-api';
import { SignInPageProps } from '@backstage/core-plugin-api';
import { SwappableComponentRef } from '@backstage/frontend-plugin-api';
import { TranslationMessages } from '@backstage/frontend-plugin-api';
import { TranslationResource } from '@backstage/frontend-plugin-api';
@@ -315,38 +315,6 @@ const appPlugin: FrontendPlugin<
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/components': ExtensionDefinition<{
config: {};
configInput: {};
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
inputs: {
components: ExtensionInput<
ConfigurableExtensionDataRef<
{
ref: ComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
},
'core.component.component',
{}
>,
{
singleton: false;
optional: false;
}
>;
};
kind: 'api';
name: 'components';
params: <
TApi,
TImpl extends TApi,
TDeps extends { [name in string]: unknown },
>(
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/dialog': ExtensionDefinition<{
kind: 'api';
name: 'dialog';
@@ -616,6 +584,38 @@ const appPlugin: FrontendPlugin<
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/swappable-components': ExtensionDefinition<{
config: {};
configInput: {};
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
inputs: {
components: ExtensionInput<
ConfigurableExtensionDataRef<
{
ref: SwappableComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
},
'core.component.component',
{}
>,
{
singleton: false;
optional: false;
}
>;
};
kind: 'api';
name: 'swappable-components';
params: <
TApi,
TImpl extends TApi,
TDeps extends { [name in string]: unknown },
>(
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/translations': ExtensionDefinition<{
config: {};
configInput: {};
@@ -735,7 +735,7 @@ const appPlugin: FrontendPlugin<
configInput: {};
output: ExtensionDataRef<
{
ref: ComponentRef;
ref: SwappableComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
@@ -744,13 +744,19 @@ const appPlugin: FrontendPlugin<
{}
>;
inputs: {};
params: <Ref extends ComponentRef<any>>(params: {
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
params: <Ref extends SwappableComponentRef<any>>(params: {
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<
infer IInnerComponentProps,
any
>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<
@@ -758,12 +764,18 @@ const appPlugin: FrontendPlugin<
>)
: never;
}) => ExtensionBlueprintParams<{
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<
infer IInnerComponentProps,
any
>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<
@@ -779,7 +791,7 @@ const appPlugin: FrontendPlugin<
configInput: {};
output: ExtensionDataRef<
{
ref: ComponentRef;
ref: SwappableComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
@@ -788,13 +800,19 @@ const appPlugin: FrontendPlugin<
{}
>;
inputs: {};
params: <Ref extends ComponentRef<any>>(params: {
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
params: <Ref extends SwappableComponentRef<any>>(params: {
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<
infer IInnerComponentProps,
any
>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<
@@ -802,12 +820,18 @@ const appPlugin: FrontendPlugin<
>)
: never;
}) => ExtensionBlueprintParams<{
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<
infer IInnerComponentProps,
any
>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<
@@ -823,7 +847,7 @@ const appPlugin: FrontendPlugin<
configInput: {};
output: ExtensionDataRef<
{
ref: ComponentRef;
ref: SwappableComponentRef;
loader:
| (() => (props: {}) => JSX.Element | null)
| (() => Promise<(props: {}) => JSX.Element | null>);
@@ -832,13 +856,19 @@ const appPlugin: FrontendPlugin<
{}
>;
inputs: {};
params: <Ref extends ComponentRef<any>>(params: {
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
params: <Ref extends SwappableComponentRef<any>>(params: {
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<
infer IInnerComponentProps,
any
>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<
@@ -846,12 +876,18 @@ const appPlugin: FrontendPlugin<
>)
: never;
}) => ExtensionBlueprintParams<{
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
component: Ref extends SwappableComponentRef<
any,
infer IExternalComponentProps
>
? {
ref: Ref;
} & ((props: IExternalComponentProps) => JSX.Element)
} & ((props: IExternalComponentProps) => JSX.Element | null)
: never;
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
loader: Ref extends SwappableComponentRef<
infer IInnerComponentProps,
any
>
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
| (() => Promise<
@@ -18,16 +18,16 @@ import {
SwappableComponentBlueprint,
createExtensionInput,
ApiBlueprint,
componentsApiRef,
swappableComponentsApiRef,
} from '@backstage/frontend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { DefaultComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/ComponentsApi';
import { DefaultSwappableComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi';
/**
* Contains the shareable components installed into the app.
*/
export const ComponentsApi = ApiBlueprint.makeWithOverrides({
name: 'components',
export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({
name: 'swappable-components',
inputs: {
components: createExtensionInput(
[SwappableComponentBlueprint.dataRefs.component],
@@ -37,10 +37,10 @@ export const ComponentsApi = ApiBlueprint.makeWithOverrides({
factory: (originalFactory, { inputs }) => {
return originalFactory(defineParams =>
defineParams({
api: componentsApiRef,
api: swappableComponentsApiRef,
deps: {},
factory: () =>
DefaultComponentsApi.fromComponents(
DefaultSwappableComponentsApi.fromComponents(
inputs.components.map(i =>
i.get(SwappableComponentBlueprint.dataRefs.component),
),
+1 -1
View File
@@ -20,7 +20,7 @@ export { AppNav } from './AppNav';
export { AppRoot } from './AppRoot';
export { AppRoutes } from './AppRoutes';
export { AppThemeApi, DarkTheme, LightTheme } from './AppThemeApi';
export { ComponentsApi } from './ComponentsApi';
export { SwappableComponentsApi } from './SwappableComponentsApi';
export { IconsApi } from './IconsApi';
export { FeatureFlagsApi } from './FeatureFlagsApi';
export { TranslationsApi } from './TranslationsApi';
+2 -2
View File
@@ -25,7 +25,7 @@ import {
AppThemeApi,
DarkTheme,
LightTheme,
ComponentsApi,
SwappableComponentsApi,
IconsApi,
FeatureFlagsApi,
TranslationsApi,
@@ -54,7 +54,7 @@ export const appPlugin = createFrontendPlugin({
AppThemeApi,
DarkTheme,
LightTheme,
ComponentsApi,
SwappableComponentsApi,
IconsApi,
FeatureFlagsApi,
TranslationsApi,