Skip to content

Commit 31f9ceb

Browse files
authored
refactor(material/button): handle disabled states through tokens (#28007)
MDC applies disabled styles through the `:disabled` selector which forced us to work around it by using a selector like `.mat-mdc-button[disabled][disabled]` and setting both the disabled and enabled button tokens. This is problematic, because it increases the specificity too much and it introduces space for mistakes, because we have to duplicate the tokens values. These changes resolve the issue by re-applying the token slots to disabled buttons with the correct selector so that they can reuse the same tokens as the `button` nodes.
1 parent ca431d3 commit 31f9ceb

File tree

5 files changed

+204
-134
lines changed

5 files changed

+204
-134
lines changed

src/material/button/_button-base.scss

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
@use 'sass:map';
21
@use '@material/touch-target' as mdc-touch-target;
32

43
@use '../core/style/layout-common';
@@ -89,15 +88,3 @@
8988
$query: mdc-helpers.$mdc-base-styles-query);
9089
}
9190
}
92-
93-
// Changes a button token set to exclude the ripple styles.
94-
@function mat-private-button-remove-ripple($tokens) {
95-
@return map.merge($tokens, (
96-
focus-state-layer-color: null,
97-
focus-state-layer-opacity: null,
98-
hover-state-layer-color: null,
99-
hover-state-layer-opacity: null,
100-
pressed-state-layer-color: null,
101-
pressed-state-layer-opacity: null,
102-
));
103-
}

src/material/button/_button-theme-private.scss

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,6 @@
4545
}
4646
}
4747

