Skip to content

Commit 4a13f9d

Browse files
committed
Fix merging of child / parent themes.
1 parent 97a0416 commit 4a13f9d

File tree

8 files changed

+129
-15
lines changed

8 files changed

+129
-15
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ SciReactUI Changelog
1010

1111
### Fixed
1212
- Styles added to Navbar and Footer incorrectly remove built in styles.
13+
- Themes were not inheriting all details from their parent.
1314

1415
### Changed
1516
- Breadcrumbs component takes optional linkComponent prop for page routing.

src/storybook/theme/Colours.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ export function ThemeColorItem({title, theme, colourSet, mode}) {
5959
### Light Mode
6060
<ColorPalette>
6161
<ThemeColorItem title="Primary" theme={DiamondTheme} colourSet="primary" mode="light" />
62-
<ThemeColorItem title="Secodary" theme={DiamondTheme} colourSet="secondary" mode="light" />
62+
<ThemeColorItem title="Secondary" theme={DiamondTheme} colourSet="secondary" mode="light" />
6363
</ColorPalette>
6464

6565
### Dark Mode
6666
<ColorPalette>
6767
<ThemeColorItem title="Primary" theme={DiamondTheme} colourSet="primary" mode={"dark"} />
68-
<ThemeColorItem title="Secodary" theme={DiamondTheme} colourSet="secondary" mode={"dark"} />
68+
<ThemeColorItem title="Secondary" theme={DiamondTheme} colourSet="secondary" mode={"dark"} />
6969
</ColorPalette>
7070
</div>
7171
</div>

src/storybook/theme/Logos.mdx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Meta, ColorPalette, ColorItem } from '@storybook/blocks';
1+
import { Meta } from '@storybook/blocks';
22
import {ImageColorSchemeSwitch} from "../../components/controls/ImageColorSchemeSwitch";
33

