From 77dc92eecc3a2d3863446273e376686a4e4acad9 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 15 Feb 2024 14:30:34 +0100 Subject: [PATCH] refactor(core-compat-api): apply review suggestions Signed-off-by: Camila Belo --- packages/core-compat-api/api-report.md | 8 +-- packages/core-compat-api/package.json | 1 + .../src/components/SystemIcon.test.tsx | 41 +++++++++++++++ .../src/components/SystemIcon.tsx | 52 ++++++++++++++----- plugins/api-docs/src/alpha.tsx | 2 +- yarn.lock | 1 + 6 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 packages/core-compat-api/src/components/SystemIcon.test.tsx diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index 828d3d9d6a..82f13f1482 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -8,9 +8,11 @@ import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/core-plugin-api'; +import { ComponentProps } from 'react'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -73,9 +75,9 @@ export class NoOpAnalyticsApi implements AnalyticsApi, AnalyticsApi_2 { export function SystemIcon(props: SystemIconProps): React_2.JSX.Element; // @public -export type SystemIconProps = { - id: string; - fallback?: JSX.Element; +export type SystemIconProps = ComponentProps & { + keys: string | string[]; + Fallback?: IconComponent; }; // @public diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index ee70bc8089..21bf8ede49 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -45,6 +45,7 @@ "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-puppetdb": "workspace:^", "@backstage/plugin-stackstorm": "workspace:^", + "@backstage/test-utils": "workspace:^", "@oriflame/backstage-plugin-score-card": "^0.8.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0" diff --git a/packages/core-compat-api/src/components/SystemIcon.test.tsx b/packages/core-compat-api/src/components/SystemIcon.test.tsx new file mode 100644 index 0000000000..ee3a20308d --- /dev/null +++ b/packages/core-compat-api/src/components/SystemIcon.test.tsx @@ -0,0 +1,41 @@ +/* + * Copyright 2024 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 from 'react'; +import { screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { SystemIcon } from './SystemIcon'; + +describe('SystemIcon', () => { + it('should render the correct system icon', async () => { + const { container } = await renderInTestApp(); + expect(container.querySelector('svg')).toBeDefined(); + }); + + it('should render the first found ico when multiple keys are provided', async () => { + const { container } = await renderInTestApp( + , + ); + expect(container.querySelector('svg')).toBeDefined(); + }); + + it('should render the fallback component when no system icon is found', async () => { + await renderInTestApp( +
Fallback Icon
} />, + ); + expect(screen.getByText('Fallback Icon')).toBeInTheDocument(); + }); +}); diff --git a/packages/core-compat-api/src/components/SystemIcon.tsx b/packages/core-compat-api/src/components/SystemIcon.tsx index 00c20da5db..7aa7110a97 100644 --- a/packages/core-compat-api/src/components/SystemIcon.tsx +++ b/packages/core-compat-api/src/components/SystemIcon.tsx @@ -14,26 +14,30 @@ * limitations under the License. */ -import { useApp } from '@backstage/core-plugin-api'; -import React from 'react'; +import React, { ComponentProps } from 'react'; +import { useApp, IconComponent } from '@backstage/core-plugin-api'; import { compatWrapper } from '../compatWrapper'; /** * @public - * Props for the System Icon component. + * Props for the SystemIcon component. */ -export type SystemIconProps = { - // The id of the system icon to render. - id: string; - // An optional fallback element to render when the system icon is not found. - fallback?: JSX.Element; +export type SystemIconProps = ComponentProps & { + // The id of the system icon to render, if provided as an array, the first icon found will be rendered. + keys: string | string[]; + // An optional fallback icon component to render when the system icon is not found. + // Default to () => null. + Fallback?: IconComponent; }; function SystemIcon(props: SystemIconProps) { - const { id, fallback = null } = props; + const { keys, Fallback = () => null, ...rest } = props; const app = useApp(); - const Component = app.getSystemIcon(id); - return Component ? : fallback; + for (const key of Array.isArray(keys) ? keys : [keys]) { + const Icon = app.getSystemIcon(key); + if (Icon) return ; + } + return ; } /** @@ -42,11 +46,33 @@ function SystemIcon(props: SystemIconProps) { * @example * Rendering the "kind:api" icon: * ```tsx - * + * + * ``` + * @example + * Providing multiple icon ids: + * ```tsx + * + * ``` + * @example + * Customizing the fallback icon: + * ```tsx + * + * ``` + * @example + * Customizing the icon font size: + * ```tsx + * * ``` */ function CompatSystemIcon(props: SystemIconProps) { - return compatWrapper(); + try { + // Check if the app context is available + useApp(); + return ; + } catch { + // Fallback to the compat wrapper if the app context is not available + return compatWrapper(); + } } export { CompatSystemIcon as SystemIcon }; diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index 4835cd17ed..fd28a59893 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -49,7 +49,7 @@ import { apiDocsConfigRef } from './config'; const apiDocsNavItem = createNavItemExtension({ title: 'APIs', routeRef: convertLegacyRouteRef(rootRoute), - icon: () => , + icon: () => , }); const apiDocsConfigApi = createApiExtension({ diff --git a/yarn.lock b/yarn.lock index 06edaceef5..c8deda0b1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3818,6 +3818,7 @@ __metadata: "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-puppetdb": "workspace:^" "@backstage/plugin-stackstorm": "workspace:^" + "@backstage/test-utils": "workspace:^" "@backstage/version-bridge": "workspace:^" "@oriflame/backstage-plugin-score-card": ^0.8.0 "@testing-library/jest-dom": ^6.0.0