Skip to content

Commit dda8122

Browse files
crisbetojelbourn
authored andcommitted
feat(radio): align with 2018 material design spec (#12299)
Aligns the radio button with the latest iteration of the Material spec. The radio itself was mostly aligned, but these changes introduce ripples hover, as well as varying opacity for the focused and active ripples.
1 parent d7c734c commit dda8122

File tree

5 files changed

+65
-62
lines changed

5 files changed

+65
-62
lines changed

src/lib/radio/_radio-theme.scss

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
border-color: mat-color($palette);
88
}
99

10-
.mat-radio-inner-circle {
10+
.mat-radio-inner-circle,
11+
.mat-radio-ripple .mat-ripple-element:not(.mat-radio-persistent-ripple),
12+
&.mat-radio-checked .mat-radio-persistent-ripple,
13+
&:active .mat-radio-persistent-ripple {
1114
background-color: mat-color($palette);
1215
}
13-
14-
.mat-radio-ripple .mat-ripple-element {
15-
background-color: mat-color($palette, 0.26);
16-
}
1716
}
1817

1918
@mixin mat-radio-theme($theme) {
@@ -58,6 +57,12 @@
5857
color: mat-color($foreground, disabled);
5958
}
6059
}
60+
61+
// Switch this to a solid color since we're using `opacity`
62+
// to control how opaque the ripple should be.
63+
.mat-ripple-element {
64+
background-color: map_get($foreground, base);
65+
}
6166
}
6267
}
6368

src/lib/radio/radio.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
[matRippleTrigger]="label"
1010
[matRippleDisabled]="_isRippleDisabled()"
1111
[matRippleCentered]="true"
12-
[matRippleRadius]="23"
12+
[matRippleRadius]="20"
1313
[matRippleAnimation]="{enterDuration: 150}">
14+
15+
<div class="mat-ripple-element mat-radio-persistent-ripple"></div>
1416
</div>
1517
</div>
1618

src/lib/radio/radio.scss

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
$mat-radio-size: $mat-toggle-size !default;
7-
$mat-radio-ripple-radius: 25px;
7+
$mat-radio-ripple-radius: 20px;
88

99
// Top-level host container.
1010
.mat-radio-button {
@@ -111,7 +111,8 @@ $mat-radio-ripple-radius: 25px;
111111
}
112112

113113
// Basic disabled state.
114-
.mat-radio-disabled, .mat-radio-disabled .mat-radio-label {
114+
.mat-radio-disabled,
115+
.mat-radio-disabled .mat-radio-label {
115116
cursor: default;
116117
}
117118

@@ -125,4 +126,29 @@ $mat-radio-ripple-radius: 25px;
125126
width: $mat-radio-ripple-radius * 2;
126127
z-index: 1;
127128
pointer-events: none;
129+
130+
.mat-ripple-element:not(.mat-radio-persistent-ripple) {
131+
opacity: 0.16;
132+
}
133+
}
134+
135+
.mat-radio-persistent-ripple {
136+
width: 100%;
137+
height: 100%;
138+
transform: none;
139+
140+
.mat-radio-container:hover & {
141+
opacity: 0.04;
142+
}
143+
144+
.mat-radio-button.cdk-focused & {
145+
opacity: 0.12;
146+
}
147+
148+
// We do this here, rather than having a `:not(.mat-radio-disabled)`
149+
// above in the `:hover`, because the `:not` will bump the specificity
150+
// a lot and will cause it to overide the focus styles.
151+
&, .mat-radio-disabled .mat-radio-container:hover & {
152+
opacity: 0;
153+
}
128154
}

src/lib/radio/radio.spec.ts

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/f
33
import {Component, DebugElement, ViewChild} from '@angular/core';
44
import {By} from '@angular/platform-browser';
55
import {dispatchFakeEvent} from '@angular/cdk/testing';
6-
import {defaultRippleAnimationConfig} from '@angular/material/core';
76
import {MatRadioButton, MatRadioChange, MatRadioGroup, MatRadioModule} from './index';
87

