diff --git a/.changeset/eight-doors-matter.md b/.changeset/eight-doors-matter.md new file mode 100644 index 0000000000..7dbb56b6c3 --- /dev/null +++ b/.changeset/eight-doors-matter.md @@ -0,0 +1,19 @@ +--- +'@backstage/core-api': patch +'@backstage/plugin-catalog': patch +--- + +Minor refactoring of BackstageApp.getSystemIcons to support custom registered +icons. Custom Icons can be added using: + +```tsx +import AlarmIcon from '@material-ui/icons/Alarm'; +import MyPersonIcon from './MyPerson'; + +const app = createApp({ + icons: { + user: MyPersonIcon // override system icon + alert: AlarmIcon, // Custom icon + }, +}); +``` diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 70fceb8a01..cc673c7769 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -39,10 +39,15 @@ import { EntityPage } from './components/catalog/EntityPage'; import Root from './components/Root'; import { providers } from './identityProviders'; import * as plugins from './plugins'; +import AlarmIcon from '@material-ui/icons/Alarm'; const app = createApp({ apis, plugins: Object.values(plugins), + icons: { + // Custom icon example + alert: AlarmIcon, + }, components: { SignInPage: props => { return ( diff --git a/packages/catalog-model/examples/components/artist-lookup-component.yaml b/packages/catalog-model/examples/components/artist-lookup-component.yaml index 29c892274d..edd9b8fcf9 100644 --- a/packages/catalog-model/examples/components/artist-lookup-component.yaml +++ b/packages/catalog-model/examples/components/artist-lookup-component.yaml @@ -25,6 +25,9 @@ metadata: - url: https://example.com/web title: Website icon: web + - url: https://example.com/alert + title: Alerts + icon: alert spec: type: service lifecycle: experimental diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 2aaeba9796..f50bcdd015 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -48,7 +48,7 @@ import { routeElementDiscoverer, traverseElementTree, } from '../extensions/traversal'; -import { IconComponent, SystemIconKey, SystemIcons } from '../icons'; +import { IconComponent, IconComponentMap, IconKey } from '../icons'; import { BackstagePlugin } from '../plugin'; import { RouteRef } from '../routing'; import { @@ -95,7 +95,7 @@ export function generateBoundRoutes( type FullAppOptions = { apis: Iterable; - icons: SystemIcons; + icons: IconComponentMap; plugins: BackstagePlugin[]; components: AppComponents; themes: AppTheme[]; @@ -144,7 +144,7 @@ export class PrivateAppImpl implements BackstageApp { private configApi?: ConfigApi; private readonly apis: Iterable; - private readonly icons: SystemIcons; + private readonly icons: IconComponentMap; private readonly plugins: BackstagePlugin[]; private readonly components: AppComponents; private readonly themes: AppTheme[]; @@ -169,7 +169,7 @@ export class PrivateAppImpl implements BackstageApp { return this.plugins; } - getSystemIcon(key: SystemIconKey): IconComponent { + getSystemIcon(key: IconKey): IconComponent { return this.icons[key]; } diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index b6b1002d12..1970868ca6 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -15,7 +15,7 @@ */ import { ComponentType } from 'react'; -import { IconComponent, SystemIconKey, SystemIcons } from '../icons'; +import { IconComponent, IconComponentMap, IconKey } from '../icons'; import { BackstagePlugin, AnyExternalRoutes } from '../plugin/types'; import { RouteRef } from '../routing'; import { AnyApiFactory } from '../apis'; @@ -94,7 +94,7 @@ export type AppOptions = { /** * Supply icons to override the default ones. */ - icons?: Partial; + icons?: IconComponentMap; /** * A list of all plugins to include in the app. @@ -169,9 +169,9 @@ export type BackstageApp = { getPlugins(): BackstagePlugin[]; /** - * Get a common icon for this app. + * Get a common or custom icon for this app. */ - getSystemIcon(key: SystemIconKey): IconComponent; + getSystemIcon(key: IconKey): IconComponent; /** * Provider component that should wrap the Router created with getRouter() diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx index 50c4b68e43..0c2b580ea7 100644 --- a/packages/core-api/src/icons/icons.tsx +++ b/packages/core-api/src/icons/icons.tsx @@ -15,15 +15,19 @@ */ import { SvgIconProps } from '@material-ui/core'; +import MuiDashboardIcon from '@material-ui/icons/Dashboard'; +import MuiHelpIcon from '@material-ui/icons/Help'; import PeopleIcon from '@material-ui/icons/People'; import PersonIcon from '@material-ui/icons/Person'; import React from 'react'; import { useApp } from '../app/AppContext'; -import { IconComponent, SystemIconKey, SystemIcons } from './types'; +import { IconComponent, SystemIconKey, IconComponentMap } from './types'; -export const defaultSystemIcons: SystemIcons = { +export const defaultSystemIcons: IconComponentMap = { user: PersonIcon, group: PeopleIcon, + dashboard: MuiDashboardIcon, + help: MuiHelpIcon, }; const overridableSystemIcon = (key: SystemIconKey): IconComponent => { @@ -35,5 +39,7 @@ const overridableSystemIcon = (key: SystemIconKey): IconComponent => { return Component; }; -export const UserIcon = overridableSystemIcon('user'); +export const DashboardIcon = overridableSystemIcon('dashboard'); export const GroupIcon = overridableSystemIcon('group'); +export const HelpIcon = overridableSystemIcon('help'); +export const UserIcon = overridableSystemIcon('user'); diff --git a/packages/core-api/src/icons/types.ts b/packages/core-api/src/icons/types.ts index 30e0ba53b2..be24b223ae 100644 --- a/packages/core-api/src/icons/types.ts +++ b/packages/core-api/src/icons/types.ts @@ -17,6 +17,8 @@ import { ComponentType } from 'react'; import { SvgIconProps } from '@material-ui/core'; +export type SystemIconKey = 'user' | 'group' | 'dashboard' | 'help'; + export type IconComponent = ComponentType; -export type SystemIconKey = 'user' | 'group'; -export type SystemIcons = { [key in SystemIconKey]: IconComponent }; +export type IconKey = SystemIconKey | string; +export type IconComponentMap = { [key in IconKey]: IconComponent }; diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index 60fb9c3cf5..b0c3db2333 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -import { IconComponent, InfoCard, useApp } from '@backstage/core'; +import { IconComponent, IconKey, InfoCard, useApp } from '@backstage/core'; import { useEntity } from '@backstage/plugin-catalog-react'; import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; @@ -33,9 +33,8 @@ export const EntityLinksCard = ({ cols = undefined }: Props) => { const { entity } = useEntity(); const app = useApp(); - // TODO: Refactor App.icons & App.getSystemIcon to support custom icons - const iconResolver = (key: string | undefined): IconComponent => { - return app.getSystemIcon(key as any) ?? LanguageIcon; + const iconResolver = (key: IconKey | undefined): IconComponent => { + return app.getSystemIcon(key ?? '') ?? LanguageIcon; }; const links = entity?.metadata?.links;