From f8b8e2fe6f0adcbba3c8eb43192e671df1473e67 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 26 Feb 2024 18:48:05 +0100 Subject: [PATCH] refactor: move to core components and rename it Signed-off-by: Camila Belo --- .changeset/friendly-news-sin.md | 4 +- .changeset/red-taxis-swim.md | 2 +- packages/core-compat-api/api-report.md | 11 --- packages/core-compat-api/package.json | 1 - .../src/components/SystemIcon.tsx | 78 ------------------- .../core-compat-api/src/components/index.ts | 17 ---- packages/core-compat-api/src/index.ts | 2 - packages/core-components/api-report.md | 14 +++- .../src/icons/icons.test.tsx} | 26 +++---- packages/core-components/src/icons/icons.tsx | 53 +++++++++---- plugins/api-docs/src/alpha.tsx | 4 +- yarn.lock | 1 - 12 files changed, 66 insertions(+), 147 deletions(-) delete mode 100644 packages/core-compat-api/src/components/SystemIcon.tsx delete mode 100644 packages/core-compat-api/src/components/index.ts rename packages/{core-compat-api/src/components/SystemIcon.test.tsx => core-components/src/icons/icons.test.tsx} (59%) diff --git a/.changeset/friendly-news-sin.md b/.changeset/friendly-news-sin.md index ca337370af..29843c54fb 100644 --- a/.changeset/friendly-news-sin.md +++ b/.changeset/friendly-news-sin.md @@ -1,5 +1,5 @@ --- -'@backstage/core-compat-api': patch +'@backstage/core-components': minor --- -Create an abstraction to consume legacy system icons in new system extensions. +Create a component abstraction to consume system icons. diff --git a/.changeset/red-taxis-swim.md b/.changeset/red-taxis-swim.md index 7cd2428fac..13c758d552 100644 --- a/.changeset/red-taxis-swim.md +++ b/.changeset/red-taxis-swim.md @@ -2,4 +2,4 @@ '@backstage/plugin-api-docs': patch --- -Use the system icon compatibility component in the navigation item extension. +Use the `AppIcon` component in the navigation item extension. diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index 82f13f1482..be80ca50e9 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -8,11 +8,9 @@ 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'; @@ -71,15 +69,6 @@ export class NoOpAnalyticsApi implements AnalyticsApi, AnalyticsApi_2 { captureEvent(_event: AnalyticsEvent | AnalyticsEvent_2): void; } -// @public -export function SystemIcon(props: SystemIconProps): React_2.JSX.Element; - -// @public -export type SystemIconProps = ComponentProps & { - keys: string | string[]; - Fallback?: IconComponent; -}; - // @public export type ToNewRouteRef = T extends RouteRef diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 21bf8ede49..ee70bc8089 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -45,7 +45,6 @@ "@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.tsx b/packages/core-compat-api/src/components/SystemIcon.tsx deleted file mode 100644 index 7aa7110a97..0000000000 --- a/packages/core-compat-api/src/components/SystemIcon.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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, { ComponentProps } from 'react'; -import { useApp, IconComponent } from '@backstage/core-plugin-api'; -import { compatWrapper } from '../compatWrapper'; - -/** - * @public - * Props for the SystemIcon component. - */ -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 { keys, Fallback = () => null, ...rest } = props; - const app = useApp(); - for (const key of Array.isArray(keys) ? keys : [keys]) { - const Icon = app.getSystemIcon(key); - if (Icon) return ; - } - return ; -} - -/** - * @public - * SystemIcon is a component that renders a system icon by its id. - * @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) { - 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/packages/core-compat-api/src/components/index.ts b/packages/core-compat-api/src/components/index.ts deleted file mode 100644 index e02ab727c6..0000000000 --- a/packages/core-compat-api/src/components/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { SystemIcon, type SystemIconProps } from './SystemIcon'; diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index 3da227e554..88e1892eac 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -17,8 +17,6 @@ export * from './compatWrapper'; export * from './apis'; -export * from './components'; - export { convertLegacyApp } from './convertLegacyApp'; export { convertLegacyRouteRef, diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 27b9c62653..7641386176 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -66,6 +66,15 @@ export type AlertDisplayProps = { transientTimeoutMs?: number; }; +// @public +export function AppIcon(props: AppIconProps): React_2.JSX.Element; + +// @public +export type AppIconProps = IconComponentProps & { + id: string; + Fallback?: IconComponent; +}; + // @public export const AutoLogout: (props: AutoLogoutProps) => JSX.Element | null; @@ -130,8 +139,6 @@ export type BreadcrumbsClickableTextClassKey = 'root'; // @public (undocumented) export type BreadcrumbsStyledBoxClassKey = 'root'; -// Warning: (ae-forgotten-export) The symbol "IconComponentProps" needs to be exported by the entry point index.d.ts -// // @public export function BrokenImageIcon(props: IconComponentProps): React_2.JSX.Element; @@ -541,6 +548,9 @@ export type HorizontalScrollGridClassKey = | 'buttonLeft' | 'buttonRight'; +// @public +export type IconComponentProps = ComponentProps; + // @public (undocumented) export function IconLinkVertical({ color, diff --git a/packages/core-compat-api/src/components/SystemIcon.test.tsx b/packages/core-components/src/icons/icons.test.tsx similarity index 59% rename from packages/core-compat-api/src/components/SystemIcon.test.tsx rename to packages/core-components/src/icons/icons.test.tsx index 12b8f41ea5..7cd54f37d0 100644 --- a/packages/core-compat-api/src/components/SystemIcon.test.tsx +++ b/packages/core-components/src/icons/icons.test.tsx @@ -17,24 +17,24 @@ import React from 'react'; import { screen } from '@testing-library/react'; import { renderInTestApp } from '@backstage/test-utils'; -import { SystemIcon } from './SystemIcon'; +import { AppIcon } from './icons'; -describe('SystemIcon', () => { +describe('AppIcon', () => { it('should render the correct system icon', async () => { - const { container } = await renderInTestApp(); - expect(container.querySelector('svg')).toBeDefined(); + await renderInTestApp(); + expect(screen.getByTestId('Api Icon')).toBeDefined(); }); - it('should render the first found icon 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 () => { + it('should render the default fallback component', async () => { await renderInTestApp( -
Fallback Icon
} />, + , + ); + expect(screen.getByTestId('Fallback Icon')).toBeDefined(); + }); + + it('should render the custom fallback component', async () => { + await renderInTestApp( +
Fallback Icon
} />, ); expect(screen.getByText('Fallback Icon')).toBeInTheDocument(); }); diff --git a/packages/core-components/src/icons/icons.tsx b/packages/core-components/src/icons/icons.tsx index 8d22af4664..ca3b6fa76a 100644 --- a/packages/core-components/src/icons/icons.tsx +++ b/packages/core-components/src/icons/icons.tsx @@ -18,61 +18,80 @@ import { IconComponent, useApp } from '@backstage/core-plugin-api'; import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage'; import React, { ComponentProps } from 'react'; -type IconComponentProps = ComponentProps; +/** + * @public + * Props for the {@link @backstage/core-plugin-api#IconComponent} component. + */ +export type IconComponentProps = ComponentProps; -function useSystemIcon(key: string, props: IconComponentProps) { +/** + * @public + * Props for the {@link AppIcon} component. + */ +export type AppIconProps = IconComponentProps & { + // The key of the system icon to render. + id: string; + // An optional fallback icon component to render when the system icon is not found. + // Default to () => null. + Fallback?: IconComponent; +}; + +/** + * @public + * A component that renders a system icon by its id. + */ +export function AppIcon(props: AppIconProps) { + const { id: key, Fallback = MuiBrokenImageIcon, ...rest } = props; const app = useApp(); - const Icon = app.getSystemIcon(key); - return Icon ? : ; + const Icon = app.getSystemIcon(key) ?? Fallback; + return ; } // Should match the list of overridable system icon keys in @backstage/core-app-api /** * Broken Image Icon - * * @public - * */ export function BrokenImageIcon(props: IconComponentProps) { - return useSystemIcon('brokenImage', props); + return ; } /** @public */ export function CatalogIcon(props: IconComponentProps) { - return useSystemIcon('catalog', props); + return ; } /** @public */ export function ChatIcon(props: IconComponentProps) { - return useSystemIcon('chat', props); + return ; } /** @public */ export function DashboardIcon(props: IconComponentProps) { - return useSystemIcon('dashboard', props); + return ; } /** @public */ export function DocsIcon(props: IconComponentProps) { - return useSystemIcon('docs', props); + return ; } /** @public */ export function EmailIcon(props: IconComponentProps) { - return useSystemIcon('email', props); + return ; } /** @public */ export function GitHubIcon(props: IconComponentProps) { - return useSystemIcon('github', props); + return ; } /** @public */ export function GroupIcon(props: IconComponentProps) { - return useSystemIcon('group', props); + return ; } /** @public */ export function HelpIcon(props: IconComponentProps) { - return useSystemIcon('help', props); + return ; } /** @public */ export function UserIcon(props: IconComponentProps) { - return useSystemIcon('user', props); + return ; } /** @public */ export function WarningIcon(props: IconComponentProps) { - return useSystemIcon('warning', props); + return ; } diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index fd28a59893..29bb0bcfa9 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -27,7 +27,6 @@ import { } from '@backstage/frontend-plugin-api'; import { - SystemIcon, compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; @@ -45,11 +44,12 @@ import { import { defaultDefinitionWidgets } from './components/ApiDefinitionCard'; import { rootRoute, registerComponentRouteRef } from './routes'; import { apiDocsConfigRef } from './config'; +import { AppIcon } from '@backstage/core-components'; const apiDocsNavItem = createNavItemExtension({ title: 'APIs', routeRef: convertLegacyRouteRef(rootRoute), - icon: () => , + icon: () => , }); const apiDocsConfigApi = createApiExtension({ diff --git a/yarn.lock b/yarn.lock index c8deda0b1d..06edaceef5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3818,7 +3818,6 @@ __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