Skip to content

Commit 2604f15

Browse files
authored
feat(material/button): change icon-button to use MDC's token API (#26824)
1 parent 475de3c commit 2604f15

File tree

4 files changed

+181
-47
lines changed

4 files changed

+181
-47
lines changed

src/material/button/_icon-button-theme.scss

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,66 @@
11
@use 'sass:map';
2+
@use '@material/density/functions' as mdc-density-functions;
23
@use '@material/icon-button/mixins' as mdc-icon-button;
34
@use '@material/icon-button/icon-button-theme' as mdc-icon-button-theme;
45
@use '@material/theme/theme-color' as mdc-theme-color;
6+
@use '../core/tokens/m2/mdc/icon-button' as tokens-mdc-icon-button;
57

68
@use './button-theme-private';
79
@use '../core/mdc-helpers/mdc-helpers';
810
@use '../core/theming/theming';
911
@use '../core/typography/typography';
1012

13+
@mixin _ripple-color($color) {
14+
--mat-mdc-button-persistent-ripple-color: #{$color};
15+
--mat-mdc-button-ripple-color: #{rgba($color, 0.1)};
16+
}
17+
18+
@function _variable-safe-contrast-tone($value, $is-dark) {
19+
@if ($value == 'dark' or $value == 'light' or type-of($value) == 'color') {
20+
@return mdc-theme-color.contrast-tone($value);
21+
}
22+
23+
@return if($is-dark, 'light', 'dark');
24+
}
25+
26+
1127
@mixin color($config-or-theme) {
1228
$config: theming.get-color-config($config-or-theme);
13-
@include mdc-helpers.using-mdc-theme($config) {
14-
$is-dark: map.get($config, is-dark);
15-
$on-surface: mdc-theme-color.prop-value(on-surface);
16-
$disabled-color: rgba($on-surface, if($is-dark, 0.5, 0.38));
17-
18-
.mat-mdc-icon-button {
19-
@include button-theme-private.ripple-theme-styles($config, false);
20-
21-
&.mat-primary {
22-
@include mdc-icon-button-theme.theme((icon-color: mdc-theme-color.prop-value(primary)));
23-
}
24-
25-
&.mat-accent {
26-
@include mdc-icon-button-theme.theme((icon-color: mdc-theme-color.prop-value(secondary)));
27-
}
28-
29-
&.mat-warn {
30-
@include mdc-icon-button-theme.theme((icon-color: (mdc-theme-color.prop-value(error))));
31-
}
32-
33-
@include button-theme-private.apply-disabled-style() {
34-
@include mdc-icon-button-theme.theme((
35-
icon-color: $disabled-color,
36-
disabled-icon-color: $disabled-color,
37-
));
38-
}
29+
$color-tokens: tokens-mdc-icon-button.get-color-tokens($config);
30+
$background-palette: map.get($config, background);
31+
$surface: theming.get-color-from-palette($background-palette, card);
32+
$is-dark: map.get($config, is-dark);
33+
$on-surface: if(_variable-safe-contrast-tone($surface, $is-dark) == 'dark', #000, #fff);
34+
35+
.mat-mdc-icon-button {
36+
@include button-theme-private.ripple-theme-styles($config, false);
37+
@include mdc-icon-button-theme.theme($color-tokens);
38+
@include _ripple-color($on-surface);
39+
40+
&.mat-primary {
41+
$color: theming.get-color-from-palette(map.get($config, primary));
42+
@include mdc-icon-button-theme.theme((icon-color: $color));
43+
@include _ripple-color($color);
44+
}
45+
46+
&.mat-accent {
47+
$color: theming.get-color-from-palette(map.get($config, accent));
48+
@include mdc-icon-button-theme.theme((icon-color: $color));
49+
@include _ripple-color($color);
50+
}
51+
52+
&.mat-warn {
53+
$color: theming.get-color-from-palette(map.get($config, warn));
54+
@include mdc-icon-button-theme.theme((icon-color: $color));
55+
@include _ripple-color($color);
56+
}
57+
58+
@include button-theme-private.apply-disabled-style() {
59+
$disabled-color: rgba($on-surface, if($is-dark, 0.5, 0.38));
60+
@include mdc-icon-button-theme.theme((
61+
icon-color: $disabled-color,
62+
disabled-icon-color: $disabled-color,
63+
));
3964
}
4065
}
4166
}
@@ -50,10 +75,25 @@
5075

5176
@mixin density($config-or-theme) {
5277
$density-scale: theming.get-density-config($config-or-theme);
53-
// Use `mat-mdc-button-base` to increase the specificity over the button's structural styles.
54-
.mat-mdc-icon-button.mat-mdc-button-base {
55-
@include mdc-icon-button.density($density-scale, $query: mdc-helpers.$mdc-base-styles-query);
56-
@include button-theme-private.touch-target-density($density-scale);
78+
79+
.mat-mdc-icon-button {
80+
// Manually apply the expected density theming, and include the padding
81+
// as it was applied before
82+
$calculated-size: mdc-density-functions.prop-value(
83+
$density-config: (
84+
size: (
85+
default: 48px,
86+
maximum: 48px,
87+
minimum: 28px,
88+
),
89+
),
90+
$density-scale: $density-scale,
91+
$property-name: size,
92+
);
93+
94+
@include mdc-icon-button-theme.theme((
95+
state-layer-size: $calculated-size,
96+
));
5797
}
5898
}
5999

src/material/button/icon-button.scss

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
1-
@use 'sass:map';
21
@use '@material/icon-button/icon-button' as mdc-icon-button;
32
@use '@material/icon-button/icon-button-theme' as mdc-icon-button-theme;
3+
@use '@material/theme/custom-properties' as mdc-custom-properties;
4+
5+
@use '../core/tokens/m2/mdc/icon-button' as m2-mdc-icon-button;
46

57
@use './button-base';
6-
@use '../core/mdc-helpers/mdc-helpers';
78
@use '../core/style/private';
89

9-
@include mdc-helpers.disable-mdc-fallback-declarations {
10-
@include mdc-icon-button.without-ripple($query: mdc-helpers.$mdc-base-styles-query);
10+
// The slots for tokens that will be configured in the theme can be emitted with no fallback.
11+
@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) {
12+
$token-slots: m2-mdc-icon-button.get-token-slots();
13+
14+
// Add the MDC component static styles.
15+
@include mdc-icon-button.static-styles();
16+
17+
.mat-mdc-icon-button {
18+
// Add the official slots for the MDC component.
19+
@include mdc-icon-button-theme.theme-styles($token-slots);
20+
21+
// Add default values for tokens that aren't outputted by the theming API.
22+
@include mdc-icon-button-theme.theme(m2-mdc-icon-button.get-unthemable-tokens());
23+
}
1124
}
1225

1326
.mat-mdc-icon-button {
14-
@include mdc-helpers.disable-mdc-fallback-declarations {
15-
$theme-overrides: button-base.mat-private-button-remove-ripple((
16-
icon-color: inherit,
17-
// We don't change the color on focus/hover so exclude
18-
// these styles both to reduce bundle size and specificity.
19-
focus-icon-color: null,
20-
hover-icon-color: null,
21-
pressed-icon-color: null,
22-
));
23-
24-
@include mdc-icon-button-theme.theme-styles(
25-
map.merge(mdc-icon-button-theme.$light-theme, $theme-overrides));
26-
}
27+
// Not all applications import the theming which would apply a default padding.
28+
// TODO: Determine how to enforce theming exists, otherwise padding will be unset.
29+
padding: 12px;
30+
31+
// Icon size used to be placed on the host element. Now, in `theme-styles` it is placed on
32+
// the unused `.mdc-button__icon` class. Explicitly set the font-size here.
33+
font-size: var(--mdc-icon-button-icon-size);
2734

2835
// Border radius is inherited by ripple to know its shape. Set to 50% so the ripple is round.
2936
border-radius: 50%;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
@use 'sass:map';
2+
@use '../../token-utils';
3+
4+
// The prefix used to generate the fully qualified name for tokens in this file.
5+
$prefix: (mdc, icon-button);
6+
7+
// Tokens that can't be configured through Angular Material's current theming API,
8+
// but may be in a future version of the theming API.
9+
//
10+
// Tokens that are available in MDC, but not used in Angular Material should be mapped to `null`.
11+
// `null` indicates that we are intentionally choosing not to emit a slot or value for the token in
12+
// our CSS.
13+
@function get-unthemable-tokens() {
14+
@return (
15+
// =============================================================================================
16+
// = TOKENS THAT SHOULD NOT BE CUSTOMIZABLE =
17+
// =============================================================================================
18+
// Determines the size of the icon. Name is inaccurate - applies to the whole component,
19+
// not just the state layer.
20+
state-layer-size: 48px,
21+
22+
// MDC's icon size applied to svg and img elements inside the component
23+
icon-size: 24px,
24+
25+
// Only applies to :disabled icons, but Angular Components uses [disabled] since :disabled
26+
// wouldn't work on <a> tags.
27+
disabled-icon-color: black,
28+
29+
// Angular version applies an opacity 1 with a color change, and this only applies with
30+
// :disabled anyways.
31+
disabled-icon-opacity: 0.38,
32+
33+
// =============================================================================================
34+
// = TOKENS NOT USED IN ANGULAR MATERIAL =
35+
// =============================================================================================
36+
// State layer is unused
37+
focus-icon-color: null,
38+
focus-state-layer-color: null,
39+
focus-state-layer-opacity: null,
40+
hover-icon-color: null,
41+
hover-state-layer-color: null,
42+
hover-state-layer-opacity: null,
43+
pressed-icon-color: null,
44+
pressed-state-layer-color: null,
45+
pressed-state-layer-opacity: null,
46+
47+
);
48+
}
49+
50+
// Tokens that can be configured through Angular Material's color theming API.
51+
@function get-color-tokens($config) {
52+
@return (
53+
icon-color: inherit,
54+
);
55+
}
56+
57+
// Tokens that can be configured through Angular Material's typography theming API.
58+
@function get-typography-tokens($config) {
59+
@return ();
60+
}
61+
62+
// Tokens that can be configured through Angular Material's density theming API.
63+
@function get-density-tokens($config) {
64+
@return ();
65+
}
66+
67+
// Combines the tokens generated by the above functions into a single map with placeholder values.
68+
// This is used to create token slots.
69+
@function get-token-slots() {
70+
@return map.merge(
71+
get-unthemable-tokens(),
72+
map.merge(
73+
get-color-tokens(token-utils.$placeholder-color-config),
74+
map.merge(
75+
get-typography-tokens(token-utils.$placeholder-typography-config),
76+
get-density-tokens(token-utils.$placeholder-density-config)
77+
)
78+
)
79+
);
80+
}

src/material/core/tokens/tests/test-validate-tokens.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
@use '@material/card/outlined-card-theme' as mdc-outlined-card-theme;
66
@use '@material/checkbox/checkbox-theme' as mdc-checkbox-theme;
77
@use '@material/circular-progress/circular-progress-theme' as mdc-circular-progress-theme;
8+
@use '@material/icon-button/icon-button-theme' as mdc-icon-button-theme;
89
@use '@material/linear-progress/linear-progress-theme' as mdc-linear-progress-theme;
910
@use '@material/list/list-theme' as mdc-list-theme;
1011
@use '@material/theme/validate' as mdc-validate;
1112

1213
@use '../m2/mdc/circular-progress' as tokens-mdc-circular-progress;
1314
@use '../m2/mdc/elevated-card' as tokens-mdc-elevated-card;
15+
@use '../m2/mdc/icon-button' as tokens-mdc-icon-button;
1416
@use '../m2/mdc/checkbox' as tokens-mdc-checkbox;
1517
@use '../m2/mdc/linear-progress' as tokens-mdc-linear-progress;
1618
@use '../m2/mdc/list' as tokens-mdc-list;
@@ -38,6 +40,11 @@
3840
$slots: tokens-mdc-circular-progress.get-token-slots(),
3941
$reference: mdc-circular-progress-theme.$light-theme
4042
);
43+
@include validate-slots(
44+
$component: 'm2.mdc.icon-button',
45+
$slots: tokens-mdc-icon-button.get-token-slots(),
46+
$reference: mdc-icon-button-theme.$light-theme
47+
);
4148
@include validate-slots(
4249
$component: 'm2.mdc.elevated-card',
4350
$slots: tokens-mdc-elevated-card.get-token-slots(),

0 commit comments

Comments
 (0)