diff --git a/src/material/schematics/ng-generate/m3-theme/README.md b/src/material/schematics/ng-generate/m3-theme/README.md index a9f6b22d8f2c..cf993678bed6 100644 --- a/src/material/schematics/ng-generate/m3-theme/README.md +++ b/src/material/schematics/ng-generate/m3-theme/README.md @@ -19,6 +19,10 @@ specified directory or the project root with the generated themes. The exported themes (`$light-theme` and/or `$dark-theme`) can be provided to component theme mixins. +If you're using the system variables option, you should remember to either provide values for the +system variables (all prefixed with `--sys-`), or to include the `system-level-colors` and +`system-level-typography` mixins which will generate the values based on your theme. + ```scss @use '@angular/material' as mat; @use './path/to/my-theme'; @@ -29,6 +33,11 @@ html { // Apply the light theme by default @include mat.core-theme(my-theme.$light-theme); @include mat.button-theme(my-theme.$light-theme); + + // When using system variables, remember to provide values for them + // or uncomment the lines below to generate them from the theme. + // @include mat.system-level-colors(my-theme.$light-theme); + // @include mat.system-level-typography(my-theme.$light-theme); } ``` @@ -51,3 +60,5 @@ neutral color generated from Material based on the primary. * `directory` - Relative path to a directory within the project that the generated theme file should be created in. Defaults to the project root. * `themeTypes` - Theme types ('light', 'dark', or 'both') to generate themes for. Defaults to both. +* `useSystemVariables` - Whether to generate a theme that uses system-level variables for easier +dynamic theming. Defaults to false. diff --git a/src/material/schematics/ng-generate/m3-theme/index.spec.ts b/src/material/schematics/ng-generate/m3-theme/index.spec.ts index 7d116532b634..604dab59778b 100644 --- a/src/material/schematics/ng-generate/m3-theme/index.spec.ts +++ b/src/material/schematics/ng-generate/m3-theme/index.spec.ts @@ -9,158 +9,6 @@ import {pathToFileURL} from 'url'; import {generateSCSSTheme} from './index'; import {Schema} from './schema'; -const testM3ThemeScss = `// This file was generated by running 'ng generate @angular/material:m3-theme'. -// Proceed with caution if making changes to this file. - -@use 'sass:map'; -@use '@angular/material' as mat; - -// Note: Color palettes are generated from primary: #984061 -$_palettes: ( - primary: ( - 0: #000000, - 10: #3e001d, - 20: #5e1133, - 25: #6c1d3e, - 30: #7b2949, - 35: #893455, - 40: #984061, - 50: #b6587a, - 60: #d57194, - 70: #f48bae, - 80: #ffb0c8, - 90: #ffd9e2, - 95: #ffecf0, - 98: #fff8f8, - 99: #fffbff, - 100: #ffffff, - ), - secondary: ( - 0: #000000, - 10: #2b151c, - 20: #422931, - 25: #4e343c, - 30: #5a3f47, - 35: #674b53, - 40: #74565f, - 50: #8e6f77, - 60: #aa8891, - 70: #c6a2ab, - 80: #e2bdc6, - 90: #ffd9e2, - 95: #ffecf0, - 98: #fff8f8, - 99: #fffbff, - 100: #ffffff, - ), - tertiary: ( - 0: #000000, - 10: #2e1500, - 20: #48290c, - 25: #543416, - 30: #623f20, - 35: #6f4a2a, - 40: #7c5635, - 50: #986e4b, - 60: #b48862, - 70: #d1a27b, - 80: #efbd94, - 90: #ffdcc2, - 95: #ffeee2, - 98: #fff8f5, - 99: #fffbff, - 100: #ffffff, - ), - neutral: ( - 0: #000000, - 10: #201a1b, - 20: #352f30, - 25: #413a3b, - 30: #4c4546, - 35: #585052, - 40: #645c5e, - 50: #7e7576, - 60: #988e90, - 70: #b3a9aa, - 80: #cfc4c5, - 90: #ebe0e1, - 95: #faeeef, - 98: #fff8f8, - 99: #fffbff, - 100: #ffffff, - 4: #120d0e, - 6: #171213, - 12: #241e1f, - 17: #2f282a, - 22: #3a3334, - 24: #3e3739, - 87: #e3d7d9, - 92: #f1e5e7, - 94: #f7ebec, - 96: #fdf1f2, - ), - neutral-variant: ( - 0: #000000, - 10: #24191c, - 20: #3a2d30, - 25: #45383b, - 30: #514347, - 35: #5d4f52, - 40: #6a5b5e, - 50: #837377, - 60: #9e8c90, - 70: #b9a7ab, - 80: #d5c2c6, - 90: #f2dde2, - 95: #ffecf0, - 98: #fff8f8, - 99: #fffbff, - 100: #ffffff, - ), - error: ( - 0: #000000, - 10: #410002, - 20: #690005, - 25: #7e0007, - 30: #93000a, - 35: #a80710, - 40: #ba1a1a, - 50: #de3730, - 60: #ff5449, - 70: #ff897d, - 80: #ffb4ab, - 90: #ffdad6, - 95: #ffedea, - 98: #fff8f7, - 99: #fffbff, - 100: #ffffff, - ), -); - -$_rest: ( - secondary: map.get($_palettes, secondary), - neutral: map.get($_palettes, neutral), - neutral-variant: map.get($_palettes, neutral-variant), - error: map.get($_palettes, error), -); -$_primary: map.merge(map.get($_palettes, primary), $_rest); -$_tertiary: map.merge(map.get($_palettes, tertiary), $_rest); - -$light-theme: mat.define-theme(( - color: ( - theme-type: light, - primary: $_primary, - tertiary: $_tertiary, - ) -)); -$dark-theme: mat.define-theme(( - color: ( - theme-type: dark, - primary: $_primary, - tertiary: $_tertiary, - ) -));`; - // Note: For Windows compatibility, we need to resolve the directory paths through runfiles // which are guaranteed to reside in the source tree. const testDir = runfiles.resolvePackageRelative('../m3-theme'); @@ -188,12 +36,18 @@ describe('material-m3-theme-schematic', () => { ` ${content} + @mixin _theme($theme) { + @include mat.all-component-colors($theme); + @include mat.system-level-colors($theme); + @include mat.system-level-typography($theme); + } + html { @if variable-exists(light-theme) { - @include mat.all-component-colors($light-theme); + @include _theme($light-theme); } @if variable-exists(dark-theme) { - @include mat.all-component-colors($dark-theme); + @include _theme($dark-theme); } } `, @@ -255,7 +109,7 @@ describe('material-m3-theme-schematic', () => { primaryColor: '#984061', themeTypes: 'both', }); - expect(tree.readText('m3-theme.scss')).toEqual(testM3ThemeScss); + expect(tree.readText('m3-theme.scss')).toEqual(getTestTheme()); }); it('should generate light theme when provided a primary color', async () => { @@ -269,6 +123,7 @@ describe('material-m3-theme-schematic', () => { testM3ThemePalette, 'light', 'Color palettes are generated from primary: #984061', + false, ); expect(generatedSCSS).toBe(testSCSS); @@ -286,6 +141,7 @@ describe('material-m3-theme-schematic', () => { testM3ThemePalette, 'dark', 'Color palettes are generated from primary: #984061', + false, ); expect(generatedSCSS).toBe(testSCSS); @@ -303,6 +159,7 @@ describe('material-m3-theme-schematic', () => { testM3ThemePalette, 'both', 'Color palettes are generated from primary: #984061', + false, ); expect(generatedSCSS).toBe(testSCSS); @@ -327,6 +184,7 @@ describe('material-m3-theme-schematic', () => { testPalette, 'both', 'Color palettes are generated from primary: #984061, secondary: #984061', + false, ); expect(generatedSCSS).toBe(testSCSS); @@ -353,6 +211,7 @@ describe('material-m3-theme-schematic', () => { testPalette, 'both', 'Color palettes are generated from primary: #984061, secondary: #984061, tertiary: #984061', + false, ); expect(generatedSCSS).toBe(testSCSS); @@ -381,13 +240,197 @@ describe('material-m3-theme-schematic', () => { testPalette, 'both', 'Color palettes are generated from primary: #984061, secondary: #984061, tertiary: #984061, neutral: #984061', + false, ); expect(generatedSCSS).toBe(testSCSS); expect(transpileTheme(generatedSCSS)).toBe(transpileTheme(testSCSS)); }); + + it('should be able to generate a theme using system variables', async () => { + const primaryColor = '#984061'; + const tree = await runM3ThemeSchematic(runner, { + primaryColor, + themeTypes: 'light', + useSystemVariables: true, + }); + + const generatedSCSS = tree.readText('m3-theme.scss'); + const generatedCSS = transpileTheme(generatedSCSS); + + expect(generatedSCSS).toContain( + [ + ` color: (`, + ` theme-type: light,`, + ` primary: $_primary,`, + ` tertiary: $_tertiary,`, + ` use-system-variables: true,`, + ` ),`, + ` typography: (`, + ` use-system-variables: true,`, + ` ),`, + ].join('\n'), + ); + + expect(generatedCSS).toContain(`--sys-primary: ${primaryColor}`); + expect(generatedCSS).toContain('var(--sys-primary)'); + }); }); +function getTestTheme() { + return `// This file was generated by running 'ng generate @angular/material:m3-theme'. +// Proceed with caution if making changes to this file. + +@use 'sass:map'; +@use '@angular/material' as mat; + +// Note: Color palettes are generated from primary: #984061 +$_palettes: ( + primary: ( + 0: #000000, + 10: #3e001d, + 20: #5e1133, + 25: #6c1d3e, + 30: #7b2949, + 35: #893455, + 40: #984061, + 50: #b6587a, + 60: #d57194, + 70: #f48bae, + 80: #ffb0c8, + 90: #ffd9e2, + 95: #ffecf0, + 98: #fff8f8, + 99: #fffbff, + 100: #ffffff, + ), + secondary: ( + 0: #000000, + 10: #2b151c, + 20: #422931, + 25: #4e343c, + 30: #5a3f47, + 35: #674b53, + 40: #74565f, + 50: #8e6f77, + 60: #aa8891, + 70: #c6a2ab, + 80: #e2bdc6, + 90: #ffd9e2, + 95: #ffecf0, + 98: #fff8f8, + 99: #fffbff, + 100: #ffffff, + ), + tertiary: ( + 0: #000000, + 10: #2e1500, + 20: #48290c, + 25: #543416, + 30: #623f20, + 35: #6f4a2a, + 40: #7c5635, + 50: #986e4b, + 60: #b48862, + 70: #d1a27b, + 80: #efbd94, + 90: #ffdcc2, + 95: #ffeee2, + 98: #fff8f5, + 99: #fffbff, + 100: #ffffff, + ), + neutral: ( + 0: #000000, + 10: #201a1b, + 20: #352f30, + 25: #413a3b, + 30: #4c4546, + 35: #585052, + 40: #645c5e, + 50: #7e7576, + 60: #988e90, + 70: #b3a9aa, + 80: #cfc4c5, + 90: #ebe0e1, + 95: #faeeef, + 98: #fff8f8, + 99: #fffbff, + 100: #ffffff, + 4: #120d0e, + 6: #171213, + 12: #241e1f, + 17: #2f282a, + 22: #3a3334, + 24: #3e3739, + 87: #e3d7d9, + 92: #f1e5e7, + 94: #f7ebec, + 96: #fdf1f2, + ), + neutral-variant: ( + 0: #000000, + 10: #24191c, + 20: #3a2d30, + 25: #45383b, + 30: #514347, + 35: #5d4f52, + 40: #6a5b5e, + 50: #837377, + 60: #9e8c90, + 70: #b9a7ab, + 80: #d5c2c6, + 90: #f2dde2, + 95: #ffecf0, + 98: #fff8f8, + 99: #fffbff, + 100: #ffffff, + ), + error: ( + 0: #000000, + 10: #410002, + 20: #690005, + 25: #7e0007, + 30: #93000a, + 35: #a80710, + 40: #ba1a1a, + 50: #de3730, + 60: #ff5449, + 70: #ff897d, + 80: #ffb4ab, + 90: #ffdad6, + 95: #ffedea, + 98: #fff8f7, + 99: #fffbff, + 100: #ffffff, + ), +); + +$_rest: ( + secondary: map.get($_palettes, secondary), + neutral: map.get($_palettes, neutral), + neutral-variant: map.get($_palettes, neutral-variant), + error: map.get($_palettes, error), +); +$_primary: map.merge(map.get($_palettes, primary), $_rest); +$_tertiary: map.merge(map.get($_palettes, tertiary), $_rest); + +$light-theme: mat.define-theme(( + color: ( + theme-type: light, + primary: $_primary, + tertiary: $_tertiary, + ), +)); +$dark-theme: mat.define-theme(( + color: ( + theme-type: dark, + primary: $_primary, + tertiary: $_tertiary, + ), +));`; +} + function getPaletteMap() { // Hue maps created from https://m3.material.io/theme-builder#/custom (using // #984061 as source color). Not using predefined M3 palettes since some neutral diff --git a/src/material/schematics/ng-generate/m3-theme/index.ts b/src/material/schematics/ng-generate/m3-theme/index.ts index 792c032b3db8..52dd85e472b2 100644 --- a/src/material/schematics/ng-generate/m3-theme/index.ts +++ b/src/material/schematics/ng-generate/m3-theme/index.ts @@ -100,12 +100,14 @@ function getColorPalettesSCSS(colorPalettes: Map>): * @param colorPalettes Map of colors and their hue tones and values. * @param themeTypes Theme types for the theme (ex. 'light', 'dark', or 'both'). * @param colorComment Comment with original hex colors used to generate palettes. + * @param useSystemVariables Whether to use system-level variables in the generated theme. * @returns String of the generated theme scss. */ export function generateSCSSTheme( colorPalettes: Map>, themeTypes: string, colorComment: string, + useSystemVariables: boolean, ): string { let scss = [ "// This file was generated by running 'ng generate @angular/material:m3-theme'.", @@ -138,7 +140,9 @@ export function generateSCSSTheme( ' theme-type: ' + themeType + ',', ' primary: $_primary,', ' tertiary: $_tertiary,', - ' )', + ...(useSystemVariables ? [' use-system-variables: true,'] : []), + ' ),', + ...(useSystemVariables ? [' typography: (', ' use-system-variables: true,', ' ),'] : []), '));', ]); } @@ -179,7 +183,12 @@ export default function (options: Schema): Rule { options.themeTypes = 'both'; } - const themeScss = generateSCSSTheme(colorPalettes, options.themeTypes, colorComment); + const themeScss = generateSCSSTheme( + colorPalettes, + options.themeTypes, + colorComment, + options.useSystemVariables || false, + ); createThemeFile(themeScss, tree, options.directory); }; } diff --git a/src/material/schematics/ng-generate/m3-theme/schema.d.ts b/src/material/schematics/ng-generate/m3-theme/schema.d.ts index 59d25b5ebe59..c44aa47dde13 100644 --- a/src/material/schematics/ng-generate/m3-theme/schema.d.ts +++ b/src/material/schematics/ng-generate/m3-theme/schema.d.ts @@ -27,6 +27,10 @@ export interface Schema { * Type for theme (ex. 'light', 'dark', or 'both'). */ themeTypes?: string; + /** + * Whether to use system-level variables in the theme. + */ + useSystemVariables?: boolean; /* * Workspace-relative path to a directory where the file with the custom M3 * theme will be generated. diff --git a/src/material/schematics/ng-generate/m3-theme/schema.json b/src/material/schematics/ng-generate/m3-theme/schema.json index 97ba4d8dfd2f..3118570cab0a 100644 --- a/src/material/schematics/ng-generate/m3-theme/schema.json +++ b/src/material/schematics/ng-generate/m3-theme/schema.json @@ -29,6 +29,12 @@ "description": "Workspace-relative path to a directory where generated theme file will be created", "x-prompt": "What is the directory you want to place the generated theme file in? (Enter the relative path such as 'src/app/styles/' or leave blank to generate at your project root)" }, + "useSystemVariables": { + "type": "boolean", + "default": false, + "description": "Whether to use system-level variables for the theme.", + "x-prompt": "Do you want to use system-level variables in the theme? System-level variables make dynamic theming easier through CSS custom properties, but increase the bundle size." + }, "themeTypes": { "type": "string", "enum": ["light", "dark", "both"],