diff --git a/.changeset/friendly-news-sin.md b/.changeset/friendly-news-sin.md new file mode 100644 index 0000000000..af64190f9c --- /dev/null +++ b/.changeset/friendly-news-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Create a component abstraction to consume system icons. diff --git a/.changeset/red-taxis-swim.md b/.changeset/red-taxis-swim.md new file mode 100644 index 0000000000..13c758d552 --- /dev/null +++ b/.changeset/red-taxis-swim.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Use the `AppIcon` component in the navigation item extension. 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-components/src/icons/icons.test.tsx b/packages/core-components/src/icons/icons.test.tsx new file mode 100644 index 0000000000..7cd54f37d0 --- /dev/null +++ b/packages/core-components/src/icons/icons.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 { AppIcon } from './icons'; + +describe('AppIcon', () => { + it('should render the correct system icon', async () => { + await renderInTestApp(); + expect(screen.getByTestId('Api Icon')).toBeDefined(); + }); + + it('should render the default fallback component', async () => { + await renderInTestApp( + , + ); + 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 38b8db89c6..8cb43e9108 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -30,7 +30,6 @@ import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { useApp } from '@backstage/core-plugin-api'; import { createEntityCardExtension, @@ -45,17 +44,12 @@ import { import { defaultDefinitionWidgets } from './components/ApiDefinitionCard'; import { rootRoute, registerComponentRouteRef } from './routes'; import { apiDocsConfigRef } from './config'; - -function ApiIcon() { - const app = useApp(); - const KindApiSystemIcon = app.getSystemIcon('kind:api')!; - return ; -} +import { AppIcon } from '@backstage/core-components'; const apiDocsNavItem = createNavItemExtension({ title: 'APIs', routeRef: convertLegacyRouteRef(rootRoute), - icon: () => compatWrapper(), + icon: () => compatWrapper(), }); const apiDocsConfigApi = createApiExtension({