Skip to content

Commit 971550d

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

File tree

7 files changed

+515
-230
lines changed

7 files changed

+515
-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: 15 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,6 @@ 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 so when users specify developers can show a high contrast version of their theme.

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

Lines changed: 167 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,25 @@ 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 are added for neutral o it cannot just be
232+
// replaced with primary's tonal palette.
233+
let neutralPalette = new Map(testM3ThemePalette.get('primary')!);
234+
neutralPalette.set(4, '#26000f');
235+
neutralPalette.set(6, '#2f0015');
236+
neutralPalette.set(12, '#460022');
237+
neutralPalette.set(17, '#55082c');
238+
neutralPalette.set(22, '#631637');
239+
neutralPalette.set(24, '#691a3c');
240+
neutralPalette.set(87, '#ffcdda');
241+
neutralPalette.set(92, '#ffe1e8');
242+
neutralPalette.set(94, '#ffe8ed');
243+
neutralPalette.set(96, '#fff0f2');
244+
testPalette.set('neutral', neutralPalette);
225245

226246
const testSCSS = generateSCSSTheme(
227247
testPalette,
228-
'both',
248+
['light', 'dark'],
229249
'Color palettes are generated from primary: #984061, secondary: #984061, tertiary: #984061, neutral: #984061',
230250
false,
231251
);
@@ -263,47 +283,150 @@ describe('material-m3-theme-schematic', () => {
263283
expect(generatedCSS).toContain('var(--sys-primary)');
264284
});
265285

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

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-
);
308+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
309+
310+
// Check a system variable from each color palette for their high contrast equivalent value
311+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
312+
expect(generatedCSS).toContain(`--sys-secondary: #45212d`);
313+
expect(generatedCSS).toContain(`--sys-tertiary: #4d1f00`);
314+
expect(generatedCSS).toContain(`--sys-error: #600004`);
315+
expect(generatedCSS).toContain(`--sys-surface: #fff8f8`);
316+
expect(generatedCSS).toContain(`--sys-outline: #37282c`);
317+
});
318+
319+
it('should be able to generate high contrast dark theme overrides when provided a primary color', async () => {
320+
const tree = await runM3ThemeSchematic(runner, {
321+
primaryColor: '#984061',
322+
themeTypes: 'dark',
323+
useSystemVariables: true,
324+
generateHighContrastOverrideMixins: true,
325+
});
326+
327+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
328+
329+
// Check a system variable from each color palette for their high contrast equivalent value
330+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
331+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
332+
expect(generatedCSS).toContain(`--sys-tertiary: #ffece4`);
333+
expect(generatedCSS).toContain(`--sys-error: #ffece9`);
334+
expect(generatedCSS).toContain(`--sys-surface: #191113`);
335+
expect(generatedCSS).toContain(`--sys-outline: #ffebef`);
336+
});
337+
338+
it('should be able to generate high contrast light theme overrides if useSystemVariables is not specified', async () => {
339+
const tree = await runM3ThemeSchematic(runner, {
340+
primaryColor: '#984061',
341+
themeTypes: 'light',
342+
generateHighContrastOverrideMixins: true,
343+
});
344+
345+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
346+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
347+
});
348+
349+
it('should be able to generate high contrast themes overrides when provided a primary and secondary color', async () => {
350+
const tree = await runM3ThemeSchematic(runner, {
351+
primaryColor: '#984061',
352+
secondaryColor: '#984061',
353+
themeTypes: 'both',
354+
useSystemVariables: true,
355+
generateHighContrastOverrideMixins: true,
356+
});
357+
358+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
359+
console.log(tree.readText('m3-theme.scss'));
360+
361+
// Check a system variable from each color palette for their high contrast light theme value
362+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
363+
expect(generatedCSS).toContain(`--sys-secondary: #580b2f`);
364+
365+
// Check a system variable from each color palette for their high contrast dark theme value
366+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
367+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
368+
});
369+
370+
it('should be able to generate high contrast themes overrides when provided primary, secondary, and tertiary color', async () => {
371+
const tree = await runM3ThemeSchematic(runner, {
372+
primaryColor: '#984061',
373+
secondaryColor: '#984061',
374+
tertiaryColor: '#984061',
375+
themeTypes: 'both',
376+
useSystemVariables: true,
377+
generateHighContrastOverrideMixins: true,
378+
});
379+
380+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
381+
382+
// Check a system variable from each color palette for their high contrast light theme value
383+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
384+
expect(generatedCSS).toContain(`--sys-secondary: #580b2f`);
385+
expect(generatedCSS).toContain(`--sys-tertiary: #580b2f`);
386+
387+
// Check a system variable from each color palette for their high contrast dark theme value
388+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
389+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
390+
expect(generatedCSS).toContain(`--sys-tertiary: #ffebef`);
391+
});
392+
393+
it('should be able to generate high contrast themes overrides when provided primary, secondary, tertiary, and neutral color', async () => {
394+
const tree = await runM3ThemeSchematic(runner, {
395+
primaryColor: '#984061',
396+
secondaryColor: '#984061',
397+
tertiaryColor: '#984061',
398+
neutralColor: '#dfdfdf', // Different color since #984061 does not change the tonal palette
399+
themeTypes: 'both',
400+
useSystemVariables: true,
401+
generateHighContrastOverrideMixins: true,
402+
});
403+
404+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
405+
406+
// Check a system variable from each color palette for their high contrast light theme value
407+
expect(generatedCSS).toContain(`--sys-primary: #580b2f`);
408+
expect(generatedCSS).toContain(`--sys-secondary: #580b2f`);
409+
expect(generatedCSS).toContain(`--sys-tertiary: #580b2f`);
410+
expect(generatedCSS).toContain(`--sys-surface-bright: #f9f9f9`);
411+
412+
// Check a system variable from each color palette for their high contrast dark theme value
413+
expect(generatedCSS).toContain(`--sys-primary: #ffebef`);
414+
expect(generatedCSS).toContain(`--sys-secondary: #ffebef`);
415+
expect(generatedCSS).toContain(`--sys-tertiary: #ffebef`);
416+
expect(generatedCSS).toContain(`--sys-surface-bright: #4f5051`);
417+
});
418+
419+
it('should be able to generate high contrast themes with custom system variable prefix', async () => {
420+
const tree = await runM3ThemeSchematic(runner, {
421+
primaryColor: '#984061',
422+
themeTypes: 'light',
423+
useSystemVariables: true,
424+
systemVariablePrefix: 'my-app',
425+
generateHighContrastOverrideMixins: true,
426+
});
427+
428+
const generatedCSS = transpileTheme(tree.readText('m3-theme.scss'));
429+
expect(generatedCSS).toContain(`--my-app-primary: #580b2f`);
307430
});
308431
});
309432

0 commit comments

Comments
 (0)