Skip to content

Commit

Permalink
fix(multiple): derive all token values from theme (#28664)
Browse files Browse the repository at this point in the history
During the transition to tokens, we ended up with a bunch of places with hardcoded values in the form of `if($is-dark, #fff, #000)`. This is problematic for custom palettes, because the value is always hardcoded.

These changes attempt to derive the same values from the palette directly.

Fixes #26056.
  • Loading branch information
crisbeto committed Feb 29, 2024
1 parent 26b376e commit cfdfa9a
Show file tree
Hide file tree
Showing 25 changed files with 257 additions and 139 deletions.
4 changes: 2 additions & 2 deletions src/material/core/theming/_inspection.scss
Expand Up @@ -75,8 +75,8 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
@function get-theme-color($theme, $args...) {
$version: get-theme-version($theme);
$args-count: list.length($args);
@if $args-count != 1 and $args-count != 2 {
@error #{'Expected 2 or 3 arguments. Got:'} $args-count + 1;
@if $args-count != 1 and $args-count != 2 and $args-count != 3 {
@error #{'Expected between 2 and 4 arguments. Got:'} $args-count + 1;
}

@if $version == 0 {
Expand Down
Expand Up @@ -328,7 +328,7 @@ describe('theming inspection api', () => {
color: mat.get-theme-color($theme);
}
`),
).toThrowError(/Expected 2 or 3 arguments. Got: 1/);
).toThrowError(/Expected between 2 and 4 arguments\. Got: 1/);
});

it('should get typography properties from theme', () => {
Expand Down
3 changes: 3 additions & 0 deletions src/material/core/tokens/_token-utils.scss
Expand Up @@ -9,6 +9,9 @@
@use '../theming/theming';
@use '../typography/typography';

// Indicates whether we're building internally. Used for backwards compatibility.
$private-is-internal-build: false;

$_placeholder-color-palette: theming.define-palette(palette.$red-palette);

// Placeholder color config that can be passed to token getter functions when generating token
Expand Down
3 changes: 1 addition & 2 deletions src/material/core/tokens/m2/mat/_datepicker.scss
Expand Up @@ -50,7 +50,6 @@ $private-default-overlap-color: #a8dab5;
$preview-outline-color: $divider-color;
$today-disabled-outline-color: null;
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);

$primary-color: inspection.get-theme-color($theme, primary);
$range-tokens: get-range-color-tokens(private-get-range-background-color($primary-color));
Expand All @@ -75,7 +74,7 @@ $private-default-overlap-color: #a8dab5;
@return sass-utils.merge-all($calendar-tokens, $toggle-tokens, $range-tokens, (
toggle-icon-color: $inactive-icon-color,
calendar-body-label-text-color: $secondary-text-color,
calendar-period-button-text-color: $on-surface,
calendar-period-button-text-color: inspection.get-theme-color($theme, foreground, text, 1),
calendar-period-button-icon-color: $inactive-icon-color,
calendar-navigation-button-icon-color: $inactive-icon-color,
calendar-header-divider-color: $divider-color,
Expand Down
43 changes: 30 additions & 13 deletions src/material/core/tokens/m2/mat/_fab-small.scss
Expand Up @@ -18,7 +18,6 @@ $prefix: (mat, fab-small);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$ripple-opacities: if($is-dark,
mdc-ripple-theme.$light-ink-opacities,
mdc-ripple-theme.$dark-ink-opacities
Expand All @@ -29,13 +28,13 @@ $prefix: (mat, fab-small);
foreground-color: inspection.get-theme-color($theme, foreground, base),

// Color of the element that shows the hover, focus and pressed states.
state-layer-color: $on-surface,
state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the element that shows the hover, focus and pressed states while disabled.
disabled-state-layer-color: $on-surface,
disabled-state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the ripple element.
ripple-color: rgba($on-surface, 0.1),
ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1),

// Opacity of the ripple when the button is hovered.
hover-state-layer-opacity: map.get($ripple-opacities, hover),
Expand All @@ -48,24 +47,42 @@ $prefix: (mat, fab-small);

// MDC doesn't have tokens for disabled FABs so we need to implemented them ourselves.
// Background color of the container when the FAB is disabled.
disabled-state-container-color: rgba($on-surface, 0.12),
disabled-state-container-color: inspection.get-theme-color($theme, background, disabled-button,
0.12),

// Color of the icons and projected text when the FAB is disabled.
disabled-state-foreground-color: rgba($on-surface, if($is-dark, 0.5, 0.38)),
disabled-state-foreground-color: inspection.get-theme-color($theme, foreground, disabled-button,
if($is-dark, 0.5, 0.38)),
);
}

// Generates the mapping for the properties that change based on the FAB palette color.
@function private-get-color-palette-color-tokens($theme, $palette-name) {
$is-dark: inspection.get-theme-type($theme) == dark;
$container-color: inspection.get-theme-color($theme, $palette-name);
$contrast-tone: mdc-helpers.variable-safe-contrast-tone($container-color, $is-dark);
$color: if($contrast-tone == 'dark', #000, #fff);
// Ideally we would derive all values directly from the theme, but it causes a lot of regressions
// internally. For now we fall back to the old hardcoded behavior only for internal apps.
$foreground-color: null;
$state-layer-color: null;
$ripple-color: null;

@if (token-utils.$private-is-internal-build) {
$is-dark: inspection.get-theme-type($theme) == dark;
$container-color: inspection.get-theme-color($theme, $palette-name);
$contrast-tone: mdc-helpers.variable-safe-contrast-tone($container-color, $is-dark);
$color: if($contrast-tone == 'dark', #000, #fff);
$foreground-color: $color;
$state-layer-color: $color;
$ripple-color: rgba($color, 0.1);
}
@else {
$foreground-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 1);
$state-layer-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 1);
$ripple-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 0.1);
}

@return (
foreground-color: $color,
state-layer-color: $color,
ripple-color: rgba($color, 0.1),
foreground-color: $foreground-color,
state-layer-color: $state-layer-color,
ripple-color: $ripple-color,
);
}

Expand Down
43 changes: 30 additions & 13 deletions src/material/core/tokens/m2/mat/_fab.scss
Expand Up @@ -18,7 +18,6 @@ $prefix: (mat, fab);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$ripple-opacities: if($is-dark,
mdc-ripple-theme.$light-ink-opacities,
mdc-ripple-theme.$dark-ink-opacities
Expand All @@ -29,13 +28,13 @@ $prefix: (mat, fab);
foreground-color: inspection.get-theme-color($theme, foreground, base),

// Color of the element that shows the hover, focus and pressed states.
state-layer-color: $on-surface,
state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the element that shows the hover, focus and pressed states while disabled.
disabled-state-layer-color: $on-surface,
disabled-state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the ripple element.
ripple-color: rgba($on-surface, 0.1),
ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1),

// Opacity of the ripple when the button is hovered.
hover-state-layer-opacity: map.get($ripple-opacities, hover),
Expand All @@ -48,24 +47,42 @@ $prefix: (mat, fab);

// MDC doesn't have tokens for disabled FABs so we need to implemented them ourselves.
// Background color of the container when the FAB is disabled.
disabled-state-container-color: rgba($on-surface, 0.12),
disabled-state-container-color: inspection.get-theme-color($theme, background, disabled-button,
0.12),

// Color of the icons and projected text when the FAB is disabled.
disabled-state-foreground-color: rgba($on-surface, if($is-dark, 0.5, 0.38)),
disabled-state-foreground-color: inspection.get-theme-color($theme, foreground, disabled-button,
if($is-dark, 0.5, 0.38)),
);
}

// Generates the mapping for the properties that change based on the FAB palette color.
@function private-get-color-palette-color-tokens($theme, $palette-name) {
$is-dark: inspection.get-theme-type($theme) == dark;
$container-color: inspection.get-theme-color($theme, $palette-name);
$contrast-tone: mdc-helpers.variable-safe-contrast-tone($container-color, $is-dark);
$color: if($contrast-tone == 'dark', #000, #fff);
// Ideally we would derive all values directly from the theme, but it causes a lot of regressions
// internally. For now we fall back to the old hardcoded behavior only for internal apps.
$foreground-color: null;
$state-layer-color: null;
$ripple-color: null;

@if (token-utils.$private-is-internal-build) {
$is-dark: inspection.get-theme-type($theme) == dark;
$container-color: inspection.get-theme-color($theme, $palette-name);
$contrast-tone: mdc-helpers.variable-safe-contrast-tone($container-color, $is-dark);
$color: if($contrast-tone == 'dark', #000, #fff);
$foreground-color: $color;
$state-layer-color: $color;
$ripple-color: rgba($color, 0.1);
}
@else {
$foreground-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 1);
$state-layer-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 1);
$ripple-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 0.1);
}

@return (
foreground-color: $color,
state-layer-color: $color,
ripple-color: rgba($color, 0.1),
foreground-color: $foreground-color,
state-layer-color: $state-layer-color,
ripple-color: $ripple-color,
);
}

Expand Down
32 changes: 22 additions & 10 deletions src/material/core/tokens/m2/mat/_filled-button.scss
Expand Up @@ -28,21 +28,20 @@ $prefix: (mat, filled-button);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$ripple-opacities: if($is-dark,
mdc-ripple-theme.$light-ink-opacities,
mdc-ripple-theme.$dark-ink-opacities
);

@return (
// Color of the element that shows the hover, focus and pressed states.
state-layer-color: $on-surface,
state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the element that shows the hover, focus and pressed states while disabled.
disabled-state-layer-color: $on-surface,
disabled-state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the ripple element.
ripple-color: rgba($on-surface, 0.1),
ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1),

// Opacity of the ripple when the button is hovered.
hover-state-layer-opacity: map.get($ripple-opacities, hover),
Expand All @@ -57,14 +56,27 @@ $prefix: (mat, filled-button);

// Generates the mapping for the properties that change based on the button palette color.
@function private-get-color-palette-color-tokens($theme, $palette-name) {
$is-dark: inspection.get-theme-type($theme) == dark;
$container-color: inspection.get-theme-color($theme, $palette-name);
$contrast-tone: mdc-helpers.variable-safe-contrast-tone($container-color, $is-dark);
$color: if($contrast-tone == 'dark', #000, #fff);
$state-layer-color: null;
$ripple-color: null;

// Ideally we would derive all values directly from the theme, but it causes a lot of regressions
// internally. For now we fall back to the old hardcoded behavior only for internal apps.
@if (token-utils.$private-is-internal-build) {
$is-dark: inspection.get-theme-type($theme) == dark;
$container-color: inspection.get-theme-color($theme, $palette-name);
$contrast-tone: mdc-helpers.variable-safe-contrast-tone($container-color, $is-dark);
$color: if($contrast-tone == 'dark', #000, #fff);
$state-layer-color: $color;
$ripple-color: rgba($color, 0.1);
}
@else {
$state-layer-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 1);
$ripple-color: inspection.get-theme-color($theme, $palette-name, default-contrast, 0.1);
}

@return (
state-layer-color: $color,
ripple-color: rgba($color, 0.1),
state-layer-color: $state-layer-color,
ripple-color: $ripple-color,
);
}

Expand Down
10 changes: 5 additions & 5 deletions src/material/core/tokens/m2/mat/_form-field.scss
Expand Up @@ -20,13 +20,13 @@ $prefix: (mat, form-field);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$color-tokens: private-get-color-palette-color-tokens($theme, primary);

@return map.merge($color-tokens, (
// MDC has a token for the enabled placeholder, but not for the disabled one.
disabled-input-text-placeholder-color: rgba($on-surface, 0.38),
state-layer-color: rgba($on-surface, 0.87),
disabled-input-text-placeholder-color:
inspection.get-theme-color($theme, foreground, icon, 0.38),
state-layer-color: inspection.get-theme-color($theme, foreground, base, 0.87),
error-text-color: inspection.get-theme-color($theme, warn),

// On dark themes we set the native `select` color to some shade of white,
Expand Down Expand Up @@ -56,8 +56,8 @@ $prefix: (mat, form-field);

// These values are taken from the MDC select implementation:
// https://github.com/material-components/material-components-web/blob/master/packages/mdc-select/_select-theme.scss
enabled-select-arrow-color: rgba($on-surface, 0.54),
disabled-select-arrow-color: rgba($on-surface, 0.38),
enabled-select-arrow-color: inspection.get-theme-color($theme, foreground, icon, 0.54),
disabled-select-arrow-color: inspection.get-theme-color($theme, foreground, icon, 0.38),

hover-state-layer-opacity: if($is-dark, 0.08, 0.04),
focus-state-layer-opacity: if($is-dark, 0.24, 0.08),
Expand Down
7 changes: 3 additions & 4 deletions src/material/core/tokens/m2/mat/_icon-button.scss
Expand Up @@ -18,21 +18,20 @@ $prefix: (mat, icon-button);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$ripple-opacities: if($is-dark,
mdc-ripple-theme.$light-ink-opacities,
mdc-ripple-theme.$dark-ink-opacities
);

@return (
// Color of the element that shows the hover, focus and pressed states.
state-layer-color: $on-surface,
state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the element that shows the hover, focus and pressed states while disabled.
disabled-state-layer-color: $on-surface,
disabled-state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the ripple element.
ripple-color: rgba($on-surface, 0.1),
ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1),

// Opacity of the ripple when the button is hovered.
hover-state-layer-opacity: map.get($ripple-opacities, hover),
Expand Down
4 changes: 2 additions & 2 deletions src/material/core/tokens/m2/mat/_menu.scss
Expand Up @@ -24,8 +24,8 @@ $prefix: (mat, menu);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$active-state-layer-color: rgba($on-surface, if($is-dark, 0.08, 0.04));
$active-state-layer-color: inspection.get-theme-color($theme, foreground, base,
if($is-dark, 0.08, 0.04));
$text-color: inspection.get-theme-color($theme, foreground, text);

@return (
Expand Down
4 changes: 2 additions & 2 deletions src/material/core/tokens/m2/mat/_option.scss
Expand Up @@ -15,8 +15,8 @@ $prefix: (mat, option);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme, $palette-name: primary) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$active-state-layer-color: rgba($on-surface, if($is-dark, 0.08, 0.04));
$active-state-layer-color: inspection.get-theme-color($theme, foreground, base,
if($is-dark, 0.08, 0.04));

@return (
selected-state-label-text-color: inspection.get-theme-color($theme, $palette-name),
Expand Down
7 changes: 3 additions & 4 deletions src/material/core/tokens/m2/mat/_outlined-button.scss
Expand Up @@ -28,21 +28,20 @@ $prefix: (mat, outlined-button);
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == dark;
$on-surface: if($is-dark, #fff, #000);
$ripple-opacities: if($is-dark,
mdc-ripple-theme.$light-ink-opacities,
mdc-ripple-theme.$dark-ink-opacities
);

@return (
// Color of the element that shows the hover, focus and pressed states.
state-layer-color: $on-surface,
state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the element that shows the hover, focus and pressed states while disabled.
disabled-state-layer-color: $on-surface,
disabled-state-layer-color: inspection.get-theme-color($theme, foreground, base),

// Color of the ripple element.
ripple-color: rgba($on-surface, 0.1),
ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1),

// Opacity of the ripple when the button is hovered.
hover-state-layer-opacity: map.get($ripple-opacities, hover),
Expand Down

0 comments on commit cfdfa9a

Please sign in to comment.