Skip to content

Commit 738f57c

Browse files
authored
refactor(material/radio): simplify structural styles (angular#29271)
Simplifies the radio button's structural styles to make them smaller and easier to maintain.
1 parent b4d01dc commit 738f57c

File tree

5 files changed

+352
-171
lines changed

5 files changed

+352
-171
lines changed

src/material/list/_list-theme.scss

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
@use 'sass:map';
22
@use '@material/list/evolution-mixins';
3-
@use '@material/radio/radio-theme' as mdc-radio-theme;
43
@use '@material/list/list-theme' as mdc-list-theme;
54

65
@use '../core/style/sass-utils';
@@ -44,20 +43,23 @@
4443

4544
.mdc-list-item__start,
4645
.mdc-list-item__end {
47-
@include mdc-radio-theme.theme(tokens-mdc-radio.get-color-tokens($theme, primary));
46+
@include token-utils.create-token-values(
47+
tokens-mdc-radio.$prefix, tokens-mdc-radio.get-color-tokens($theme, primary));
4848
}
4949

5050
.mat-accent {
5151
.mdc-list-item__start,
5252
.mdc-list-item__end {
53-
@include mdc-radio-theme.theme(tokens-mdc-radio.get-color-tokens($theme, accent));
53+
@include token-utils.create-token-values(
54+
tokens-mdc-radio.$prefix, tokens-mdc-radio.get-color-tokens($theme, accent));
5455
}
5556
}
5657

5758
.mat-warn {
5859
.mdc-list-item__start,
5960
.mdc-list-item__end {
60-
@include mdc-radio-theme.theme(tokens-mdc-radio.get-color-tokens($theme, warn));
61+
@include token-utils.create-token-values(
62+
tokens-mdc-radio.$prefix, tokens-mdc-radio.get-color-tokens($theme, warn));
6163
}
6264
}
6365

@@ -111,7 +113,8 @@
111113

112114
.mdc-list-item__start,
113115
.mdc-list-item__end {
114-
@include mdc-radio-theme.theme(tokens-mdc-radio.get-density-tokens($theme));
116+
@include token-utils.create-token-values(
117+
tokens-mdc-radio.$prefix, tokens-mdc-radio.get-density-tokens($theme));
115118
}
116119

117120
// TODO(mmalerba): This is added to maintain the same style MDC used prior to the token-based

src/material/list/list-option.scss

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
@use '@material/radio/radio' as mdc-radio;
2-
@use '@material/radio/radio-theme' as mdc-radio-theme;
3-
41
@use '../checkbox/checkbox-common';
2+
@use '../radio/radio-common';
53
@use '../core/mdc-helpers/mdc-helpers';
6-
@use '../core/tokens/m2/mdc/radio' as tokens-mdc-radio;
74
@use './list-option-trailing-avatar-compat';
85
@use './list-item-hcm-indicator';
96

@@ -12,29 +9,12 @@
129
@include list-option-trailing-avatar-compat.core-styles($query: mdc-helpers.$mdc-base-styles-query);
1310

1411
.mat-mdc-list-option {
15-
// The MDC-based list-option uses the MDC checkbox/radio for the selection indicators.
16-
// We need to ensure that the checkbox and radio styles are not included for the list-option.
17-
@include mdc-helpers.disable-mdc-fallback-declarations {
18-
@include mdc-radio.static-styles(
19-
$query: mdc-helpers.$mdc-base-styles-without-animation-query);
20-
21-
&:not(._mat-animation-noopable) {
22-
@include mdc-radio.static-styles($query: animation);
23-
}
24-
}
25-
2612
// We can't use the MDC checkbox here directly, because this checkbox is purely
2713
// decorative and including the MDC one will bring in unnecessary JS.
2814
@include checkbox-common.checkbox-structure(false);
2915
@include checkbox-common.checkbox-noop-animations;
30-
31-
// We can't use the MDC radio here directly, because this radio is purely
32-
// decorative and including the MDC one will bring in unnecessary JS.
33-
.mdc-radio {
34-
// MDC theme styles also include structural styles so we have to include the theme at least
35-
// once here. The values will be overwritten by our own theme file afterwards.
36-
@include mdc-radio-theme.theme-styles(tokens-mdc-radio.get-token-slots());
37-
}
16+
@include radio-common.radio-structure(false);
17+
@include radio-common.radio-noop-animations;
3818

3919
// The internal checkbox/radio is purely decorative, but because it's an `input`, the user can
4020
// still focus it by tabbing or clicking. Furthermore, `mat-list-option` has the `option` role

src/material/radio/_radio-common.scss

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
@use '../core/tokens/m2/mdc/radio' as tokens-mdc-radio;
2+
@use '../core/tokens/token-utils';
3+
4+
$_icon-size: 20px;
5+
6+
@function _enter-transition($name) {
7+
@return $name 90ms cubic-bezier(0, 0, 0.2, 1);
8+
}
9+
10+
@function _exit-transition($name) {
11+
@return $name 90ms cubic-bezier(0.4, 0, 0.6, 1);
12+
}
13+
14+
// Structural styles for a radio button. Shared with the selection list.
15+
@mixin radio-structure($is-interactive) {
16+
$tokens: tokens-mdc-radio.$prefix, tokens-mdc-radio.get-token-slots();
17+
18+
.mdc-radio {
19+
display: inline-block;
20+
position: relative;
21+
flex: 0 0 auto;
22+
box-sizing: content-box;
23+
width: $_icon-size;
24+
height: $_icon-size;
25+
cursor: pointer;
26+
27+
// This is something we inherited from MDC, but it shouldn't be necessary.
28+
// Removing it will likely lead to screenshot diffs.
29+
will-change: opacity, transform, border-color, color;
30+
31+
@include token-utils.use-tokens($tokens...) {
32+
$size-token: var(#{token-utils.get-token-variable(state-layer-size)});
33+
padding: calc((#{$size-token} - #{$_icon-size}) / 2);
34+
}
35+
36+
@if ($is-interactive) {
37+
// MDC's hover indication comes from their ripple which we don't use.
38+
&:hover .mdc-radio__native-control:not([disabled]):not(:focus) {
39+
& ~ .mdc-radio__background::before {
40+
opacity: 0.04;
41+
transform: scale(1);
42+
}
43+
}
44+
45+
&:hover .mdc-radio__native-control:not([disabled]) ~ .mdc-radio__background {
46+
.mdc-radio__outer-circle {
47+
@include token-utils.use-tokens($tokens...) {
48+
@include token-utils.create-token-slot(border-color, unselected-hover-icon-color);
49+
}
50+
}
51+
}
52+
53+
&:hover .mdc-radio__native-control:enabled:checked + .mdc-radio__background {
54+
.mdc-radio__outer-circle,
55+
.mdc-radio__inner-circle {
56+
@include token-utils.use-tokens($tokens...) {
57+
@include token-utils.create-token-slot(border-color, selected-hover-icon-color);
58+
}
59+
}
60+
}
61+
62+
&:active .mdc-radio__native-control:enabled:not(:checked) + .mdc-radio__background {
63+
.mdc-radio__outer-circle {
64+
@include token-utils.use-tokens($tokens...) {
65+
@include token-utils.create-token-slot(border-color, unselected-pressed-icon-color);
66+
}
67+
}
68+
}
69+
70+
&:active .mdc-radio__native-control:enabled:checked + .mdc-radio__background {
71+
.mdc-radio__outer-circle,
72+
.mdc-radio__inner-circle {
73+
@include token-utils.use-tokens($tokens...) {
74+
@include token-utils.create-token-slot(border-color, selected-pressed-icon-color);
75+
}
76+
}
77+
}
78+
}
79+
}
80+
81+
.mdc-radio__background {
82+
display: inline-block;
83+
position: relative;
84+
box-sizing: border-box;
85+
width: $_icon-size;
86+
height: $_icon-size;
87+
88+
&::before {
89+
position: absolute;
90+
transform: scale(0, 0);
91+
border-radius: 50%;
92+
opacity: 0;
93+
pointer-events: none;
94+
content: '';
95+
transition: _exit-transition(opacity), _exit-transition(transform);
96+
97+
@include token-utils.use-tokens($tokens...) {
98+
$size-token: var(#{token-utils.get-token-variable(state-layer-size)});
99+
$offset: calc(-1 * (#{$size-token} - #{$_icon-size}) / 2);
100+
width: $size-token;
101+
height: $size-token;
102+
top: $offset;
103+
left: $offset;
104+
}
105+
}
106+
}
107+
108+
.mdc-radio__outer-circle {
109+
position: absolute;
110+
top: 0;
111+
left: 0;
112+
box-sizing: border-box;
113+
width: 100%;
114+
height: 100%;
115+
border-width: 2px;
116+
border-style: solid;
117+
border-radius: 50%;
118+
transition: _exit-transition(border-color);
119+
}
120+
121+
.mdc-radio__inner-circle {
122+
position: absolute;
123+
top: 0;
124+
left: 0;
125+
box-sizing: border-box;
126+
width: 100%;
127+
height: 100%;
128+
transform: scale(0, 0);
129+
border-width: 10px;
130+
border-style: solid;
131+
border-radius: 50%;
132+
transition: _exit-transition(transform), _exit-transition(border-color);
133+
}
134+
135+
.mdc-radio__native-control {
136+
position: absolute;
137+
margin: 0;
138+
padding: 0;
139+
opacity: 0;
140+
top: 0;
141+
right: 0;
142+
left: 0;
143+
cursor: inherit;
144+
z-index: 1;
145+
146+
@include token-utils.use-tokens($tokens...) {
147+
@include token-utils.create-token-slot(width, state-layer-size);
148+
@include token-utils.create-token-slot(height, state-layer-size);
149+
}
150+
151+
&:checked, &:disabled {
152+
+ .mdc-radio__background {
153+
transition: _enter-transition(opacity), _enter-transition(transform);
154+
155+
.mdc-radio__outer-circle {
156+
transition: _enter-transition(border-color);
157+
}
158+
159+
.mdc-radio__inner-circle {
160+
transition: _enter-transition(transform), _enter-transition(border-color);
161+
}
162+
}
163+
}
164+
165+
@if ($is-interactive) {
166+
&:focus + .mdc-radio__background::before {
167+
transform: scale(1);
168+
opacity: 0.12;
169+
transition: _enter-transition(opacity), _enter-transition(transform);
170+
}
171+
}
172+
173+
&:disabled {
174+
@include token-utils.use-tokens($tokens...) {
175+
&:not(:checked) + .mdc-radio__background .mdc-radio__outer-circle {
176+
@include token-utils.create-token-slot(border-color, disabled-unselected-icon-color);
177+
@include token-utils.create-token-slot(opacity, disabled-unselected-icon-opacity);
178+
}
179+
180+
+ .mdc-radio__background {
181+
cursor: default;
182+
183+
.mdc-radio__inner-circle,
184+
.mdc-radio__outer-circle {
185+
@include token-utils.create-token-slot(border-color, disabled-selected-icon-color);
186+
@include token-utils.create-token-slot(opacity, disabled-selected-icon-opacity);
187+
}
188+
}
189+
}
190+
}
191+
192+
&:enabled {
193+
@include token-utils.use-tokens($tokens...) {
194+
&:not(:checked) + .mdc-radio__background .mdc-radio__outer-circle {
195+
@include token-utils.create-token-slot(border-color, unselected-icon-color);
196+
}
197+
198+
&:checked + .mdc-radio__background {
199+
.mdc-radio__outer-circle,
200+
.mdc-radio__inner-circle {
201+
@include token-utils.create-token-slot(border-color, selected-icon-color);
202+
}
203+
}
204+
205+
@if ($is-interactive) {
206+
&:focus:checked + .mdc-radio__background {
207+
.mdc-radio__inner-circle,
208+
.mdc-radio__outer-circle {
209+
@include token-utils.create-token-slot(border-color, selected-focus-icon-color);
210+
}
211+
}
212+
}
213+
}
214+
}
215+
216+
&:checked + .mdc-radio__background .mdc-radio__inner-circle {
217+
transform: scale(0.5);
218+
transition: _enter-transition(transform), _enter-transition(border-color);
219+
}
220+
}
221+
222+
.mdc-radio--disabled {
223+
cursor: default;
224+
pointer-events: none;
225+
}
226+
}
227+
228+
// Conditionally disables the animations of the radio button.
229+
@mixin radio-noop-animations() {
230+
&._mat-animation-noopable {
231+
.mdc-radio__background::before,
232+
.mdc-radio__outer-circle,
233+
.mdc-radio__inner-circle {
234+
// Needs to be `!important`, because MDC's selectors are really specific.
235+
transition: none !important;
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)