Skip to content

Commit

Permalink
feat(material/schematics): Update custom theme schematic to work with…
Browse files Browse the repository at this point in the history
… light-dark and use theme-overrides mixin (#29911)
  • Loading branch information
amysorto authored Oct 23, 2024
1 parent 4b49d73 commit 3fc1f9a
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 26 deletions.
45 changes: 41 additions & 4 deletions src/material/schematics/ng-generate/theme-color/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,47 @@ html {
}
```

High contrast override theme mixins are also generated in the file if specified
(`high-contrast-light-theme-overrides` and `high-contrast-dark-theme-overrides`). These mixins
## High contrast override mixins
High contrast override theme mixins are also generated in the file if specified. These mixins
override the system level variables with high contrast equivalent values from your theme. This is
helpful for users who prefer more contrastful colors for either preference or accessibility reasons.

### Creating one theme for light and dark mode
As of v19, the `theme` mixin can create one theme that detects and adapts to a user if they have
light or dark theme with the [`light-dark` function](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark).

Apply the `high-contrast-overrides(color-scheme)` mixin wrapped inside `@media (prefers-contrast: more)`.

```scss
@use '@angular/material';
@use './path/to/my-theme'; // location of generated file

html {
// Must specify color-scheme for theme mixin to automatically work
color-scheme: light;

// Create one theme that works automatically for light and dark theme
@include material.theme((
color: (
primary: my-theme.$primary-palette,
tertiary: my-theme.$tertiary-palette,
),
typography: Roboto,
density: 0,
));

// Use high contrast values when users prefer contrast
@media (prefers-contrast: more) {
@include my-theme.high-contrast-overrides(color-scheme);
}
}
```

### Creating separate themes for light and dark mode
You can manually define the light theme and dark theme separately. This is recommended if you need
granular control over when to show each specific theme in your application. Prior to v19, this was
the only way to create light and dark themes.

```scss
@use '@angular/material';
@use './path/to/my-theme'; // location of generated file
Expand All @@ -49,14 +85,15 @@ html {
color: (
primary: my-theme.$primary-palette,
tertiary: my-theme.$tertiary-palette,
theme-type: light,
),
typography: Roboto,
density: 0,
));

// Use high contrast light theme colors when users prefer contrast
@media (prefers-contrast: more) {
@include my-theme.high-contrast-light-theme-overrides();
@include my-theme.high-contrast-overrides(light);
}

// Apply dark theme when users prefer a dark color scheme
Expand All @@ -71,7 +108,7 @@ html {

// Use high contrast dark theme colors when users prefers a dark color scheme and contrast
@media (prefers-contrast: more) {
@include my-theme.high-contrast-dark-theme-overrides();
@include my-theme.high-contrast-overrides(dark);
}
}
}
Expand Down
13 changes: 6 additions & 7 deletions src/material/schematics/ng-generate/theme-color/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ describe('material-theme-color-schematic', () => {
),
));
@if mixin-exists(high-contrast-light-theme-overrides) {
@if mixin-exists(high-contrast-overrides) {
& {
@include high-contrast-light-theme-overrides();
@include high-contrast-overrides(light);
}
}
Expand All @@ -48,9 +48,9 @@ describe('material-theme-color-schematic', () => {
),
));
@if mixin-exists(high-contrast-dark-theme-overrides) {
@if mixin-exists(high-contrast-overrides) {
& {
@include high-contrast-dark-theme-overrides();
@include high-contrast-overrides(dark);
}
}
}
Expand Down Expand Up @@ -213,16 +213,15 @@ describe('material-theme-color-schematic', () => {
expect(transpileTheme(generatedSCSS)).toBe(transpileTheme(testSCSS));
});

it('should be able to generate high contrast theme mixins', async () => {
it('should be able to generate high contrast overrides mixin', async () => {
const tree = await runM3ThemeSchematic(runner, {
primaryColor: '#984061',
includeHighContrast: true,
});

const generatedSCSS = tree.readText('_theme-colors.scss');

expect(generatedSCSS).toContain(`@mixin high-contrast-light-theme-overrides`);
expect(generatedSCSS).toContain(`@mixin high-contrast-dark-theme-overrides`);
expect(generatedSCSS).toContain(`@mixin high-contrast-overrides`);
});

it('should be able to generate high contrast themes overrides when provided a primary color', async () => {
Expand Down
63 changes: 48 additions & 15 deletions src/material/schematics/ng-generate/theme-color/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ function getHighContrastOverides(
overrides.set('surface-dim', hexFromArgb(scheme.surfaceDim));
overrides.set('surface-bright', hexFromArgb(scheme.surfaceBright));
overrides.set('surface-container-lowest', hexFromArgb(scheme.surfaceContainerLowest));
overrides.set('surface-container-lowest', hexFromArgb(scheme.surfaceContainerLow));
overrides.set('surface-container', hexFromArgb(scheme.surfaceContainer));
overrides.set('surface-container-high', hexFromArgb(scheme.surfaceContainerHigh));
overrides.set('surface-container-highest', hexFromArgb(scheme.surfaceContainerHighest));
Expand Down Expand Up @@ -278,22 +277,56 @@ function generateHighContrastOverrideMixinsSCSS(
neutralPalette: TonalPalette,
neutralVariantPalette: TonalPalette,
): string {
const lightOverrides = getHighContrastOverides(
primaryPalette,
secondaryPalette,
tertiaryPalette,
neutralPalette,
neutralVariantPalette,
/** isDark **/ false,
);

const darkOverrides = getHighContrastOverides(
primaryPalette,
secondaryPalette,
tertiaryPalette,
neutralPalette,
neutralVariantPalette,
/** isDark **/ true,
);

// Create private function to grab correct values based on theme-type
let scss = '\n';
for (const themeType of ['light', 'dark']) {
const overrides = getHighContrastOverides(
primaryPalette,
secondaryPalette,
tertiaryPalette,
neutralPalette,
neutralVariantPalette,
themeType === 'dark',
);
scss += '\n@mixin high-contrast-' + themeType + '-theme-overrides {\n';
for (const [key, value] of overrides!.entries()) {
scss += ' --mat-sys-' + key + ': ' + value + ';\n';
}
scss += '};\n';
scss += '\n@function _high-contrast-value($light, $dark, $theme-type) {\n';
scss += ' @if ($theme-type == light) {\n';
scss += ' @return $light;\n';
scss += ' }\n';
scss += ' @if ($theme-type == dark) {\n';
scss += ' @return $dark;\n';
scss += ' }\n';
scss += ' @if ($theme-type == color-scheme) {\n';
scss += ' @return light-dark(#{$light}, #{$dark});\n';
scss += ' }\n';
scss +=
" \n @error 'Unknown theme-type #{$theme-type}. Expected light, dark, or color-scheme';\n";
scss += '}\n';

// Create high contrast mixin with theme-type input that can be light, dark, or color-scheme.
scss += '\n@mixin high-contrast-overrides($theme-type) {\n';
scss += ' @include mat.theme-overrides((\n';
for (const [key, value] of lightOverrides!.entries()) {
scss +=
' ' +
key +
': _high-contrast-value(' +
value +
', ' +
darkOverrides.get(key) +
', $theme-type),\n';
}
scss += ' ))\n';
scss += ' }\n';

return scss;
}

Expand Down

0 comments on commit 3fc1f9a

Please sign in to comment.