Skip to content

Commit db5b8dc

Browse files
authoredJul 31, 2024··
feat(multiple): fallback to system level variables (#29480)
* feat(multiple): fallback to system level variables * feat(multiple): create sass util for checking css var name * feat(multiple): add docs for generate-tokens density param * feat(multiple): make some properties/functions private * feat(multiple): lint fixes * feat(multiple): add todo for weird param * feat(multiple): add check for using color mix
1 parent 5403b2b commit db5b8dc

File tree

7 files changed

+209
-57
lines changed

7 files changed

+209
-57
lines changed
 

‎src/material/_index.scss

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
@forward './core/typography/typography' show typography-hierarchy;
1919
@forward './core/typography/typography-utils' show font-shorthand;
2020
@forward './core/tokens/m2' show m2-tokens-from-theme;
21-
@forward './core/tokens/m3-tokens' show system-level-colors, system-level-typography;
21+
@forward './core/tokens/m3-tokens' show system-level-colors,
22+
system-level-typography, system-level-elevation, system-level-shape,
23+
system-level-motion, system-level-state;
2224

2325
// Private/Internal
2426
@forward './core/density/private/all-density' show all-component-densities;

‎src/material/core/style/_elevation.scss

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
@use 'sass:meta';
44
@use 'sass:string';
55
@use './variables';
6+
@use './sass-utils';
67

78
$_umbra-opacity: 0.2;
89
$_penumbra-opacity: 0.14;
@@ -143,6 +144,10 @@ $prefix: 'mat-elevation-z';
143144
@return null;
144145
}
145146

147+
@if (sass-utils.is-css-var-name($zValue)) {
148+
@return $zValue;
149+
}
150+
146151
@if meta.type-of($zValue) != number or not math.is-unitless($zValue) {
147152
@error '$zValue must be a unitless number, but received `#{$zValue}`';
148153
}

‎src/material/core/style/_sass-utils.scss

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@use 'sass:color';
2+
@use 'sass:string';
23
@use 'sass:list';
34
@use 'sass:map';
45
@use 'sass:meta';
@@ -64,12 +65,23 @@ $use-system-typography-variables: false;
6465
/// A version of the Sass `color.change` function that is safe ot use with CSS variables.
6566
@function safe-color-change($color, $args...) {
6667
$args: meta.keywords($args);
68+
$use-color-mix: $use-system-color-variables or
69+
(is-css-var-name($color) and string.index($color, '--mat') == 1);
6770
@if (meta.type-of($color) == 'color') {
6871
@return color.change($color, $args...);
6972
}
70-
@else if ($color != null and map.get($args, alpha) != null and $use-system-color-variables) {
73+
@else if ($color != null and
74+
map.get($args, alpha) != null and $use-color-mix) {
7175
$opacity: map.get($args, alpha);
72-
@return #{color-mix(in srgb, #{$color} #{($opacity * 100) + '%'}, transparent)};
76+
@if meta.type-of($opacity) == number {
77+
$opacity: ($opacity * 100) + '%';
78+
}
79+
80+
@if (is-css-var-name($color)) {
81+
$color: var($color);
82+
}
83+
84+
@return #{color-mix(in srgb, #{$color} #{$opacity}, transparent)};
7385
}
7486
@return $color;
7587
}
@@ -91,3 +103,9 @@ $use-system-typography-variables: false;
91103
}
92104
@return $kwargs;
93105
}
106+
107+
// Returns whether the $value is a CSS variable name based on whether it's a string prefixed
108+
// by "--".
109+
@function is-css-var-name($value) {
110+
@return meta.type-of($value) == string and string.index($value, '--') == 1;
111+
}

‎src/material/core/tokens/_m3-tokens.scss

+106-12
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@
44
@use './m3';
55
@use './m3/definitions' as m3-token-definitions;
66
@use '../tokens/m2' as m2-tokens;
7+
@use '../style/elevation';
78
@use './density';
89
@use './format-tokens';
910