48-
// Wraps the content style in a selector for the disabled state.
49-
// MDC adds theme color by using :not(:disabled), so just using [disabled] once will not
50-
// override this, neither will it apply to anchor tags. This needs to override the
51-
// previously set theme color, so it must be ordered after the theme styles.
52-
// TODO(andrewseguin): Discuss with the MDC team to see if we can avoid the :not(:disabled) by
53-
// manually styling disabled buttons with a [disabled] selector.
54-
@mixin apply-disabled-style() {
55-
&[disabled][disabled] {
56-
@content;
57-
}
58-
}
59-
6048
// Hides the touch target on lower densities.
6149
@mixin touch-target-density($scale) {
6250
@include mdc-helpers.if-touch-targets-unsupported($scale) {

src/material/button/_button-theme.scss

Lines changed: 34 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
@use '@material/button/button-filled-theme' as mdc-button-filled-theme;
55
@use '@material/button/button-protected-theme' as mdc-button-protected-theme;
66
@use '@material/button/button-outlined-theme' as mdc-button-outlined-theme;
7-
@use '@material/theme/theme-color' as mdc-theme-color;
87
@use '@material/elevation/elevation-theme' as mdc-elevation-theme;
98

109
@use './button-theme-private';
@@ -13,6 +12,7 @@
1312
@use '../core/theming/inspection';
1413
@use '../core/typography/typography';
1514
@use '../core/tokens/m2/mdc/filled-button' as tokens-mdc-filled-button;
15+
@use '../core/tokens/m2/mdc/outlined-button' as tokens-mdc-outlined-button;
1616
@use '../core/tokens/m2/mdc/protected-button' as tokens-mdc-protected-button;
1717
@use '../core/tokens/m2/mdc/text-button' as tokens-mdc-text-button;
1818

@@ -21,58 +21,23 @@
2121
@return if(mdc-helpers.variable-safe-contrast-tone($palette, $is-dark) == 'dark', #000, #fff);
2222
}
2323

24-
@mixin _outlined-button-variant($color) {
25-
@include mdc-button-outlined-theme.theme((
26-
label-text-color: $color,
27-
));
28-
}
29-
3024
@mixin base($theme) {
3125
// TODO(mmalerba): Move button base tokens here
3226
}
3327

3428
@mixin color($theme) {
35-
@include mdc-helpers.using-mdc-theme($theme) {
36-
$is-dark: inspection.get-theme-type($theme) == dark;
37-
$on-surface: mdc-theme-color.prop-value(on-surface);
38-
$disabled-ink-color: rgba($on-surface, if($is-dark, 0.5, 0.38));
39-
$primary: mdc-theme-color.prop-value(primary);
40-
$secondary: mdc-theme-color.prop-value(secondary);
41-
$error: mdc-theme-color.prop-value(error);
42-
43-
.mat-mdc-outlined-button {
44-
@include mdc-button-outlined-theme.theme((
45-
outline-color: rgba(mdc-theme-color.prop-value(on-surface), 0.12)
46-
));
47-
48-
&.mat-unthemed {
49-
@include _outlined-button-variant($on-surface);
50-
}
51-
52-
&.mat-primary {
53-
@include _outlined-button-variant($primary);
54-
}
55-
56-
&.mat-accent {
57-
@include _outlined-button-variant($secondary);
58-
}
59-
60-
&.mat-warn {
61-
@include _outlined-button-variant($error);
62-
}
29+
$surface: inspection.get-theme-color($theme, background, card);
30+
$primary: inspection.get-theme-color($theme, primary);
31+
$accent: inspection.get-theme-color($theme, accent);
32+
$error: inspection.get-theme-color($theme, warn);
6333

64-
@include button-theme-private.apply-disabled-style() {
65-
@include mdc-button-outlined-theme.theme((
66-
// We need to pass both the disabled and enabled values, because the enabled
67-
// ones apply to anchors while the disabled ones are for buttons.
68-
label-text-color: $disabled-ink-color,
69-
disabled-label-text-color: $disabled-ink-color,
70-
outline-color: rgba($on-surface, 0.12),
71-
disabled-outline-color: rgba($on-surface, 0.12),
72-
));
73-
}
74-
}
34+
$on-surface: _on-color($theme, $surface);
35+
$on-primary: _on-color($theme, $primary);
36+
$on-accent: _on-color($theme, $accent);
37+
$on-error: _on-color($theme, $error);
7538

39+
// TODO: remove these when tokenizing the ripples.
40+
@include mdc-helpers.using-mdc-theme($theme) {
7641
// Ripple colors
7742
.mat-mdc-button, .mat-mdc-outlined-button {
7843
@include button-theme-private.ripple-theme-styles($theme, false);
@@ -83,16 +48,6 @@
8348
}
8449
}
8550

86-
$surface: inspection.get-theme-color($theme, background, card);
87-
$primary: inspection.get-theme-color($theme, primary);
88-
$accent: inspection.get-theme-color($theme, accent);
89-
$error: inspection.get-theme-color($theme, warn);
90-
91-
$on-surface: _on-color($theme, $surface);
92-
$on-primary: _on-color($theme, $primary);
93-
$on-accent: _on-color($theme, $accent);
94-
$on-error: _on-color($theme, $error);
95-
9651
.mat-mdc-button {
9752
@include mdc-button-text-theme.theme(tokens-mdc-text-button.get-color-tokens($theme));
9853

@@ -179,41 +134,34 @@
179134
}
180135
}
181136

182-
$is-dark: inspection.get-theme-type($theme) == dark;
183-
$disabled-ink-color: rgba($on-surface, if($is-dark, 0.5, 0.38));
184-
$disabled-container-color: rgba($on-surface, 0.12);
137+
.mat-mdc-outlined-button {
138+
$default-color-tokens: tokens-mdc-outlined-button.get-color-tokens(
139+
$theme,
140+
$on-surface,
141+
$on-surface
142+
);
143+
$primary-color-tokens: tokens-mdc-outlined-button.get-color-tokens(
144+
$theme,
145+
$primary,
146+
$on-primary
147+
);
148+
$accent-color-tokens: tokens-mdc-outlined-button.get-color-tokens($theme, $accent, $on-accent);
149+
$warn-color-tokens: tokens-mdc-outlined-button.get-color-tokens($theme, $error, $on-error);
185150

186-
// TODO: these disabled styles are a bit too specific currently.
187-
// Once the buttons are fully tokenized, we should rework how they're applied.
188-
.mat-mdc-button {
189-
@include button-theme-private.apply-disabled-style() {
190-
@include mdc-button-text-theme.theme((
191-
disabled-label-text-color: $disabled-ink-color,
192-
label-text-color: $disabled-ink-color,
193-
));
151+
&.mat-unthemed {
152+
@include mdc-button-outlined-theme.theme($default-color-tokens);
194153
}
195-
}
196154

197-
.mat-mdc-raised-button {
198-
@include button-theme-private.apply-disabled-style() {
199-
@include mdc-elevation-theme.elevation(0);
200-
@include mdc-button-protected-theme.theme((
201-
disabled-container-color: $disabled-container-color,
202-
disabled-label-text-color: $disabled-ink-color,
203-
container-color: $disabled-container-color,
204-
label-text-color: $disabled-ink-color,
205-
));
155+
&.mat-primary {
156+
@include mdc-button-outlined-theme.theme($primary-color-tokens);
206157
}
207-
}
208158

209-
.mat-mdc-unelevated-button {
210-
@include button-theme-private.apply-disabled-style() {
211-
@include mdc-button-filled-theme.theme((
212-
disabled-container-color: $disabled-container-color,
213-
disabled-label-text-color: $disabled-ink-color,
214-
container-color: $disabled-container-color,
215-
label-text-color: $disabled-ink-color,
216-
));
159+
&.mat-accent {
160+
@include mdc-button-outlined-theme.theme($accent-color-tokens);
161+
}
162+
163+
&.mat-warn {
164+
@include mdc-button-outlined-theme.theme($warn-color-tokens);
217165
}
218166
}
219167
}

src/material/button/button.scss

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
@use 'sass:map';
21
@use '@material/button/button' as mdc-button;
32
@use '@material/button/button-base' as mdc-button-base;
43
@use '@material/button/variables' as mdc-button-variables;
@@ -11,39 +10,33 @@
1110
@use './button-base';
1211
@use '../core/mdc-helpers/mdc-helpers';
1312
@use '../core/style/private' as style-private;
13+
@use '../core/tokens/token-utils';
1414
@use '../core/focus-indicators/private' as focus-indicators-private;
1515
@use '../core/tokens/m2/mdc/filled-button' as tokens-mdc-filled-button;
16+
@use '../core/tokens/m2/mdc/outlined-button' as tokens-mdc-outlined-button;
1617
@use '../core/tokens/m2/mdc/protected-button' as tokens-mdc-protected-button;
1718
@use '../core/tokens/m2/mdc/text-button' as tokens-mdc-text-button;
1819

1920
@include mdc-helpers.disable-mdc-fallback-declarations {
2021
@include mdc-button.static-styles-without-ripple($query: mdc-helpers.$mdc-base-styles-query);
21-
22-
.mat-mdc-outlined-button {
23-
// Keys to exclude from the MDC theme config, allowing us to drop styles we don't need.
24-
$override-keys: button-base.mat-private-button-remove-ripple((
25-
label-text-font: null,
26-
label-text-size: null,
27-
label-text-tracking: null,
28-
label-text-transform: null,
29-
label-text-weight: null,
30-
with-icon-icon-size: null,
31-
label-text-color: inherit,
32-
));
33-
34-
@include mdc-button-outlined-theme.theme-styles(
35-
map.merge(mdc-button-outlined-theme.$light-theme, $override-keys));
36-
}
3722
}
3823

3924
@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) {
4025
.mat-mdc-button {
41-
@include mdc-button-text-theme.theme-styles(tokens-mdc-text-button.get-token-slots());
26+
$mdc-text-button-slots: tokens-mdc-text-button.get-token-slots();
27+
28+
@include mdc-button-text-theme.theme-styles($mdc-text-button-slots);
4229
@include mdc-button-text-theme.theme(tokens-mdc-text-button.get-unthemable-tokens());
30+
31+
@include token-utils.use-tokens(tokens-mdc-text-button.$prefix, $mdc-text-button-slots) {
32+
// We need to re-apply the disabled tokens since MDC uses
33+
// `:disabled` which doesn't apply to anchors.
34+
@include button-base.mat-private-button-disabled {
35+
@include token-utils.create-token-slot(color, disabled-label-text-color);
36+
}
37+
}
4338
}
4439

45-
// Note that we don't include a feature query, because this mixins declare
46-
// all the "slots" for CSS variables that will be defined in the theme.
4740
.mat-mdc-unelevated-button {
4841
$mdc-filled-button-slots: tokens-mdc-filled-button.get-token-slots();
4942

@@ -52,10 +45,17 @@
5245

5346
// Add default values for MDC text button tokens that aren't outputted by the theming API.
5447
@include mdc-button-filled-theme.theme(tokens-mdc-filled-button.get-unthemable-tokens());
48+
49+
@include token-utils.use-tokens(tokens-mdc-filled-button.$prefix, $mdc-filled-button-slots) {
50+
// We need to re-apply the disabled tokens since MDC uses
51+
// `:disabled` which doesn't apply to anchors.
52+
@include button-base.mat-private-button-disabled {
53+
@include token-utils.create-token-slot(color, disabled-label-text-color);
54+
@include token-utils.create-token-slot(background-color, disabled-container-color);
55+
}
56+
}
5557
}
5658

57-
// Note that we don't include a feature query, because this mixins declare
58-
// all the "slots" for CSS variables that will be defined in the theme.
5959
.mat-mdc-raised-button {
6060
$mdc-button-protected-slots: tokens-mdc-protected-button.get-token-slots();
6161

@@ -64,6 +64,43 @@
6464

6565
// Add default values for MDC text button tokens that aren't outputted by the theming API.
6666
@include mdc-button-protected-theme.theme(tokens-mdc-protected-button.get-unthemable-tokens());
67+
68+
@include token-utils.use-tokens(
69+
tokens-mdc-protected-button.$prefix,
70+
$mdc-button-protected-slots) {
71+
// We need to re-apply the disabled tokens since MDC uses
72+
// `:disabled` which doesn't apply to anchors.
73+
@include button-base.mat-private-button-disabled {
74+
@include token-utils.create-token-slot(color, disabled-label-text-color);
75+
@include token-utils.create-token-slot(background-color, disabled-container-color);
76+
77+
// Since we're still doing elevation through the theme, we need additional specificity here.
78+
&[disabled] {
79+
box-shadow: none;
80+
}
81+
}
82+
}
83+
}
84+
85+
.mat-mdc-outlined-button {
86+
$mdc-outlined-button-slots: tokens-mdc-outlined-button.get-token-slots();
87+
88+
// Add the slots for MDC text button.
89+
@include mdc-button-outlined-theme.theme-styles($mdc-outlined-button-slots);
90+
91+
// Add default values for MDC text button tokens that aren't outputted by the theming API.
92+
@include mdc-button-outlined-theme.theme(tokens-mdc-outlined-button.get-unthemable-tokens());
93+
94+
@include token-utils.use-tokens(
95+
tokens-mdc-outlined-button.$prefix,
96+
$mdc-outlined-button-slots) {
97+
// We need to re-apply the disabled tokens since MDC uses
98+
// `:disabled` which doesn't apply to anchors.
99+
@include button-base.mat-private-button-disabled {
100+
@include token-utils.create-token-slot(color, disabled-label-text-color);
101+
@include token-utils.create-token-slot(border-color, disabled-outline-color);
102+
}
103+
}
67104
}
68105
}
69106

@@ -72,7 +109,6 @@
72109
.mat-mdc-raised-button,
73110
.mat-mdc-outlined-button {
74111
@include button-base.mat-private-button-interactive();
75-
@include button-base.mat-private-button-disabled();
76112
@include button-base.mat-private-button-touch-target(false);
77113
@include style-private.private-animation-noop();
78114
}

0 commit comments

Comments
 (0)