Skip to content

Commit a715a38

Browse files
committed
feat(material/schematics): Add custom system variable prefix and high contrast override mixins options to custom theme schematic
1 parent 7cf8c6c commit a715a38

File tree

7 files changed

+514
-230
lines changed

7 files changed

+514
-230
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
"@bazel/terser": "5.8.1",
9999
"@bazel/worker": "5.8.1",
100100
"@firebase/app-types": "^0.7.0",
101-
"@material/material-color-utilities": "^0.2.7",
101+
"@material/material-color-utilities": "^0.3.0",
102102
"@octokit/rest": "18.3.5",
103103
"@rollup/plugin-commonjs": "^21.0.0",
104104
"@rollup/plugin-node-resolve": "^13.1.3",

src/material/schematics/ng-generate/m3-theme/README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ If you're using the system variables option, you should remember to either provi
2323
system variables (all prefixed with `--sys-`), or to include the `system-level-colors` and
2424
`system-level-typography` mixins which will generate the values based on your theme.
2525

26-
The default prefix for system variables is `--sys-`. This prefix can be customized. For
27-
example, to change the prefix to `--md-sys-`, use the following configuration at the color or typography level:
28-
`system-prefix: md-sys`.
26+
The default prefix for system variables is `--sys-`. This prefix can be customized if specified.
27+
28+
When using system variables you can also generate high contrast override mixins. The mixin overrides
29+
the system level variables with high contrast equivalent values from your theme. This is helpful for
30+
users who prefer more contrastful colors for either preference or accessibility reasons.
31+
2932

3033
```scss
3134
@use '@angular/material' as mat;
@@ -42,6 +45,12 @@ html {
4245
// or uncomment the lines below to generate them from the theme.
4346
// @include mat.system-level-colors(my-theme.$light-theme);
4447
// @include mat.system-level-typography(my-theme.$light-theme);
48+
49+
// When generating high contrast override mixins, uncomment the lines below to override
50+
// the system variables only when users specify.
51+
// @media (prefers-contrast: more) {
52+
// @include my-theme.high-contrast-light-theme-overrides();
53+
// }
4554
}
4655
```
4756

@@ -66,3 +75,7 @@ generated theme file should be created in. Defaults to the project root.
6675
* `themeTypes` - Theme types ('light', 'dark', or 'both') to generate themes for. Defaults to both.
6776
* `useSystemVariables` - Whether to generate a theme that uses system-level variables for easier
6877
dynamic theming. Defaults to false.
78+
* `systemVariablePrefix` - Prefix for system-level variables. Defaults to 'sys'.
79+
* `generateHighContrastOverrideMixins` - Whether to add high contrast override mixins to generated
80+
theme file. Developers can call the mixin when they want to show a high contrast version of their
81+
theme.

src/material/schematics/ng-generate/m3-theme/index.spec.ts