98
describe('MatRadio', () => {
@@ -197,25 +196,6 @@ describe('MatRadio', () => {
197196
expect(changeSpy).toHaveBeenCalledTimes(1);
198197
});
199198

200-
it('should show a ripple when focusing via the keyboard', fakeAsync(() => {
201-
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
202-
.toBe(0, 'Expected no ripples on init.');
203-
204-
dispatchFakeEvent(radioInputElements[0], 'keydown');
205-
dispatchFakeEvent(radioInputElements[0], 'focus');
206-
207-
tick(defaultRippleAnimationConfig.enterDuration);
208-
209-
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
210-
.toBe(1, 'Expected one ripple after keyboard focus.');
211-
212-
dispatchFakeEvent(radioInputElements[0], 'blur');
213-
tick(defaultRippleAnimationConfig.exitDuration);
214-
215-
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
216-
.toBe(0, 'Expected no ripples on blur.');
217-
}));
218-
219199
it('should update the group and radios when updating the group value', () => {
220200
expect(groupInstance.value).toBeFalsy();
221201

@@ -253,16 +233,21 @@ describe('MatRadio', () => {
253233
dispatchFakeEvent(radioLabelElements[0], 'mousedown');
254234
dispatchFakeEvent(radioLabelElements[0], 'mouseup');
255235

256-
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
257-
.toBe(0, 'Expected a disabled radio button to not show ripples');
236+
let rippleAmount = radioNativeElements[0]
237+
.querySelectorAll('.mat-ripple-element:not(.mat-radio-persistent-ripple)').length;
238+
239+
expect(rippleAmount).toBe(0, 'Expected a disabled radio button to not show ripples');
258240

259241
testComponent.isFirstDisabled = false;
260242
fixture.detectChanges();
261243

262244
dispatchFakeEvent(radioLabelElements[0], 'mousedown');
263245
dispatchFakeEvent(radioLabelElements[0], 'mouseup');
264246

265-
expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
247+
rippleAmount = radioNativeElements[0]
248+
.querySelectorAll('.mat-ripple-element:not(.mat-radio-persistent-ripple)').length;
249+
250+
expect(rippleAmount)
266251
.toBe(1, 'Expected an enabled radio button to show ripples');
267252
});
268253

@@ -274,7 +259,10 @@ describe('MatRadio', () => {
274259
dispatchFakeEvent(radioLabel, 'mousedown');
275260
dispatchFakeEvent(radioLabel, 'mouseup');
276261

277-
expect(radioLabel.querySelectorAll('.mat-ripple-element').length).toBe(0);
262+
const rippleAmount = radioNativeElements[0]
263+
.querySelectorAll('.mat-ripple-element:not(.mat-radio-persistent-ripple)').length;
264+
265+
expect(rippleAmount).toBe(0);
278266
}
279267

280268
testComponent.disableRipple = false;
@@ -284,7 +272,10 @@ describe('MatRadio', () => {
284272
dispatchFakeEvent(radioLabel, 'mousedown');
285273
dispatchFakeEvent(radioLabel, 'mouseup');
286274

287-
expect(radioLabel.querySelectorAll('.mat-ripple-element').length).toBe(1);
275+
const rippleAmount = radioNativeElements[0]
276+
.querySelectorAll('.mat-ripple-element:not(.mat-radio-persistent-ripple)').length;
277+
278+
expect(rippleAmount).toBe(1);
288279
}
289280
});
290281

src/lib/radio/radio.ts

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
9+
import {FocusMonitor} from '@angular/cdk/a11y';
1010
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1111
import {UniqueSelectionDispatcher} from '@angular/cdk/collections';
1212
import {
@@ -36,12 +36,10 @@ import {
3636
CanDisable,
3737
CanDisableRipple,
3838
HasTabIndex,
39-
MatRipple,
4039
mixinColor,
4140
mixinDisabled,
4241
mixinDisableRipple,
4342
mixinTabIndex,
44-
RippleRef,
4543
} from '@angular/material/core';
4644
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
4745

@@ -459,12 +457,6 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
459457
/** Value assigned to this radio. */
460458
private _value: any = null;
461459

462-
/** The child ripple instance. */
463-
@ViewChild(MatRipple) _ripple: MatRipple;
464-
465-
/** Reference to the current focus ripple. */
466-
private _focusRipple: RippleRef | null;
467-
468460
/** Unregister function for _radioDispatcher */
469461
private _removeUniqueSelectionListener: () => void = () => {};
470462

@@ -518,12 +510,16 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
518510

519511
ngAfterViewInit() {
520512
this._focusMonitor
521-
.monitor(this._inputElement)
522-
.subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
513+
.monitor(this._elementRef, true)
514+
.subscribe(focusOrigin => {
515+
if (!focusOrigin && this.radioGroup) {
516+
this.radioGroup._touch();
517+
}
518+
});
523519
}
524520

525521
ngOnDestroy() {
526-
this._focusMonitor.stopMonitoring(this._inputElement);
522+
this._focusMonitor.stopMonitoring(this._elementRef);
527523
this._removeUniqueSelectionListener();
528524
}
529525

@@ -570,21 +566,4 @@ export class MatRadioButton extends _MatRadioButtonMixinBase
570566
}
571567
}
572568

573-
/** Function is called whenever the focus changes for the input element. */
574-
private _onInputFocusChange(focusOrigin: FocusOrigin) {
575-
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
576-
if (!this._focusRipple && focusOrigin === 'keyboard') {
577-
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
578-
} else if (!focusOrigin) {
579-
if (this.radioGroup) {
580-
this.radioGroup._touch();
581-
}
582-
583-
if (this._focusRipple) {
584-
this._focusRipple.fadeOut();
585-
this._focusRipple = null;
586-
}
587-
}
588-
}
589-
590569
}

0 commit comments

Comments
 (0)