From 620a9e80a8be9c91ce21e84fc4be3c86b90da3ad Mon Sep 17 00:00:00 2001 From: Frank Kong <50030060+Zaperex@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:56:33 -0400 Subject: [PATCH] Add ability to configure header and navigation side bar indicator colors (#564) * feat(app): make header and navigation indicator colors configurable * chore: update app-config.yaml for branding * Update app-config.yaml --- .changeset/tidy-cows-tell.md | 5 +++ app-config.yaml | 15 ++++++- packages/app/config.d.ts | 22 +++++++++- packages/app/src/App.tsx | 8 ++-- packages/app/src/hooks/useUpdateTheme.test.ts | 23 ++++++++-- packages/app/src/hooks/useUpdateTheme.ts | 22 +++++++--- packages/app/src/themes/darkTheme.ts | 11 ++--- packages/app/src/themes/lightTheme.ts | 11 ++--- packages/app/src/themes/pageTheme.ts | 44 ++++++++++++++----- packages/app/src/types/types.ts | 7 +++ 10 files changed, 130 insertions(+), 38 deletions(-) create mode 100644 .changeset/tidy-cows-tell.md diff --git a/.changeset/tidy-cows-tell.md b/.changeset/tidy-cows-tell.md new file mode 100644 index 000000000..2bbf32ddd --- /dev/null +++ b/.changeset/tidy-cows-tell.md @@ -0,0 +1,5 @@ +--- +'app': minor +--- + +Adds ability to configure header color and navigation indicator color diff --git a/app-config.yaml b/app-config.yaml index 5e3a9b6a9..e317746b4 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -7,7 +7,20 @@ app: writeKey: ${SEGMENT_WRITE_KEY} maskIP: ${SEGMENT_MASK_IP} # prevents IP addresses from being sent if true testMode: ${SEGMENT_TEST_MODE} # prevents data from being sent if true - + branding: + fullLogo: ${BASE64_EMBEDDED_FULL_LOGO} + iconLogo: ${BASE64_EMBEDDED_ICON_LOGO} + theme: + light: + primaryColor: ${PRIMARY_LIGHT_COLOR} + headerColor1: ${HEADER_LIGHT_COLOR_1} + headerColor2: ${HEADER_LIGHT_COLOR_2} + navigationIndicatorColor: ${NAV_INDICATOR_LIGHT_COLOR} + dark: + primaryColor: ${PRIMARY_DARK_COLOR} + headerColor1: ${HEADER_DARK_COLOR_1} + headerColor2: ${HEADER_DARK_COLOR_2} + navigationIndicatorColor: ${NAV_INDICATOR_LIGHT_COLOR} organization: name: My Org diff --git a/packages/app/config.d.ts b/packages/app/config.d.ts index b422fe3b8..d90e184ed 100644 --- a/packages/app/config.d.ts +++ b/packages/app/config.d.ts @@ -23,10 +23,28 @@ export interface Config { [key: string]: { /** * primaryColor Configuration for the instance - * The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color() + * The following formats are supported: #nnn, #nnnnnn * @visibility frontend */ - primaryColor: string; + primaryColor?: string; + /** + * Header Theme color Configuration for the instance + * The following formats are supported: #nnn, #nnnnnn + * @visibility frontend + */ + headerColor1?: string; + /** + * Header Theme color Configuration for the instance + * The following formats are supported: #nnn, #nnnnnn + * @visibility frontend + */ + headerColor2?: string; + /** + * Navigation Side Bar Indicator color Configuration for the instance + * The following formats are supported: #nnn, #nnnnnn + * @visibility frontend + */ + navigationIndicatorColor?: string; }; }; }; diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 194dfad42..8bc175862 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -80,10 +80,10 @@ const app = createApp({ variant: 'light', icon: , Provider: ({ children }) => { - const { primaryColor } = useUpdateTheme('light'); + const themeColors = useUpdateTheme('light'); return ( ); @@ -95,10 +95,10 @@ const app = createApp({ variant: 'dark', icon: , Provider: ({ children }) => { - const { primaryColor } = useUpdateTheme('dark'); + const themeColors = useUpdateTheme('dark'); return ( ); diff --git a/packages/app/src/hooks/useUpdateTheme.test.ts b/packages/app/src/hooks/useUpdateTheme.test.ts index eb8acf654..6c8fc3cb6 100644 --- a/packages/app/src/hooks/useUpdateTheme.test.ts +++ b/packages/app/src/hooks/useUpdateTheme.test.ts @@ -8,13 +8,28 @@ jest.mock('@backstage/core-plugin-api', () => ({ })); describe('useUpdateTheme', () => { - it('returns the primaryColor when config is available', () => { - (useApi as any).mockReturnValue({ - getOptionalString: jest.fn().mockReturnValue('blue'), + it('returns the themeColors when config for them is available', () => { + (useApi as jest.Mock).mockReturnValue({ + getOptionalString: jest.fn().mockImplementation(key => { + switch (key) { + case 'app.branding.theme.someTheme.primaryColor': + return 'blue'; + case 'app.branding.theme.someTheme.headerColor1': + return 'red'; + case 'app.branding.theme.someTheme.headerColor2': + return 'yellow'; + case 'app.branding.theme.someTheme.navigationIndicatorColor': + return 'purple'; + default: + return ''; + } + }), }); - const { result } = renderHook(() => useUpdateTheme('someTheme')); expect(result.current.primaryColor).toBe('blue'); + expect(result.current.headerColor1).toBe('red'); + expect(result.current.headerColor2).toBe('yellow'); + expect(result.current.navigationIndicatorColor).toBe('purple'); }); it('returns undefined when config is unavailable', () => { diff --git a/packages/app/src/hooks/useUpdateTheme.ts b/packages/app/src/hooks/useUpdateTheme.ts index 1995aced8..efe1aa99f 100644 --- a/packages/app/src/hooks/useUpdateTheme.ts +++ b/packages/app/src/hooks/useUpdateTheme.ts @@ -1,16 +1,24 @@ import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { ThemeColors } from '../types/types'; -export const useUpdateTheme = ( - selTheme: string, -): { primaryColor: string | undefined } => { - let primaryColor: string | undefined; +export const useUpdateTheme = (selTheme: string): ThemeColors => { + const themeColors: ThemeColors = {}; try { const configApi = useApi(configApiRef); - primaryColor = configApi.getOptionalString( + themeColors.primaryColor = configApi.getOptionalString( `app.branding.theme.${selTheme}.primaryColor`, ); + themeColors.headerColor1 = configApi.getOptionalString( + `app.branding.theme.${selTheme}.headerColor1`, + ); + themeColors.headerColor2 = configApi.getOptionalString( + `app.branding.theme.${selTheme}.headerColor2`, + ); + themeColors.navigationIndicatorColor = configApi.getOptionalString( + `app.branding.theme.${selTheme}.navigationIndicatorColor`, + ); } catch (err) { - // useApi won't be initialized initally in createApp theme provider, and will get updated later + // useApi won't be initialized initially in createApp theme provider, and will get updated later } - return { primaryColor }; + return themeColors; }; diff --git a/packages/app/src/themes/darkTheme.ts b/packages/app/src/themes/darkTheme.ts index 2ae63c3bd..02dc35931 100644 --- a/packages/app/src/themes/darkTheme.ts +++ b/packages/app/src/themes/darkTheme.ts @@ -2,21 +2,22 @@ import { createUnifiedTheme, themes } from '@backstage/theme'; import { components } from './componentOverrides'; import { pageFontFamily, typography } from './consts'; import { pageTheme } from './pageTheme'; +import { ThemeColors } from '../types/types'; -export const customDarkTheme = (primaryColor?: string | undefined) => +export const customDarkTheme = (themeColors: ThemeColors) => createUnifiedTheme({ fontFamily: pageFontFamily, palette: { ...themes.dark.getTheme('v5')?.palette, - ...(primaryColor && { + ...(themeColors.primaryColor && { primary: { ...themes.light.getTheme('v5')?.palette.primary, - main: primaryColor, + main: themeColors.primaryColor, }, }), navigation: { background: '#0f1214', - indicator: '#009596', + indicator: themeColors.navigationIndicatorColor || '#009596', color: '#ffffff', selectedColor: '#ffffff', navItem: { @@ -25,7 +26,7 @@ export const customDarkTheme = (primaryColor?: string | undefined) => }, }, defaultPageTheme: 'home', - pageTheme, + pageTheme: pageTheme(themeColors), components, typography, }); diff --git a/packages/app/src/themes/lightTheme.ts b/packages/app/src/themes/lightTheme.ts index 624c1848c..5d088eda5 100644 --- a/packages/app/src/themes/lightTheme.ts +++ b/packages/app/src/themes/lightTheme.ts @@ -2,21 +2,22 @@ import { createUnifiedTheme, themes } from '@backstage/theme'; import { components } from './componentOverrides'; import { pageFontFamily, typography } from './consts'; import { pageTheme } from './pageTheme'; +import { ThemeColors } from '../types/types'; -export const customLightTheme = (primaryColor?: string | undefined) => +export const customLightTheme = (themeColors: ThemeColors) => createUnifiedTheme({ fontFamily: pageFontFamily, palette: { ...themes.light.getTheme('v5')?.palette, - ...(primaryColor && { + ...(themeColors.primaryColor && { primary: { ...themes.light.getTheme('v5')?.palette.primary, - main: primaryColor, + main: themeColors.primaryColor, }, }), navigation: { background: '#222427', - indicator: '#009596', + indicator: themeColors.navigationIndicatorColor || '#009596', color: '#ffffff', selectedColor: '#ffffff', navItem: { @@ -25,7 +26,7 @@ export const customLightTheme = (primaryColor?: string | undefined) => }, }, defaultPageTheme: 'home', - pageTheme, + pageTheme: pageTheme(themeColors), components, typography, }); diff --git a/packages/app/src/themes/pageTheme.ts b/packages/app/src/themes/pageTheme.ts index 87042db25..c181c312c 100644 --- a/packages/app/src/themes/pageTheme.ts +++ b/packages/app/src/themes/pageTheme.ts @@ -1,13 +1,37 @@ import { PageTheme, genPageTheme, shapes } from '@backstage/theme'; +import { ThemeColors } from '../types/types'; -export const pageTheme: Record = { - home: genPageTheme({ colors: ['#005f60', '#73c5c5'], shape: shapes.wave }), - app: genPageTheme({ colors: ['#005f60', '#73c5c5'], shape: shapes.wave }), - apis: genPageTheme({ colors: ['#005f60', '#73c5c5'], shape: shapes.wave }), - documentation: genPageTheme({ - colors: ['#005f60', '#73c5c5'], - shape: shapes.wave, - }), - tool: genPageTheme({ colors: ['#005f60', '#73c5c5'], shape: shapes.round }), - other: genPageTheme({ colors: ['#005f60', '#73c5c5'], shape: shapes.wave }), +export const pageTheme = (input: ThemeColors): Record => { + const { headerColor1, headerColor2 } = input; + const defaultColors = ['#005f60', '#73c5c5']; + const headerColor = [ + headerColor1 || defaultColors[0], + headerColor2 || defaultColors[1], + ]; + return { + home: genPageTheme({ + colors: [headerColor[0], headerColor[1]], + shape: shapes.wave, + }), + app: genPageTheme({ + colors: [headerColor[0], headerColor[1]], + shape: shapes.wave, + }), + apis: genPageTheme({ + colors: [headerColor[0], headerColor[1]], + shape: shapes.wave, + }), + documentation: genPageTheme({ + colors: [headerColor[0], headerColor[1]], + shape: shapes.wave, + }), + tool: genPageTheme({ + colors: [headerColor[0], headerColor[1]], + shape: shapes.round, + }), + other: genPageTheme({ + colors: [headerColor[0], headerColor[1]], + shape: shapes.wave, + }), + }; }; diff --git a/packages/app/src/types/types.ts b/packages/app/src/types/types.ts index caa6f8d50..1f04bcbfc 100644 --- a/packages/app/src/types/types.ts +++ b/packages/app/src/types/types.ts @@ -5,3 +5,10 @@ export type QuickAccessLinks = { isExpanded?: boolean; links: (Tool & { iconUrl: string })[]; }; + +export type ThemeColors = { + primaryColor?: string | undefined; + headerColor1?: string | undefined; + headerColor2?: string | undefined; + navigationIndicatorColor?: string | undefined; +};