diff --git a/src/material/schematics/ng-add/index.spec.ts b/src/material/schematics/ng-add/index.spec.ts index e8c240462a10..9101f6a017c9 100644 --- a/src/material/schematics/ng-add/index.spec.ts +++ b/src/material/schematics/ng-add/index.spec.ts @@ -87,7 +87,7 @@ describe('ng-add schematic', () => { const workspace = await readWorkspace(tree); const project = getProjectFromWorkspace(workspace, baseOptions.project); - expectProjectStyleFile(project, '@angular/material/prebuilt-themes/azure-blue.css'); + expectProjectStyleFile(project, 'projects/material/src/custom-theme.scss'); }); it('should support adding a custom theme', async () => { @@ -96,7 +96,7 @@ describe('ng-add schematic', () => { const tree = await runner.runSchematic( 'ng-add-setup-project', - {...baseOptions, theme: 'custom'}, + {...baseOptions, theme: 'azure-blue'}, appTree, ); const workspace = await readWorkspace(tree); @@ -116,7 +116,7 @@ describe('ng-add schematic', () => { const tree = await runner.runSchematic( 'ng-add-setup-project', - {...baseOptions, theme: 'custom'}, + {...baseOptions, theme: 'azure-blue'}, appTree, ); const workspace = await readWorkspace(tree); @@ -213,108 +213,14 @@ describe('ng-add schematic', () => { runner.runSchematic('ng-add-setup-project', baseOptions, appTree), ).toBeRejected(); }); - - it('should warn if the "test" target has been changed', async () => { - overwriteTargetBuilder(appTree, 'test', 'thirdparty-test-builder'); - await runner.runSchematic('ng-add-setup-project', baseOptions, appTree); - - expect(errorOutput.length).toBe(0); - expect(warnOutput.length).toBe(1); - expect(warnOutput[0]).toMatch( - /not using the default builders.*cannot add the configured theme/, - ); - }); }); describe('theme files', () => { - /** Path to the default prebuilt theme file that will be added when running ng-add. */ - const defaultPrebuiltThemePath = '@angular/material/prebuilt-themes/azure-blue.css'; - - /** Writes a specific style file to the workspace in the given tree */ - function writeStyleFileToWorkspace(tree: Tree, stylePath: string) { - tree.overwrite( - '/angular.json', - JSON.stringify( - { - version: 1, - projects: { - material: { - projectType: 'application', - root: 'projects/material', - sourceRoot: 'projects/material/src', - prefix: 'app', - architect: { - build: { - builder: '@angular-devkit/build-angular:application', - options: { - outputPath: 'dist/material', - index: 'projects/material/src/index.html', - browser: 'projects/material/src/main.ts', - styles: ['projects/material/src/styles.css', stylePath], - }, - }, - }, - }, - }, - }, - null, - 2, - ), - ); - } - - it('should replace existing prebuilt theme files', async () => { - const existingThemePath = '@angular/material/prebuilt-themes/purple-green.css'; - writeStyleFileToWorkspace(appTree, existingThemePath); - - const tree = await runner.runSchematic('ng-add-setup-project', baseOptions, appTree); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - const styles = getProjectTargetOptions(project, 'build')['styles']; - - expect(styles) - .not.withContext('Expected the existing prebuilt theme file to be removed.') - .toContain(existingThemePath); - expect(styles) - .withContext('Expected the default prebuilt theme to be added.') - .toContain(defaultPrebuiltThemePath); - }); - - it('should not replace existing custom theme files', async () => { - writeStyleFileToWorkspace(appTree, './projects/material/custom-theme.scss'); - - const tree = await runner.runSchematic('ng-add-setup-project', baseOptions, appTree); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - const styles = getProjectTargetOptions(project, 'build')['styles']; - - expect(styles) - .not.withContext('Expected the default prebuilt theme to be not configured.') - .toContain(defaultPrebuiltThemePath); - expect(errorOutput.length).toBe(1); - expect(errorOutput[0]).toMatch(/Could not add the selected theme/); - }); - - it('should not add a theme file multiple times', async () => { - writeStyleFileToWorkspace(appTree, defaultPrebuiltThemePath); - - const tree = await runner.runSchematic('ng-add-setup-project', baseOptions, appTree); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - const styles = getProjectTargetOptions(project, 'build')['styles']; - - expect(styles) - .withContext( - 'Expected the "styles.css" file and default prebuilt theme to be ' + 'the only styles', - ) - .toEqual(['projects/material/src/styles.css', defaultPrebuiltThemePath]); - }); - it('should not overwrite existing custom theme files', async () => { appTree.create('/projects/material/custom-theme.scss', 'custom-theme'); const tree = await runner.runSchematic( 'ng-add-setup-project', - {...baseOptions, theme: 'custom'}, + {...baseOptions, theme: 'azure-blue'}, appTree, ); expect(tree.readContent('/projects/material/custom-theme.scss')) @@ -323,117 +229,6 @@ describe('ng-add schematic', () => { }); }); - it('should add the global typography class if the body has no classes', async () => { - const tree = await runner.runSchematic( - 'ng-add-setup-project', - { - ...baseOptions, - typography: true, - }, - appTree, - ); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - - const indexFiles = getProjectIndexFiles(project); - expect(indexFiles.length).toBe(1); - - indexFiles.forEach(indexPath => { - const buffer = tree.read(indexPath)!; - expect(buffer.toString()).toContain(''); - }); - }); - - it('should add the global typography class if the body has existing classes', async () => { - appTree.overwrite( - 'projects/material/src/index.html', - ` - - - - - `, - ); - - const tree = await runner.runSchematic( - 'ng-add-setup-project', - { - ...baseOptions, - typography: true, - }, - appTree, - ); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - const indexFiles = getProjectIndexFiles(project); - expect(indexFiles.length).toBe(1); - - indexFiles.forEach(indexPath => { - const buffer = tree.read(indexPath)!; - expect(buffer.toString()).toContain(''); - }); - }); - - it('should not add the global typography class if it exists already', async () => { - appTree.overwrite( - 'projects/material/src/index.html', - ` - - - - - `, - ); - - const tree = await runner.runSchematic( - 'ng-add-setup-project', - { - ...baseOptions, - typography: true, - }, - appTree, - ); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - const indexFiles = getProjectIndexFiles(project); - expect(indexFiles.length).toBe(1); - - indexFiles.forEach(indexPath => { - const buffer = tree.read(indexPath)!; - expect(buffer.toString()).toContain(''); - }); - }); - - it('should not add the global typography class if the user did not opt into it', async () => { - appTree.overwrite( - 'projects/material/src/index.html', - ` - - - - - `, - ); - - const tree = await runner.runSchematic( - 'ng-add-setup-project', - { - ...baseOptions, - typography: false, - }, - appTree, - ); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - const indexFiles = getProjectIndexFiles(project); - expect(indexFiles.length).toBe(1); - - indexFiles.forEach(indexPath => { - const buffer = tree.read(indexPath)!; - expect(buffer.toString()).toContain(''); - }); - }); - describe('using browser builder', () => { beforeEach(() => { const config = { @@ -476,21 +271,7 @@ describe('ng-add schematic', () => { const workspace = await readWorkspace(tree); const project = getProjectFromWorkspace(workspace, baseOptions.project); - expectProjectStyleFile(project, '@angular/material/prebuilt-themes/azure-blue.css'); - }); - - it('should add material app styles', async () => { - const tree = await runner.runSchematic('ng-add-setup-project', baseOptions, appTree); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - - const defaultStylesPath = getProjectStyleFile(project)!; - const htmlContent = tree.read(defaultStylesPath)!.toString(); - - expect(htmlContent).toContain('html, body { height: 100%; }'); - expect(htmlContent).toContain( - 'body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }', - ); + expectProjectStyleFile(project, 'projects/material/src/custom-theme.scss'); }); }); @@ -536,21 +317,7 @@ describe('ng-add schematic', () => { const workspace = await readWorkspace(tree); const project = getProjectFromWorkspace(workspace, baseOptions.project); - expectProjectStyleFile(project, '@angular/material/prebuilt-themes/azure-blue.css'); - }); - - it('should add material app styles', async () => { - const tree = await runner.runSchematic('ng-add-setup-project', baseOptions, appTree); - const workspace = await readWorkspace(tree); - const project = getProjectFromWorkspace(workspace, baseOptions.project); - - const defaultStylesPath = getProjectStyleFile(project)!; - const htmlContent = tree.read(defaultStylesPath)!.toString(); - - expect(htmlContent).toContain('html, body { height: 100%; }'); - expect(htmlContent).toContain( - 'body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }', - ); + expectProjectStyleFile(project, 'projects/material/src/custom-theme.scss'); }); }); @@ -587,7 +354,7 @@ describe('ng-add schematic', () => { const workspace = await readWorkspace(tree); const project = getProjectFromWorkspace(workspace, baseOptions.project); - expectProjectStyleFile(project, '@angular/material/prebuilt-themes/azure-blue.css'); + expectProjectStyleFile(project, 'projects/material/src/custom-theme.scss'); }); }); }); diff --git a/src/material/schematics/ng-add/schema.json b/src/material/schematics/ng-add/schema.json index 7017356a89ff..683e954780f6 100644 --- a/src/material/schematics/ng-add/schema.json +++ b/src/material/schematics/ng-add/schema.json @@ -16,7 +16,7 @@ "type": "string", "default": "azure-blue", "x-prompt": { - "message": "Choose a prebuilt theme name, or \"custom\" for a custom theme:", + "message": "Select a pair of starter prebuilt color palettes for your Angular Material theme", "type": "list", "items": [ { @@ -34,16 +34,9 @@ { "value": "cyan-orange", "label": "Cyan/Orange [Preview: https://material.angular.dev?theme=cyan-orange]" - }, - {"value": "custom", "label": "Custom"} + } ] } - }, - "typography": { - "type": "boolean", - "default": false, - "description": "Whether to set up global typography styles.", - "x-prompt": "Set up global Angular Material typography styles?" } }, "required": [] diff --git a/src/material/schematics/ng-add/setup-project.ts b/src/material/schematics/ng-add/setup-project.ts index dfb077b7f227..f0897ade7206 100644 --- a/src/material/schematics/ng-add/setup-project.ts +++ b/src/material/schematics/ng-add/setup-project.ts @@ -6,13 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ -import {chain, noop, Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; +import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import {getProjectFromWorkspace, getProjectStyleFile} from '@angular/cdk/schematics'; import {readWorkspace} from '@schematics/angular/utility'; import {ProjectType} from '@schematics/angular/utility/workspace-models'; import {addFontsToIndex} from './fonts/material-fonts'; import {Schema} from './schema'; -import {addThemeToAppStyles, addTypographyClass} from './theming/theming'; +import {addThemeToAppStyles} from './theming/theming'; /** * Scaffolds the basics of a Angular Material application, this includes: @@ -29,7 +29,6 @@ export default function (options: Schema): Rule { addThemeToAppStyles(options), addFontsToIndex(options), addMaterialAppStyles(options), - options.typography ? addTypographyClass(options) : noop(), ]); } context.logger.warn( diff --git a/src/material/schematics/ng-add/theming/create-custom-theme.ts b/src/material/schematics/ng-add/theming/create-custom-theme.ts index d490edef7b90..91c8b0ad9660 100644 --- a/src/material/schematics/ng-add/theming/create-custom-theme.ts +++ b/src/material/schematics/ng-add/theming/create-custom-theme.ts @@ -7,21 +7,41 @@ */ /** Create custom theme for the given application configuration. */ -export function createCustomTheme(name: string = 'app') { +export function createCustomTheme(userPaletteChoice: string) { + const colorPalettes = new Map([ + ['azure-blue', {primary: 'azure', tertiary: 'blue'}], + ['rose-red', {primary: 'rose', tertiary: 'red'}], + ['magenta-violet', {primary: 'magenta', tertiary: 'violet'}], + ['cyan-orange', {primary: 'cyan', tertiary: 'orange'}], + ]); return ` -// Custom Theming for Angular Material -// For more information: https://material.angular.dev/guide/theming +// Include theming for Angular Material with \`mat.theme()\`. +// This Sass mixin will define CSS variables that are used for styling Angular Material +// components according to the Material 3 design spec. +// Learn more about theming and how to use it for your application's +// custom components at https://material.angular.dev/guide/theming @use '@angular/material' as mat; html { @include mat.theme(( color: ( - theme-type: light, - primary: mat.$azure-palette, - tertiary: mat.$blue-palette, + primary: mat.$${colorPalettes.get(userPaletteChoice)!.primary}-palette, + tertiary: mat.$${colorPalettes.get(userPaletteChoice)!.tertiary}-palette, ), typography: Roboto, density: 0, )); + + // Default the application to a light color theme. This can be changed to + // \`dark\` to enable the dark color theme, or to \`light dark\` to defer to the + // user's system settings. + color-scheme: light; + + // Set a default background, font and text colors for the application using + // Angular Material's system-level CSS variables. Learn more about these + // variables at https://material.angular.dev/guide/system-variables + background-color: var(--mat-sys-surface); + color: var(--mat-sys-on-surface); + font: var(--mat-sys-body-medium); }`; } diff --git a/src/material/schematics/ng-add/theming/theming.ts b/src/material/schematics/ng-add/theming/theming.ts index 7be0e02ff941..ed9263dfd49a 100644 --- a/src/material/schematics/ng-add/theming/theming.ts +++ b/src/material/schematics/ng-add/theming/theming.ts @@ -7,20 +7,11 @@ */ import {normalize, logging} from '@angular-devkit/core'; +import {noop, Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; import { - chain, - noop, - Rule, - SchematicContext, - SchematicsException, - Tree, -} from '@angular-devkit/schematics'; -import { - addBodyClass, getProjectFromWorkspace, getProjectStyleFile, getProjectTargetOptions, - getProjectIndexFiles, getProjectTestTargets, getProjectBuildTargets, } from '@angular/cdk/schematics'; @@ -39,33 +30,17 @@ const defaultCustomThemeFilename = 'custom-theme.scss'; /** Add pre-built styles to the main project style file. */ export function addThemeToAppStyles(options: Schema): Rule { return (host: Tree, context: SchematicContext) => { - const themeName = options.theme || 'azure-blue'; - return themeName === 'custom' - ? insertCustomTheme(options.project, host, context.logger) - : insertPrebuiltTheme(options.project, themeName, context.logger); - }; -} - -/** Adds the global typography class to the body element. */ -export function addTypographyClass(options: Schema): Rule { - return async (host: Tree) => { - const workspace = await readWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); - const projectIndexFiles = getProjectIndexFiles(project); - - if (!projectIndexFiles.length) { - throw new SchematicsException('No project index HTML file could be found.'); - } - - projectIndexFiles.forEach(path => addBodyClass(host, path, 'mat-typography')); + const palettes = options.theme || 'azure-blue'; + return insertCustomTheme(palettes, options.project, host, context.logger); }; } /** - * Insert a custom theme to project style file. If no valid style file could be found, a new + * Insert an Angular Material theme to project style file. If no valid style file could be found, a new * Scss file for the custom theme will be created. */ async function insertCustomTheme( + palettes: string, projectName: string, host: Tree, logger: logging.LoggerApi, @@ -73,7 +48,7 @@ async function insertCustomTheme( const workspace = await readWorkspace(host); const project = getProjectFromWorkspace(workspace, projectName); const stylesPath = getProjectStyleFile(project, 'scss'); - const themeContent = createCustomTheme(projectName); + const themeContent = createCustomTheme(palettes); if (!stylesPath) { if (!project.sourceRoot) { @@ -89,7 +64,7 @@ async function insertCustomTheme( if (host.exists(customThemePath)) { logger.warn(`Cannot create a custom Angular Material theme because - ${customThemePath} already exists. Skipping custom theme generation.`); + ${customThemePath} already exists. Skipping theme generation.`); return noop(); } @@ -105,16 +80,6 @@ async function insertCustomTheme( return noop(); } -/** Insert a pre-built theme into the angular.json file. */ -function insertPrebuiltTheme(project: string, theme: string, logger: logging.LoggerApi): Rule { - const themePath = `@angular/material/prebuilt-themes/${theme}.css`; - - return chain([ - addThemeStyleToTarget(project, 'build', themePath, logger), - addThemeStyleToTarget(project, 'test', themePath, logger), - ]); -} - /** Adds a theming style entry to the given project target options. */ function addThemeStyleToTarget( projectName: string,