diff --git a/docs/assets/getting-started/add-icons-links-example.png b/docs/assets/getting-started/add-icons-links-example.png new file mode 100644 index 0000000000..dfff84b79c Binary files /dev/null and b/docs/assets/getting-started/add-icons-links-example.png differ diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index 9ca82d01a1..fc39412d61 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -338,6 +338,57 @@ const app = createApp({ [...] ``` +## Adding Icons + +You can add more icons, if the [default icons](https://github.com/backstage/backstage/blob/8d83f5cb4fa0544b0f9160ac22bd5f0f1fe285c6/packages/app-defaults/src/defaults/icons.tsx#L38) do not fit your needs, so that they can be used in other places like for Links in your entities. For this example we'll be using icons from[Material UI](https://v4.mui.com/components/material-icons/) and specifically the `AlarmIcon`. Here's how to do that: + +1. First you will want to open your `App.tsx` in `/packages/app/src` +2. Then you want to import your icon, add this to the rest of your imports: `import AlarmIcon from '@material-ui/icons/Alarm';` +3. Next you want to add the icon like this to your `createApp`: + + ```diff + const app = createApp({ + apis: ..., + plugins: ..., + + icons: { + + alert: AlarmIcon, + + }, + themes: ..., + components: ..., + }); + ``` + +4. Now we can reference `alert` for our icon in our entity links like this: + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: artist-lookup + description: Artist Lookup + links: + - url: https://example.com/alert + title: Alerts + icon: alert + ``` + + And this is the result: + + ![Example Link with Alert icon](../assets/getting-started/add-icons-links-example.png) + + Another way you can use these icons is from the `AppContext` like this: + + ```ts + import { useApp } from '@backstage/core-plugin-api'; + + const app = useApp(); + const alertIcon = app.getSystemIcon('alert'); + ``` + + You might want to use this method if you have an icon you want to use in several locations. + +Note: If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon` + ## Custom Homepage In addition to a custom theme, a custom logo, you can also customize the diff --git a/packages/core-app-api/src/app/AppContext.test.tsx b/packages/core-app-api/src/app/AppContext.test.tsx index bc67ee3a9e..a27b7c6f35 100644 --- a/packages/core-app-api/src/app/AppContext.test.tsx +++ b/packages/core-app-api/src/app/AppContext.test.tsx @@ -36,6 +36,7 @@ describe('v1 consumer', () => { getPlugins: jest.fn(), getComponents: jest.fn(), getSystemIcon: jest.fn(), + getSystemIcons: jest.fn(), }; const renderedHook = renderHook(() => useMockAppV1(), { diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 72c69f7da4..41c57afb98 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -71,6 +71,7 @@ import { AppComponents, AppConfigLoader, AppContext, + AppIcons, AppOptions, BackstageApp, SignInPageProps, @@ -153,6 +154,10 @@ class AppContextImpl implements AppContext { return this.app.getSystemIcon(key); } + getSystemIcons(): AppIcons & { [key in string]: IconComponent } { + return this.app.getSystemIcons(); + } + getComponents(): AppComponents { return this.app.getComponents(); } @@ -194,6 +199,10 @@ export class AppManager implements BackstageApp { return this.icons[key]; } + getSystemIcons(): AppIcons & { [key in string]: IconComponent } { + return this.icons; + } + getComponents(): AppComponents { return this.components; } diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 979fb0496e..b196e2cf90 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -328,6 +328,11 @@ export type AppContext = { */ getSystemIcon(key: string): IconComponent | undefined; + /** + * Get a list of common and custom icons for this app. + */ + getSystemIcons(): AppIcons & { [key in string]: IconComponent }; + /** * Get the components registered for various purposes in the app. */ diff --git a/packages/core-app-api/src/routing/RoutingProvider.test.tsx b/packages/core-app-api/src/routing/RoutingProvider.test.tsx index dec92072fc..c559c0fff0 100644 --- a/packages/core-app-api/src/routing/RoutingProvider.test.tsx +++ b/packages/core-app-api/src/routing/RoutingProvider.test.tsx @@ -124,6 +124,7 @@ const Extension5 = plugin.provide( const mockContext = { getComponents: () => ({ Progress: () => null } as any), getSystemIcon: jest.fn(), + getSystemIcons: jest.fn(), getPlugins: jest.fn(), }; diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 49e0be3800..e0686596aa 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -149,6 +149,9 @@ export type AppComponents = { export type AppContext = { getPlugins(): BackstagePlugin_2[]; getSystemIcon(key: string): IconComponent_2 | undefined; + getSystemIcons(): AppIcons & { + [key in string]: IconComponent_2; + }; getComponents(): AppComponents; }; @@ -780,4 +783,8 @@ export function withApis(apis: TypesToApiRefs):

( (props: React_2.PropsWithChildren>): JSX.Element; displayName: string; }; + +// Warnings were encountered during analysis: +// +// /backstage/dist-types/packages/core-app-api/src/app/types.d.ts:277:5 - (ae-forgotten-export) The symbol "AppIcons" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/user-settings/src/components/General/UserSettingsAvailableIconsTable.tsx b/plugins/user-settings/src/components/General/UserSettingsAvailableIconsTable.tsx new file mode 100644 index 0000000000..d0e6034c66 --- /dev/null +++ b/plugins/user-settings/src/components/General/UserSettingsAvailableIconsTable.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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 { IconComponent, useApp } from '@backstage/core-plugin-api'; +import { Table, TableColumn } from '@backstage/core-components'; + +import { Box } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; +import React from 'react'; + +type SystemIcon = { + key: string; + icon: IconComponent; +}; + +const columns: TableColumn[] = [ + { + title: 'Icon', + field: 'icon', + width: 'auto', + render: (row: Partial) => ( + + {row.icon ? : } + + ), + }, + { + title: 'Key', + field: 'key', + width: 'auto', + defaultSort: 'asc', + }, +]; + +export const UserSettingsAvailableIconsTable = () => { + const app = useApp(); + const systemIcons = app.getSystemIcons(); + const systemIconList: SystemIcon[] = []; + for (const icon in systemIcons) { + if (Object.prototype.hasOwnProperty.call(systemIcons, icon)) { + const sysIcon = { + key: icon, + icon: systemIcons[icon], + }; + systemIconList.push(sysIcon); + } + } + + return ( + + ); +}; diff --git a/plugins/user-settings/src/components/General/UserSettingsGeneral.tsx b/plugins/user-settings/src/components/General/UserSettingsGeneral.tsx index db7fdd7be6..e3a9567164 100644 --- a/plugins/user-settings/src/components/General/UserSettingsGeneral.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsGeneral.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { UserSettingsProfileCard } from './UserSettingsProfileCard'; import { UserSettingsAppearanceCard } from './UserSettingsAppearanceCard'; import { UserSettingsIdentityCard } from './UserSettingsIdentityCard'; +import { UserSettingsAvailableIconsTable } from './UserSettingsAvailableIconsTable'; export const UserSettingsGeneral = () => { return ( @@ -31,6 +32,9 @@ export const UserSettingsGeneral = () => { + + + ); };