11+
// Default system level prefix to use when directly calling the `system-level-*` mixins
12+
$_system-level-prefix: sys;
13+
14+
// Prefix used for component token fallback variables, e.g.
15+
// `color: var(--mdc-text-button-label-text-color, var(--mat-app-primary));`
16+
$_system-fallback-prefix: mat-app;
17+
1018
/// Generates tokens for the given palette with the given prefix.
1119
/// @param {Map} $palette The palette to generate tokens for
1220
/// @param {String} $prefix The key prefix used to name the tokens
@@ -86,8 +94,9 @@ $_cached-token-slots: null;
8694
/// Generates a set of namespaced tokens for all components.
8795
/// @param {Map} $systems The MDC system tokens
8896
/// @param {Boolean} $include-non-systemized Whether to include non-systemized tokens
97+
/// @param {Boolean} $include-density Whether to include density tokens
8998
/// @return {Map} A map of namespaced tokens
90-
@function _generate-tokens($systems, $include-non-systemized: false) {
99+
@function _generate-tokens($systems, $include-non-systemized: false, $include-density: false) {
91100
$systems: map.merge((
92101
md-sys-color: (),
93102
md-sys-elevation: (),
@@ -116,16 +125,63 @@ $_cached-token-slots: null;
116125
// Strip out tokens that are systemized by our made up density system.
117126
@each $namespace, $tokens in $result {
118127
@each $token, $value in $tokens {
119-
@if density.is-systemized($namespace, $token) {
128+
@if density.is-systemized($namespace, $token) and not $include-density {
120129
$tokens: map.remove($tokens, $token);
121130
}
122131
}
123132
$result: map.set($result, $namespace, $tokens);
124133
}
134+
125135
@return $result;
126136
}
127137

128-
@mixin system-level-colors($theme, $overrides: ()) {
138+
// Return a new map where the values are the same as the provided map's
139+
// keys, prefixed with "--mat-app-". For example:
140+
// (key1: '', key2: '') --> (key1: --mat-app-key1, key2: --mat-app-key2)
141+
@function _create-system-app-vars-map($map) {
142+
$new-map: ();
143+
@each $key, $value in $map {
144+
$new-map: map.set($new-map, $key, --#{$_system-fallback-prefix}-#{$key});
145+
}
146+
@return $new-map;
147+
}
148+
149+
// Create a components tokens map where values are based on
150+
// system fallback variables referencing Material's system keys.
151+
// Includes density token fallbacks where density is 0.
152+
@function create-system-fallbacks() {
153+
$app-vars: (
154+
'md-sys-color':
155+
_create-system-app-vars-map(m3-token-definitions.md-sys-color-values-light()),
156+
'md-sys-typescale':
157+
_create-system-app-vars-map(m3-token-definitions.md-sys-typescale-values()),
158+
'md-sys-elevation':
159+
_create-system-app-vars-map(m3-token-definitions.md-sys-elevation-values()),
160+
'md-sys-state':
161+
_create-system-app-vars-map(m3-token-definitions.md-sys-state-values()),
162+
'md-sys-shape':
163+
_create-system-app-vars-map(m3-token-definitions.md-sys-shape-values()),
164+
);
165+
166+
@return sass-utils.deep-merge-all(
167+
_generate-tokens($app-vars, true, true),
168+
generate-density-tokens(0)
169+
);
170+
}
171+
172+
// Emits CSS variables for Material's system level values. Uses the
173+
// namespace prefix in $_system-fallback-prefix.
174+
// e.g. --mat-app-surface: #E5E5E5
175+
@mixin theme($theme, $overrides: ()) {
176+
@include system-level-colors($theme, $overrides, $_system-fallback-prefix);
177+
@include system-level-typography($theme, $overrides, $_system-fallback-prefix);
178+
@include system-level-elevation($theme, $overrides, $_system-fallback-prefix);
179+
@include system-level-shape($theme, $overrides, $_system-fallback-prefix);
180+
@include system-level-motion($theme, $overrides, $_system-fallback-prefix);
181+
@include system-level-state($theme, $overrides, $_system-fallback-prefix);
182+
}
183+
184+
@mixin system-level-colors($theme, $overrides: (), $prefix: null) {
129185
$palettes: map.get($theme, _mat-theming-internals-do-not-access, palettes);
130186
$base-palettes: (
131187
neutral: map.get($palettes, neutral),
@@ -135,12 +191,15 @@ $_cached-token-slots: null;
135191
);
136192

137193
$type: map.get($theme, _mat-theming-internals-do-not-access, theme-type);
138-
$system-variables-prefix: map.get($theme, _mat-theming-internals-do-not-access,
139-
color-system-variables-prefix) or sys;
140194
$primary: map.merge(map.get($palettes, primary), $base-palettes);
141195
$tertiary: map.merge(map.get($palettes, tertiary), $base-palettes);
142196
$error: map.get($palettes, error);
143197

198+
@if (not $prefix) {
199+
$prefix: map.get($theme, _mat-theming-internals-do-not-access,
200+
color-system-variables-prefix) or $_system-level-prefix;
201+
}
202+
144203
$ref: (
145204
md-ref-palette: _generate-ref-palette-tokens($primary, $tertiary, $error)
146205
);
@@ -150,27 +209,28 @@ $_cached-token-slots: null;
150209
m3-token-definitions.md-sys-color-values-light($ref));
151210

152211
@each $name, $value in $sys-colors {
153-
--#{$system-variables-prefix}-#{$name}: #{map.get($overrides, $name) or $value};
212+
--#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
154213
}
155214
}
156215

157-
@mixin system-level-typography($theme, $overrides: ()) {
216+
@mixin system-level-typography($theme, $overrides: (), $prefix: null) {
158217
$font-definition: map.get($theme, _mat-theming-internals-do-not-access, font-definition);
159218
$brand: map.get($font-definition, brand);
160219
$plain: map.get($font-definition, plain);
161220
$bold: map.get($font-definition, bold);
162221
$medium: map.get($font-definition, medium);
163222
$regular: map.get($font-definition, regular);
164-
$system-variables-prefix: map.get($theme, _mat-theming-internals-do-not-access,
165-
typography-system-variables-prefix) or sys;
166223
$ref: (
167224
md-ref-typeface: _generate-ref-typeface-tokens($brand, $plain, $bold, $medium, $regular)
168225
);
169226

170-
$sys-typescale: m3-token-definitions.md-sys-typescale-values($ref);
227+
@if (not $prefix) {
228+
$prefix: map.get($theme, _mat-theming-internals-do-not-access,
229+
typography-system-variables-prefix) or $_system-level-prefix;
230+
}
171231

172-
@each $name, $value in $sys-typescale {
173-
--#{$system-variables-prefix}-#{$name}: #{map.get($overrides, $name) or $value};
232+
@each $name, $value in m3-token-definitions.md-sys-typescale-values($ref) {
233+
--#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
174234
}
175235
}
176236

@@ -182,6 +242,40 @@ $_cached-token-slots: null;
182242
@return $result;
183243
}
184244

245+
@mixin system-level-elevation($theme, $overrides: (), $prefix: $_system-level-prefix) {
246+
$shadow-color: map.get(
247+
$theme, _mat-theming-internals-do-not-access, color-tokens, (mdc, theme), shadow);
248+
249+
@for $level from 0 through 24 {
250+
$value: elevation.get-box-shadow($level, $shadow-color);
251+
--#{$prefix}-elevation-shadow-level-#{$level}: #{$value};
252+
}
253+
254+
@each $name, $value in m3-token-definitions.md-sys-elevation-values() {
255+
$level: map.get($overrides, $name) or $value;
256+
$value: elevation.get-box-shadow($level, $shadow-color);
257+
--#{$prefix}-#{$name}: #{$value};
258+
}
259+
}
260+
261+
@mixin system-level-shape($theme, $overrides: (), $prefix: $_system-level-prefix) {
262+
@each $name, $value in m3-token-definitions.md-sys-shape-values() {
263+
--#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
264+
}
265+
}
266+
267+
@mixin system-level-state($theme, $overrides: (), $prefix: $_system-level-prefix) {
268+
@each $name, $value in m3-token-definitions.md-sys-state-values() {
269+
--#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
270+
}
271+
}
272+
273+
@mixin system-level-motion($theme, $overrides: (), $prefix: $_system-level-prefix) {
274+
@each $name, $value in m3-token-definitions.md-sys-motion-values() {
275+
--#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
276+
}
277+
}
278+
185279
@function _get-sys-color($type, $ref, $prefix) {
186280
$mdc-sys-color: if($type == dark,
187281
m3-token-definitions.md-sys-color-values-dark($ref),

‎src/material/core/tokens/_token-definition.scss

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@use '../m2/palette' as m2-palette;
77
@use '../m2/theming' as m2-theming;
88
@use '../m2/typography' as m2-typography;
9+
@use '../style/sass-utils';
910
@use './m3/definitions' as m3-token-definitions;
1011

1112
// Indicates whether we're building internally. Used for backwards compatibility.
@@ -161,6 +162,11 @@ $_system-fallbacks: null;
161162
$color-key: map.get($pair, color);
162163
$opacity-key: map.get($pair, opacity);
163164
$color: map.get($tokens, $color-key);
165+
166+
@if (sass-utils.is-css-var-name($color)) {
167+
$color: var(#{$color});
168+
}
169+
164170
$opacity: map.get($opacity-lookup, $opacity-key);
165171

166172
@if(meta.type-of($color) == 'color') {

‎src/material/core/tokens/_token-utils.scss

+65-41
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
@use 'sass:string';
44
@use '../style/elevation';
55
@use '../style/sass-utils';
6+
@use './m3-tokens';
67

78
$_tokens: null;
89
$_component-prefix: null;
10+
$_system-fallbacks: m3-tokens.create-system-fallbacks();
911

1012
// Sets the token prefix and map to use when creating token slots.
1113
@mixin use-tokens($prefix, $tokens) {
@@ -19,8 +21,8 @@ $_component-prefix: null;
1921
}
2022

2123
// Combines a prefix and a string to generate a CSS variable name for a token.
22-
@function _get-css-variable($prefix, $name) {
23-
@if $prefix == null or $name == null {
24+
@function _create-var-name($prefix, $token) {
25+
@if $prefix == null or $token == null {
2426
@error 'Must specify both prefix and name when generating token';
2527
}
2628

@@ -31,77 +33,99 @@ $_component-prefix: null;
3133
$string-prefix: if($string-prefix == '', $part, '#{$string-prefix}-#{$part}');
3234
}
3335

34-
@return string.unquote('--#{$string-prefix}-#{$name}');
36+
@return string.unquote('--#{$string-prefix}-#{$token}');
3537
}
3638

37-
// Emits a slot for the given token, provided that it has a non-null value in the token map passed
38-
// to `use-tokens`.
39-
@mixin create-token-slot($property, $token, $emit-fallback: false) {
39+
// Creates a CSS variable, including the fallback if provided.
40+
@function _create-var($name, $fallback: null) {
41+
@if ($fallback) {
42+
@return var($name, $fallback);
43+
} @else {
44+
@return var($name);
45+
}
46+
}
47+
48+
// Gets the value of the token given the current global context state.
49+
@function _get-token-value($token, $fallback) {
50+
$var-name: _create-var-name($_component-prefix, $token);
51+
$fallback: _get-token-fallback($token, $fallback);
52+
@return _create-var($var-name, $fallback);
53+
}
54+
55+
// Assertion mixin that throws an error if the global state has not been set up by wrapping
56+
// calls with `use-tokens`.
57+
@function _assert-use-tokens($token) {
4058
@if $_component-prefix == null or $_tokens == null {
41-
@error '`create-token-slot` must be used within `use-tokens`';
59+
@error 'Function was not called within a wrapping call of `use-tokens`';
4260
}
4361
@if not map.has-key($_tokens, $token) {
4462
@error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}';
4563
}
46-
@if map.get($_tokens, $token) != null {
47-
$fallback: null;
4864

49-
@if ($emit-fallback == true) {
50-
$fallback: map.get($_tokens, $token);
51-
}
52-
@else if ($emit-fallback) {
53-
$fallback: $emit-fallback;
54-
}
65+
@return true;
66+
}
5567

56-
$var-name: _get-css-variable($_component-prefix, $token);
57-
$var-reference: if($fallback == null, var(#{$var-name}), var(#{$var-name}, #{$fallback}));
58-
#{$property}: #{$var-reference};
68+
// Emits a slot for the given token, provided that it has a non-null value in the token map passed
69+
// to `use-tokens`.
70+
// Accepts an optional fallback parameter to include in the CSS variable.
71+
// If $fallback is `true`, then use the tokens map to get the fallback.
72+
// TODO: Remove the use case where we accept "true" and handle any failing client screenshots
73+
@mixin create-token-slot($property, $token, $fallback: null) {
74+
$_assert: _assert-use-tokens($token);
75+
@if map.get($_tokens, $token) != null {
76+
#{$property}: #{_get-token-value($token, $fallback)};
5977
}
6078
}
6179

6280
// Returns the name of a token including the current prefix. Intended to be used in calculations
6381
// involving tokens. `create-token-slot` should be used when outputting tokens.
6482
@function get-token-variable-name($token) {
65-
@if $_component-prefix == null or $_tokens == null {
66-
@error '`get-token-variable` must be used within `use-tokens`';
67-
}
68-
@if not map.has-key($_tokens, $token) {
69-
@error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}';
70-
}
71-
72-
@return _get-css-variable($_component-prefix, $token);
83+
$_assert: _assert-use-tokens($token);
84+
@return _create-var-name($_component-prefix, $token);
7385
}
7486

7587
// Returns a `var()` reference to a specific token. Intended for declarations
7688
// where the token has to be referenced as a part of a larger expression.
77-
@function get-token-variable($token, $use-tokens-fallback: false, $fallback: null) {
78-
@if $_component-prefix == null or $_tokens == null {
79-
@error '`get-token-variable-reference` must be used within `use-tokens`';
80-
}
81-
@if not map.has-key($_tokens, $token) {
82-
@error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}';
83-
}
84-
85-
$var: get-token-variable-name($token);
89+
// Accepts an optional fallback parameter to include in the CSS variable.
90+
// If $fallback is `true`, then use the tokens map to get the fallback.
91+
// TODO: Remove the use case where we accept "true" and handle any failing client screenshots
92+
@function get-token-variable($token, $fallback: null) {
93+
$_assert: _assert-use-tokens($token);
94+
@return _get-token-value($token, $fallback);
95+
}
8696

87-
@if ($use-tokens-fallback) {
97+
// Gets the token's fallback value. Prefers adding a system-level fallback if one exists, otherwise
98+
// use the provided fallback.
99+
@function _get-token-fallback($token, $fallback: null) {
100+
// If the $fallback is `true`, this is the component's signal to use the current token map value
101+
@if ($fallback == true) {
88102
$fallback: map.get($_tokens, $token);
89103
}
90104

91-
@if ($fallback != null) {
92-
@return var($var, $fallback);
105+
// Check whether there's a system-level fallback. If not, return the optional
106+
// provided fallback (otherwise null).
107+
$sys-fallback: map.get($_system-fallbacks, $_component-prefix, $token);
108+
@if (not $sys-fallback) {
109+
@return $fallback;
93110
}
94-
@else {
95-
@return var($var);
111+
112+
@if (sass-utils.is-css-var-name($sys-fallback)) {
113+
@return _create-var($sys-fallback, $fallback);
96114
}
115+
116+
// TODO(mat-app-theme): Return the system-level fallback.
117+
// Changing this will affect clients that do not properly call theme mixins since the tokens
118+
// will be undefined and now default to M3 system values, causing a number of screenshot failures.
119+
// @return $sys-fallback;
120+
@return $fallback;
97121
}
98122

99123
// Outputs a map of tokens under a specific prefix.
100124
@mixin create-token-values($prefix, $tokens) {
101125
@if $tokens != null {
102126
@each $key, $value in $tokens {
103127
@if $value != null {
104-
#{_get-css-variable($prefix, $key)}: #{$value};
128+
#{_create-var-name($prefix, $key)}: #{$value};
105129
}
106130
}
107131
}

‎src/material/core/tokens/m3/mat/_app.scss

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@use 'sass:map';
2+
@use 'sass:meta';
23
@use '../../../style/elevation';
34
@use '../../token-definition';
45

@@ -17,7 +18,9 @@ $prefix: (mat, app);
1718
text-color: map.get($systems, md-sys-color, on-background),
1819
);
1920

20-
@if ($shadow-color) {
21+
// If the shadow-color is an actual color, convert it to a box-shadow value. Otherwise
22+
// use the token value as itself (e,g. as a CSS var name).
23+
@if (meta.type-of($shadow-color) == color) {
2124
@for $zValue from 0 through 24 {
2225
$shadow: elevation.get-box-shadow($zValue, $shadow-color);
2326
$tokens: map.set($tokens, 'elevation-shadow-level-#{$zValue}', $shadow);

0 commit comments

Comments
 (0)
Please sign in to comment.