Lines changed: 166 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ describe('material-m3-theme-schematic', () => {
3333
html {
3434
@if variable-exists(light-theme) {
3535
@include _theme($light-theme);
36+
@if mixin-exists(high-contrast-light-theme-overrides) {
37+
@include high-contrast-light-theme-overrides();
38+
}
3639
}
3740
@if variable-exists(dark-theme) {
3841
@include _theme($dark-theme);
42+
@if mixin-exists(high-contrast-dark-theme-overrides) {
43+
@include high-contrast-dark-theme-overrides();
44+
}
3945
}
4046
}
4147
`,
@@ -109,7 +115,7 @@ describe('material-m3-theme-schematic', () => {
109115
const generatedSCSS = tree.readText('m3-theme.scss');
110116
const testSCSS = generateSCSSTheme(
111117
testM3ThemePalette,
112-
'light',
118+
['light'],
113119
'Color palettes are generated from primary: #984061',
114120
false,
115121
);
@@ -126,7 +132,7 @@ describe('material-m3-theme-schematic', () => {
126132
const generatedSCSS = tree.readText('m3-theme.scss');
127133
const testSCSS = generateSCSSTheme(
128134
testM3ThemePalette,
129-
'dark',
135+
['dark'],
130136
'Color palettes are generated from primary: #984061',
131137
false,
132138
);
@@ -144,7 +150,7 @@ describe('material-m3-theme-schematic', () => {
144150
const generatedSCSS = tree.readText('m3-theme.scss');
145151
const testSCSS = generateSCSSTheme(
146152
testM3ThemePalette,
147-
'both',
153+
['light', 'dark'],
148154
'Color palettes are generated from primary: #984061',
149155
false,
150156
);
@@ -169,7 +175,7 @@ describe('material-m3-theme-schematic', () => {
169175

170176
const testSCSS = generateSCSSTheme(
171177
testPalette,
172-
'both',
178+
['light', 'dark'],
173179
'Color palettes are generated from primary: #984061, secondary: #984061',
174180
false,
175181
);
@@ -196,7 +202,7 @@ describe('material-m3-theme-schematic', () => {
196202

197203
const testSCSS = generateSCSSTheme(
198204
testPalette,
199-
'both',
205+
['light', 'dark'],
200206
'Color palettes are generated from primary: #984061, secondary: #984061, tertiary: #984061',
201207
false,
202208
);
@@ -221,11 +227,24 @@ describe('material-m3-theme-schematic', () => {
221227
let testPalette = testM3ThemePalette;
222228
testPalette.set('secondary', testM3ThemePalette.get('primary')!);
223229
testPalette.set('tertiary', testM3ThemePalette.get('primary')!);
224-
testPalette.set('neutral', testM3ThemePalette.get('primary')!);
230+
231+
// Neutral's tonal palette has additional tones as opposed to the other color palettes.
232+
let neutralPalette = new Map(testM3ThemePalette.get('primary')!);
233+
neutralPalette.set(4, '#26000f');
234+
neutralPalette.set(6, '#2f0015');
235+
neutralPalette.set(12, '#460022');
236+
neutralPalette.set(17, '#55082c');
237+
neutralPalette.set(22, '#631637');
238+
neutralPalette.set(24, '#691a3c');
239+
neutralPalette.set(87, '#ffcdda');
240+
neutralPalette.set(92, '#ffe1e8');
241+
neutralPalette.set(94, '#ffe8ed');
242+
neutralPalette.set(96, '#fff0f2');
243+
testPalette.set('neutral', neutralPalette);
225244

226245
const testSCSS = generateSCSSTheme(
227246
testPalette,
228-
'both',
247+
['light', 'dark'],
229248
'Color palettes are generated from primary: #984061, secondary: #984061, tertiary: #984061, neutral: #984061',
230249
false,
231250
);
@@ -263,47 +282,150 @@ describe('material-m3-theme-schematic', () => {
263282
expect(generatedCSS).toContain('var(--sys-primary)');
264283
});
265284

266-
it('should estimate missing neutral hues', async () => {
285+
it('should be able to generate high contrast mixins for light and dark themes', async () => {
286+
const tree = await runM3ThemeSchematic(runner, {
287+
primaryColor: '#984061',
288+
themeTypes: 'both',
289+
useSystemVariables: true,
290+
generateHighContrastOverrideMixins: true,
291+
});
292+
293+
const generatedSCSS = tree.readText('m3-theme.scss');
294+
295+
expect(generatedSCSS).toContain(`@mixin high-contrast-light-theme-overrides`);
296+
expect(generatedSCSS).toContain(`@mixin high-contrast-dark-theme-overrides`);
297+
});
298+
299+
it('should be able to generate high contrast light theme overrides when provided a primary color', async () => {
267300
const tree = await runM3ThemeSchematic(runner, {
268-
primaryColor: '#232e62',
269-
secondaryColor: '#cc862a',
270-
tertiaryColor: '#44263e',
271-
neutralColor: '#929093',
301+
primaryColor: '#984061',
272302
themeTypes: 'light',
303+
useSystemVariables: true,
304+
generateHighContrastOverrideMixins: true,
273305
});
274306

275-
expect(tree.readContent('m3-theme.scss')).toContain(
276-
[
277-
` neutral: (`,
278-
` 0: #000000,`,
279-
` 4: #0b0b0c,`,
280-
` 6: #111012,`,
281-
` 10: #1c1b1e,`,
282-
` 12: #201f22,`,
283-
` 17: #2b2a2d,`,
284-
` 20: #313033,`,
285-
` 22: #353437,`,
286-
` 24: #3a393c,`,
287-
` 25: #3c3b3e,`,
288-
` 30: #474649,`,
289-
` 35: #535255,`,
290-
` 40: #5f5e61,`,
291-
` 50: #787679,`,
292-
` 60: #929093,`,
293-
` 70: #adaaad,`,
294-
` 80: #c8c5c9,`,
295-
` 87: #dcd9dd,`,
296-
` 90: #e5e1e5,`,
297-
` 92: #ebe7eb,`,
298-
` 94: #f0edf0,`,
299-
` 95: #f3f0f3,`,
300-
` 96: #f6f3f6,`,
301-
` 98: #fcf8fb,`,
302-
` 99: #fffbfe,`,
303-
` 100: #ffffff,`,
304-
` ),`,
305-
].join('\n'),
306-
);
307+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
308+
309+
// Check a system variable from each color palette for their high contrast equivalent value
310+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
311+
expect(generatedCSS).toContain(`--sys-secondary: #45212d`);
312+
expect(generatedCSS).toContain(`--sys-tertiary: #4d1f00`);
313+
expect(generatedCSS).toContain(`--sys-error: #600004`);
314+
expect(generatedCSS).toContain(`--sys-surface: #fff8f8`);
315+
expect(generatedCSS).toContain(`--sys-outline: #37282c`);
316+
});
317+
318+
it('should be able to generate high contrast dark theme overrides when provided a primary color', async () => {
319+
const tree = await runM3ThemeSchematic(runner, {
320+
primaryColor: '#984061',
321+
themeTypes: 'dark',
322+
useSystemVariables: true,
323+
generateHighContrastOverrideMixins: true,
324+
});
325+
326+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
327+
328+
// Check a system variable from each color palette for their high contrast equivalent value
329+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
330+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
331+
expect(generatedCSS).toContain(`--sys-tertiary: #ffece4`);
332+
expect(generatedCSS).toContain(`--sys-error: #ffece9`);
333+
expect(generatedCSS).toContain(`--sys-surface: #191113`);
334+
expect(generatedCSS).toContain(`--sys-outline: #ffebef`);
335+
});
336+
337+
it('should be able to generate high contrast light theme overrides if useSystemVariables is not specified', async () => {
338+
const tree = await runM3ThemeSchematic(runner, {
339+
primaryColor: '#984061',
340+
themeTypes: 'light',
341+
generateHighContrastOverrideMixins: true,
342+
});
343+
344+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
345+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
346+
});
347+
348+
it('should be able to generate high contrast themes overrides when provided a primary and secondary color', async () => {
349+
const tree = await runM3ThemeSchematic(runner, {
350+
primaryColor: '#984061',
351+
secondaryColor: '#984061',
352+
themeTypes: 'both',
353+
useSystemVariables: true,
354+
generateHighContrastOverrideMixins: true,
355+
});
356+
357+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
358+
console.log(tree.readText('m3-theme.scss'));
359+
360+
// Check a system variable from each color palette for their high contrast light theme value
361+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
362+
expect(generatedCSS).toContain(`--sys-secondary: #580b2f`);
363+
364+
// Check a system variable from each color palette for their high contrast dark theme value
365+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
366+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
367+
});
368+
369+
it('should be able to generate high contrast themes overrides when provided primary, secondary, and tertiary color', async () => {
370+
const tree = await runM3ThemeSchematic(runner, {
371+
primaryColor: '#984061',
372+
secondaryColor: '#984061',
373+
tertiaryColor: '#984061',
374+
themeTypes: 'both',
375+
useSystemVariables: true,
376+
generateHighContrastOverrideMixins: true,
377+
});
378+
379+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
380+
381+
// Check a system variable from each color palette for their high contrast light theme value
382+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
383+
expect(generatedCSS).toContain(`--sys-secondary: #580b2f`);
384+
expect(generatedCSS).toContain(`--sys-tertiary: #580b2f`);
385+
386+
// Check a system variable from each color palette for their high contrast dark theme value
387+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
388+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
389+
expect(generatedCSS).toContain(`--sys-tertiary: #ffebef`);
390+
});
391+
392+
it('should be able to generate high contrast themes overrides when provided primary, secondary, tertiary, and neutral color', async () => {
393+
const tree = await runM3ThemeSchematic(runner, {
394+
primaryColor: '#984061',
395+
secondaryColor: '#984061',
396+
tertiaryColor: '#984061',
397+
neutralColor: '#dfdfdf', // Different color since #984061 does not change the tonal palette
398+
themeTypes: 'both',
399+
useSystemVariables: true,
400+
generateHighContrastOverrideMixins: true,
401+
});
402+
403+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
404+
405+
// Check a system variable from each color palette for their high contrast light theme value
406+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
407+
expect(generatedCSS).toContain(`--sys-secondary: #580b2f`);
408+
expect(generatedCSS).toContain(`--sys-tertiary: #580b2f`);
409+
expect(generatedCSS).toContain(`--sys-surface-bright: #f9f9f9`);
410+
411+
// Check a system variable from each color palette for their high contrast dark theme value
412+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
413+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
414+
expect(generatedCSS).toContain(`--sys-tertiary: #ffebef`);
415+
expect(generatedCSS).toContain(`--sys-surface-bright: #4f5051`);
416+
});
417+
418+
it('should be able to generate high contrast themes with custom system variable prefix', async () => {
419+
const tree = await runM3ThemeSchematic(runner, {
420+
primaryColor: '#984061',
421+
themeTypes: 'light',
422+
useSystemVariables: true,
423+
systemVariablePrefix: 'my-app',
424+
generateHighContrastOverrideMixins: true,
425+
});
426+
427+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
428+
expect(generatedCSS).toContain(`--my-app-primary: #580b2f`);
307429
});
308430
});
309431

0 commit comments

Comments
 (0)