44
<Meta title="Theme/Logos" />
@@ -24,7 +24,10 @@ import {ImageColorSchemeSwitch} from "../../components/controls/ImageColorScheme
2424
}
2525
```
2626

27-
<ImageColorSchemeSwitch image={{"src":"static/media/src/public/generic/logo-light.svg","alt":"A Logo"}}/>
27+
<div style={{width:"200px", margin:"0 auto"}} >
28+
<ImageColorSchemeSwitch image={{"src":"static/media/src/public/generic/logo-light.svg","alt":"A Logo",width:200}}/>
29+
<p>Example logo</p>
30+
</div>
2831

2932
## Sizes
3033
There are two image sizes. I regular `normal` and a smaller version `short`, these are used in the Navbar and

src/themes/BaseTheme.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ const BaseThemeOptions /* : ThemeOptions */ = {
2929
palette: {
3030
background: { default: "#fafafa" },
3131
},
32+
text: {
33+
primary: "#050505"
34+
},
3235
},
3336
dark: {
3437
palette: {
3538
background: { default: "#050505" },
3639
},
40+
text: {
41+
primary: "#fafafa"
42+
},
3743
},
3844
},
3945
components: {},

src/themes/DiamondTheme.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { createTheme, Theme } from "@mui/material/styles";
21
import type {} from "@mui/material/themeCssVarsAugmentation";
2+
import { createTheme, Theme } from "@mui/material/styles";
33

4-
import { BaseThemeOptions } from "./BaseTheme";
4+
import {mergeThemeOptions} from "./ThemeManager";
55

66
import logoImageDark from "../public/diamond/logo-dark.svg";
77
import logoImageLight from "../public/diamond/logo-light.svg";
@@ -10,8 +10,7 @@ import logoShort from "../public/diamond/logo-short.svg";
1010
const dlsLogoBlue = "#202740";
1111
const dlsLogoYellow = "#facf07";
1212

13-
const DiamondTheme: Theme = createTheme({
14-
...BaseThemeOptions,
13+
const DiamondThemeOptions = mergeThemeOptions({
1514
logos: {
1615
normal: {
1716
src: logoImageLight,
@@ -41,6 +40,9 @@ const DiamondTheme: Theme = createTheme({
4140
dark: "#AF9004", // dark yellow
4241
contrastText: "#000000", // black
4342
},
43+
text: {
44+
secondary: "#161B2C"
45+
},
4446
},
4547
},
4648
dark: {
@@ -57,13 +59,16 @@ const DiamondTheme: Theme = createTheme({
5759
dark: "#AF9004", // dark yellow
5860
contrastText: "#000000", // black
5961
},
62+
text: {
63+
secondary: "#8090CA"
64+
},
6065
},
6166
},
6267
},
6368
components: {
6469
MuiButton: {
6570
styleOverrides: {
66-
root: ({ theme }) => ({
71+
root: ({ theme }: any ) => ({
6772
textTransform: "none",
6873
"&.MuiButton-contained": {},
6974
"&.default": {
@@ -92,4 +97,6 @@ const DiamondTheme: Theme = createTheme({
9297
},
9398
});
9499

95-
export { DiamondTheme };
100+
const DiamondTheme: Theme = createTheme( DiamondThemeOptions );
101+
102+
export { DiamondTheme, DiamondThemeOptions };

src/themes/GenericTheme.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { createTheme, Theme } from "@mui/material/styles";
1+
import type {} from "@mui/material/themeCssVarsAugmentation";
2+
import {createTheme, Theme} from "@mui/material/styles";
23

3-
import { BaseThemeOptions } from "./BaseTheme";
4+
import {mergeThemeOptions} from "./ThemeManager";
45

56
import logoImageDark from "../public/generic/logo-dark.svg";
67
import logoImageLight from "../public/generic/logo-light.svg";
78

8-
const GenericTheme: Theme = createTheme({
9-
...BaseThemeOptions,
9+
const GenericThemeOptions = mergeThemeOptions({
1010
logos: {
1111
normal: {
1212
src: logoImageLight,
@@ -22,4 +22,6 @@ const GenericTheme: Theme = createTheme({
2222
},
2323
});
2424

25-
export { GenericTheme };
25+
const GenericTheme: Theme = createTheme(GenericThemeOptions)
26+
console.log( GenericTheme )
27+
export { GenericTheme, GenericThemeOptions };

src/themes/ThemeManager.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import "@testing-library/jest-dom";
2+
3+
import { BaseThemeOptions } from "./BaseTheme";
4+
import { mergeThemeOptions } from "./ThemeManager";
5+
6+
describe("Theme Manager merge", () => {
7+
it("should merge", () => {
8+
const a = { a: 1, b: 2 };
9+
const b = { x: 1, y: 2 };
10+
const new_a = mergeThemeOptions(a,b);
11+
12+
expect(new_a).toStrictEqual( { a: 1, b: 2, x: 1, y: 2} )
13+
})
14+
15+
16+
it("should deep merge", () => {
17+
const a = { a: 1, b: 2, c: {d: 1, e: 2}};
18+
const b = { x: 1, y:{ z: 3 } };
19+
const merged = mergeThemeOptions(a, b);
20+
21+
expect(merged).toStrictEqual( { a: 1, b: 2, c: {d: 1, e: 2}, x: 1, y:{ z: 3 }} )
22+
})
23+
24+
it("should use a value over b", () => {
25+
const a = {a: 100, b: 2};
26+
const b = {a: 1, c: 3};
27+
const merged = mergeThemeOptions(a, b);
28+
29+
expect(merged).toStrictEqual({ a: 100, b: 2, c:3 })
30+
})
31+
32+
it("should take the base theme and make a new one", () => {
33+
const fontSize = 4422;
34+
const a = { typography: { fontSize: fontSize } };
35+
const b = BaseThemeOptions;
36+
37+
const merged = mergeThemeOptions(a, b);
38+
39+
expect(BaseThemeOptions.typography.fontSize).not.toStrictEqual( fontSize )
40+
expect(merged.typography.fontSize).toStrictEqual( fontSize )
41+
})
42+
43+
it("should effectively clone to an empty object", () => {
44+
const a = {a: 100, b: 2};
45+
const cloned = mergeThemeOptions({}, a);
46+
47+
expect(cloned).toStrictEqual(a)
48+
})
49+
50+
})

src/themes/ThemeManager.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { BaseThemeOptions } from "./BaseTheme";
2+
3+
/*
4+
Merge two options, with newThemeOptions having precedence.
5+
If no parent is selected the BaseThemeOptions is used
6+
Doesn't affect either options passed in.
7+
*/
8+
function mergeThemeOptions(newThemeOptions: any, parentThemeOptions: any=BaseThemeOptions ) {
9+
const parentThemeOptionsCopy = deepCopyObject(parentThemeOptions);
10+
return mergeObjects(parentThemeOptionsCopy, newThemeOptions )
11+
}
12+
13+
14+
function mergeObjects(mainThemeOptions: any, parentThemeOptions: any, visited = new Map<any, any>()) {
15+
//This Deep Merge algorithm is based on https://www.geeksforgeeks.org/how-to-deep-merge-two-objects-in-typescript/
16+
if (isObject(mainThemeOptions) && isObject(parentThemeOptions)) {
17+
for (const key in parentThemeOptions) {
18+
if (isObject(parentThemeOptions[key])) {
19+
if (!mainThemeOptions[key]) {
20+
mainThemeOptions[key] = {};
21+
}
22+
// Check if the parentThemeOptions object has already been visited
23+
if (!visited.has(parentThemeOptions[key])) {
24+
visited.set(parentThemeOptions[key], {});
25+
mergeObjects(mainThemeOptions[key], parentThemeOptions[key], visited);
26+
} else {
27+
mainThemeOptions[key] = visited.get(parentThemeOptions[key]);
28+
}
29+
} else {
30+
mainThemeOptions[key] = parentThemeOptions[key];
31+
}
32+
}
33+
}
34+
return mainThemeOptions;
35+
}
36+
37+
function isObject(item: any): boolean {
38+
return item !== null && typeof item === 'object' && !Array.isArray(item);
39+
}
40+
41+
function deepCopyObject(themeOptions: any) {
42+
return mergeObjects({}, themeOptions )
43+
}
44+
45+
export { mergeThemeOptions };

0 commit comments

Comments
 (0)