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;
+};