Skip to content

Commit

Permalink
Add ability to configure header and navigation side bar indicator col…
Browse files Browse the repository at this point in the history
…ors (janus-idp#564)

* feat(app): make header and navigation indicator colors configurable

* chore: update app-config.yaml for branding

* Update app-config.yaml
  • Loading branch information
Zaperex committed Sep 26, 2023
1 parent fb319ee commit 620a9e8
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/tidy-cows-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'app': minor
---

Adds ability to configure header color and navigation indicator color
15 changes: 14 additions & 1 deletion app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
22 changes: 20 additions & 2 deletions packages/app/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
};
};
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ const app = createApp({
variant: 'light',
icon: <LightIcon />,
Provider: ({ children }) => {
const { primaryColor } = useUpdateTheme('light');
const themeColors = useUpdateTheme('light');
return (
<UnifiedThemeProvider
theme={customLightTheme(primaryColor)}
theme={customLightTheme(themeColors)}
children={children}
/>
);
Expand All @@ -95,10 +95,10 @@ const app = createApp({
variant: 'dark',
icon: <DarkIcon />,
Provider: ({ children }) => {
const { primaryColor } = useUpdateTheme('dark');
const themeColors = useUpdateTheme('dark');
return (
<UnifiedThemeProvider
theme={customDarkTheme(primaryColor)}
theme={customDarkTheme(themeColors)}
children={children}
/>
);
Expand Down
23 changes: 19 additions & 4 deletions packages/app/src/hooks/useUpdateTheme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
22 changes: 15 additions & 7 deletions packages/app/src/hooks/useUpdateTheme.ts
Original file line number Diff line number Diff line change
@@ -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;
};
11 changes: 6 additions & 5 deletions packages/app/src/themes/darkTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -25,7 +26,7 @@ export const customDarkTheme = (primaryColor?: string | undefined) =>
},
},
defaultPageTheme: 'home',
pageTheme,
pageTheme: pageTheme(themeColors),
components,
typography,
});
11 changes: 6 additions & 5 deletions packages/app/src/themes/lightTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -25,7 +26,7 @@ export const customLightTheme = (primaryColor?: string | undefined) =>
},
},
defaultPageTheme: 'home',
pageTheme,
pageTheme: pageTheme(themeColors),
components,
typography,
});
44 changes: 34 additions & 10 deletions packages/app/src/themes/pageTheme.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
import { PageTheme, genPageTheme, shapes } from '@backstage/theme';
import { ThemeColors } from '../types/types';

export const pageTheme: Record<string, PageTheme> = {
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<string, PageTheme> => {
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,
}),
};
};
7 changes: 7 additions & 0 deletions packages/app/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

0 comments on commit 620a9e8

Please sign in to comment.