Skip to content

Commit b77094d

Browse files
authored
feat(material-experimental/themeing): add M3 token values for checkbox and card (#27409)
* feat(material-experimental/themeing): add M3 token values for checkbox and card * Add a function to get the default M3 tokens * Add filtering for only the supported tokens * Use the M3 tokens (and disable custom override styles so we can see what they look like) * Rename checkbox tokens to be compatible with MDC's implementation * Resolve card elevation since MDC doesn't do it correctly * Fix lint issues
1 parent e3e0c6b commit b77094d

File tree

4 files changed

+168
-76
lines changed

4 files changed

+168
-76
lines changed

src/dev-app/theme-token-api.scss

Lines changed: 5 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
@use 'sass:map';
21
@use '@angular/material' as mat;
32
@use '@angular/material-experimental' as matx;
43

@@ -37,81 +36,13 @@ $theme: mat.define-light-theme((
3736
// on the page will inherit these tokens.
3837
html {
3938
@include matx.theme(
40-
matx.token-defaults(mat.m2-tokens-from-theme($theme)),
39+
matx.token-defaults(matx.get-m3-tokens()),
4140
matx.card(),
4241
matx.checkbox(),
4342
);
4443
}
4544

46-
47-
// Apply tokens needed for dark theme to the element with `.demo-unicorn-dark-theme`.
48-
// This ensures that checkboxes within the element inherit the new tokens for dark theme,
49-
// rather than the ones for light theme tokens set on `body`. Note that we're not setting *all* of
50-
// the tokens, since many (density, typography, etc) are the same between light and dark theme.
51-
.demo-unicorn-dark-theme {
52-
@include matx.theme(
53-
// TODO(mmalerba): In the future this should be configured through `matx.system-colors()`
54-
matx.checkbox((theme-type: dark)),
55-
matx.card((theme-type: dark)),
56-
);
57-
}
58-
59-
// Apply tokens related to the color palette to any element with `.mat-primary`, `.mat-accent`, or
60-
// `.mat-warn` This ensures that checkboxes within the element inherit the new tokens for the
61-
// appropriate palette, rather than the any color that may have been set on an element further up
62-
// the hierarchy. Again, rather than applying *all* the tokens, we apply only the ones effected by
63-
// the palette color. With this setup, the palette class need not go on the component itself
64-
// (e.g. <mat-checkbox class="mat-primary">), it can go on some ancestor element and the tokens will
65-
// flow down. If multiple elements specify different classes, the closest one to the component will
66-
// take precedence.
67-
// (e.g. <div class="mat-warn><mat-checkbox class="mat-primary">I'm primary</mat-checkbox></div>)
68-
.mat-primary {
69-
@include matx.theme(
70-
matx.checkbox((
71-
color-palette: map.get($theme, color, primary)
72-
)),
73-
);
74-
}
75-
.mat-accent {
76-
@include matx.theme(
77-
matx.checkbox((
78-
color-palette: map.get($theme, color, accent)
79-
)),
80-
);
81-
}
82-
.mat-warn {
83-
@include matx.theme(
84-
matx.checkbox((
85-
color-palette: map.get($theme, color, warn)
86-
)),
87-
);
88-
}
89-
90-
// Apply tokens for a completely custom checkbox that appears as an unfilled red box when unchecked,
91-
// and a filled green box when checked.
92-
.demo-traffic-light-checkbox {
93-
@include matx.theme(
94-
matx.checkbox((
95-
checkmark-color: transparent,
96-
selected-box-color: green,
97-
selected-focus-box-color: green,
98-
selected-hover-box-color: green,
99-
selected-pressed-box-color: green,
100-
selected-focus-ring-color: green,
101-
selected-hover-ring-color: green,
102-
selected-pressed-ring-color: green,
103-
unselected-box-color: red,
104-
unselected-focus-box-color: red,
105-
unselected-hover-box-color: red,
106-
unselected-pressed-box-color: red,
107-
unselected-focus-ring-color: red,
108-
unselected-hover-ring-color: red,
109-
unselected-pressed-ring-color: red,
110-
))
111-
);
112-
}
113-
114-
.demo-what-am-i-doing {
115-
// Will not produce any output, should result in a warning.
116-
@include matx.theme(matx.checkbox());
117-
}
45+
// TODO(mmalerba): Figure out a consistent solution for handling dark themes & color palette
46+
// variants across M2 & M3 (likely by implementing `matx.system-colors`). As a reference, see the
47+
// prior version of this file that showed a possible way to accomplish this in M2:
48+
// https://github.com/angular/components/blob/5f5c5160dc20331619fc6729aa2ad78ac84af1c3/src/dev-app/theme-token-api.scss

src/material-experimental/_index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
@forward './theming/theming' show theme, token-defaults;
99
@forward './theming/checkbox' show checkbox;
1010
@forward './theming/card' show card;
11+
@forward './theming/m3-tokens' show get-m3-tokens;
1112

1213
// Additional public APIs for individual components
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
@use 'sass:map';
2+
@use '@angular/material' as mat;
3+
@use '@material/tokens/v0_161' as mdc-tokens;
4+
5+
/// Picks a submap containing only the given keys out the given map.
6+
/// @param {Map} $map The map to pick from.
7+
/// @param {List} $keys The map keys to pick.
8+
/// @return {Map} A submap containing only the given keys.
9+
@function _pick($map, $keys) {
10+
$result: ();
11+
@each $key in $keys {
12+
@if map.has-key($map, $key) {
13+
$result: map.set($result, $key, map.get($map, $key));
14+
}
15+
}
16+
@return $result;
17+
}
18+
19+
/// Filters keys with a null value out of the map.
20+
/// @param {Map} $map The map to filter.
21+
/// @return {Map} The given map with all of the null keys filtered out.
22+
@function _filter-nulls($map) {
23+
$result: ();
24+
@each $key, $val in $map {
25+
@if $val != null {
26+
$result: map.set($result, $key, $val);
27+
}
28+
}
29+
@return $result;
30+
}
31+
32+
/// Gets the MDC tokens for the given prefix, M3 token values, and supported token slots.
33+
/// @param {List} $prefix The token prefix for the given tokens.
34+
/// @param {Map} $m3-values A map of M3 token values for the given prefix.
35+
/// @param {Map} $slots A map of token slots, with null value indicating the token is not supported.
36+
/// @return {Map} A map of fully qualified token names to values, for only the supported tokens.
37+
@function _get-mdc-tokens($prefix, $m3-values, $slots) {
38+
$used-token-names: map.keys(_filter-nulls(map.get($slots, $prefix)));
39+
$used-m3-tokens: _pick($m3-values, $used-token-names);
40+
@return (
41+
$prefix: $used-m3-tokens,
42+
);
43+
}
44+
45+
/// Sets all of the standard typography tokens for the given token base name to the given typography
46+
/// level.
47+
/// @param {Map} $typography-tokens The MDC system-typescale tokens.
48+
/// @param {String} $base-name The token base name to get the typography tokens for
49+
/// @param {String} $typography-level The typography level to base the token values on.
50+
/// @return {Map} A map containing the typography tokens for the given base token name.
51+
@function _get-typography-tokens($typography-tokens, $base-name, $typography-level) {
52+
$result: ();
53+
@each $prop in (font, line-height, size, tracking, weight) {
54+
$result: map.set(
55+
$result,
56+
#{$base-name}-#{$prop},
57+
map.get($typography-tokens, #{$typography-level}-#{$prop}
58+
));
59+
}
60+
@return $result;
61+
}
62+
63+
/// Renames the keys in a map
64+
/// @param {Map} $map The map whose keys should be renamed
65+
/// @param {Map} $rename-keys A map of original key to renamed key to apply to $map
66+
/// @return {Map} The result of applying the given key renames to the given map.
67+
@function _rename-map-keys($map, $rename-keys) {
68+
$result: $map;
69+
@each $old-key-name, $new-key-name in $rename-keys {
70+
@if map.has-key($map, $old-key-name) {
71+
$result: map.set($result, $new-key-name, map.get($map, $old-key-name));
72+
}
73+
}
74+
@return $result;
75+
}
76+
77+
/// Renames the official checkbox tokens to match the names actually used in MDC's code (which are
78+
/// different). This is a temporary workaround until MDC updates to use the correct names for the
79+
/// tokens.
80+
/// @param {Map} $tokens The map of checkbox tokens with the official tokens names
81+
/// @return {Map} The given tokens, renamed to be compatible with MDC's token implementation.
82+
@function _fix-checkbox-token-names($tokens) {
83+
$rename-keys: (
84+
'selected-icon-color': 'selected-checkmark-color',
85+
'selected-disabled-icon-color': 'disabled-selected-checkmark-color',
86+
'selected-container-color': 'selected-icon-color',
87+
'selected-hover-container-color': 'selected-hover-icon-color',
88+
'selected-disabled-container-color': 'disabled-selected-icon-color',
89+
'selected-disabled-container-opacity': 'disabled-selected-icon-opacity',
90+
'selected-focus-container-color': 'selected-focus-icon-color',
91+
'selected-pressed-container-color': 'selected-pressed-icon-color',
92+
'unselected-disabled-outline-color': 'disabled-unselected-icon-color',
93+
'unselected-disabled-container-opacity': 'disabled-unselected-icon-opacity',
94+
'unselected-focus-outline-color': 'unselected-focus-icon-color',
95+
'unselected-hover-outline-color': 'unselected-hover-icon-color',
96+
'unselected-outline-color': 'unselected-icon-color',
97+
'unselected-pressed-outline-color': 'unselected-pressed-icon-color'
98+
);
99+
@return _rename-map-keys($tokens, $rename-keys);
100+
}
101+
102+
// TODO(mmalerba): We need a way to accept custom M3 token values generated from MDCs theme builder
103+
// or other means. We can't just use them directly without processing them first because we need to
104+
// add our made up tokens,
105+
/// Gets the default token values for M3.
106+
/// @return The default set of M3 tokens.
107+
@function get-m3-tokens() {
108+
$typography: mdc-tokens.md-sys-typescale-values();
109+
$colors: mdc-tokens.md-sys-color-values-light();
110+
111+
// TODO(mmalerba): Refactor this to not depend on the legacy theme. This is a hack for now because
112+
// there is no good way to get the token slots in material-experimental without exposing them all
113+
// from material.
114+
$fake-theme: mat.define-light-theme((
115+
color: (
116+
primary: mat.define-palette(mat.$red-palette),
117+
accent: mat.define-palette(mat.$red-palette),
118+
warn: mat.define-palette(mat.$red-palette),
119+
),
120+
typography: mat.define-typography-config(),
121+
density: 0
122+
));
123+
$token-slots: mat.m2-tokens-from-theme($fake-theme);
124+
125+
// TODO(mmalerba): Fill in remaining tokens.
126+
@return mat.private-merge-all(
127+
// Fill in official MDC tokens.
128+
_get-mdc-tokens((mdc, checkbox),
129+
_fix-checkbox-token-names(mdc-tokens.md-comp-checkbox-values()), $token-slots),
130+
_get-mdc-tokens((mdc, elevated-card), mdc-tokens.md-comp-elevated-card-values(),
131+
$token-slots),
132+
_get-mdc-tokens((mdc, outlined-card), mdc-tokens.md-comp-outlined-card-values(),
133+
$token-slots),
134+
// Choose values for our made up tokens based on MDC system tokens or sensible hardcoded
135+
// values.
136+
(
137+
(mat, card): mat.private-merge-all(
138+
_get-typography-tokens($typography, title-text, title-large),
139+
_get-typography-tokens($typography, subtitle-text, title-medium),
140+
(
141+
subtitle-text-color: map.get($colors, on-surface)
142+
)
143+
)
144+
),
145+
);
146+
}

src/material/card/_card-theme.scss

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,22 @@
8080

8181
@mixin theme-from-tokens($tokens) {
8282
@if ($tokens != ()) {
83-
@include mdc-elevated-card-theme.theme(map.get($tokens, tokens-mdc-elevated-card.$prefix));
84-
@include mdc-outlined-card-theme.theme(map.get($tokens, tokens-mdc-outlined-card.$prefix));
83+
$elevated-card-tokens: map.get($tokens, tokens-mdc-elevated-card.$prefix);
84+
// Work around a bug in MDC where the elevation is not resolved to an actual shadow value.
85+
$elevated-card-tokens: token-utils.resolve-elevation(
86+
$elevated-card-tokens,
87+
container-elevation,
88+
container-shadow-color
89+
);
90+
$outlined-card-tokens: map.get($tokens, tokens-mdc-outlined-card.$prefix);
91+
// Work around a bug in MDC where the elevation is not resolved to an actual shadow value.
92+
$outlined-card-tokens: token-utils.resolve-elevation(
93+
$outlined-card-tokens,
94+
container-elevation,
95+
container-shadow-color
96+
);
97+
@include mdc-elevated-card-theme.theme($elevated-card-tokens);
98+
@include mdc-outlined-card-theme.theme($outlined-card-tokens);
8599
@include token-utils.create-token-values(
86100
tokens-mat-card.$prefix, map.get($tokens, tokens-mat-card.$prefix));
87101
}

0 commit comments

Comments
 (0)