diff --git a/packages/core/src/api/apis/definitions/AppThemeApi.ts b/packages/core/src/api/apis/definitions/AppThemeApi.ts index bddb6f1a10..f866e38738 100644 --- a/packages/core/src/api/apis/definitions/AppThemeApi.ts +++ b/packages/core/src/api/apis/definitions/AppThemeApi.ts @@ -21,6 +21,11 @@ import { BackstageTheme } from '@backstage/theme'; * Describes a theme provided by the app. */ export type AppTheme = { + /** + * ID used to remember theme selections. + */ + id: string; + /** * Title of the theme */ diff --git a/packages/core/src/api/app/App.tsx b/packages/core/src/api/app/App.tsx index c44454b10e..462734ee4b 100644 --- a/packages/core/src/api/app/App.tsx +++ b/packages/core/src/api/app/App.tsx @@ -29,14 +29,16 @@ import { SystemIconKey, defaultSystemIcons, } from '../../icons'; -import { ApiHolder, ApiProvider, ApiRegistry } from '../apis'; +import { ApiHolder, ApiProvider, ApiRegistry, AppTheme } from '../apis'; import LoginPage from './LoginPage'; +import { lightTheme } from '@backstage/theme'; type FullAppOptions = { apis: ApiHolder; icons: SystemIcons; plugins: BackstagePlugin[]; components: AppComponents; + themes: AppTheme[]; }; class AppImpl implements BackstageApp { @@ -44,12 +46,14 @@ class AppImpl implements BackstageApp { private readonly icons: SystemIcons; private readonly plugins: BackstagePlugin[]; private readonly components: AppComponents; + private readonly themes: AppTheme[]; constructor(options: FullAppOptions) { this.apis = options.apis; this.icons = options.icons; this.plugins = options.plugins; this.components = options.components; + this.themes = options.themes; } getApis(): ApiHolder { @@ -178,8 +182,38 @@ export function createApp(options?: AppOptions) { NotFoundErrorPage: DefaultNotFoundPage, ...options?.components, }; + const themes = new Array(); - const app = new AppImpl({ apis, icons, plugins, components }); + if (Array.isArray(options?.themes)) { + themes.push(...options?.themes!); + } else { + if (options?.themes?.light) { + themes.push({ + id: 'light', + title: 'Light Theme', + variant: 'light', + theme: options?.themes?.light, + }); + } + if (options?.themes?.dark) { + themes.push({ + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + theme: options?.themes?.dark, + }); + } + } + if (themes.length === 0) { + themes.push({ + id: 'light', + title: 'Default Theme', + variant: 'light', + theme: lightTheme, + }); + } + + const app = new AppImpl({ apis, icons, plugins, components, themes }); app.verify(); diff --git a/packages/core/src/api/app/types.ts b/packages/core/src/api/app/types.ts index e8d597e120..b208b85afb 100644 --- a/packages/core/src/api/app/types.ts +++ b/packages/core/src/api/app/types.ts @@ -15,9 +15,11 @@ */ import { ComponentType } from 'react'; +import { BackstageTheme } from '@backstage/theme'; import { IconComponent, SystemIconKey, SystemIcons } from '../../icons'; import { BackstagePlugin } from '../plugin'; import { ApiHolder } from '../apis'; +import { AppTheme } from '../apis/definitions'; export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; @@ -45,6 +47,23 @@ export type AppOptions = { * Supply components to the app to override the default ones. */ components?: Partial; + + /** + * Themes provided as a part of the app. + * + * The themes can be specified either as just default light and dark mode themes. + * If only one of then is specified then that theme will always be used. + * If both are provided the default theme will be set based on browser preferences. + * + * If an array of themes is provided, the first occurence of light and dark mode variants + * will be treated as the default for each variant. + */ + themes?: + | { + light?: BackstageTheme; + dark?: BackstageTheme; + } + | AppTheme[]; }; export type BackstageApp = {