Skip to content

Commit

Permalink
feat: add ripples to button-toggle (#9891)
Browse files Browse the repository at this point in the history
* Adds ripples to the `MatButtonToggle` component
* Fixes that the focus overlay for button toggles shows on mouse/touch press.
* Properly stops monitoring through the `FocusMonitor` on component destroy.
* Removes unnecessary mixin for focus-overlay color. Color can be always set, because the overlay will be toggled through `opacity` (performance improvement)

Closes #9442
  • Loading branch information
devversion authored and jelbourn committed Feb 13, 2018
1 parent 66a01fb commit 53417d4
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 25 deletions.
16 changes: 12 additions & 4 deletions src/demo-app/button-toggle/button-toggle-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ <h1>Exclusive Selection</h1>

<section class="demo-section">
<mat-button-toggle-group name="alignment" [vertical]="isVertical">
<mat-button-toggle value="left"><mat-icon>format_align_left</mat-icon></mat-button-toggle>
<mat-button-toggle value="center"><mat-icon>format_align_center</mat-icon></mat-button-toggle>
<mat-button-toggle value="right"><mat-icon>format_align_right</mat-icon></mat-button-toggle>
<mat-button-toggle value="justify" [disabled]="isDisabled"><mat-icon>format_align_justify</mat-icon></mat-button-toggle>
<mat-button-toggle value="left"[disabled]="isDisabled">
<mat-icon>format_align_left</mat-icon>
</mat-button-toggle>
<mat-button-toggle value="center" [disabled]="isDisabled">
<mat-icon>format_align_center</mat-icon>
</mat-button-toggle>
<mat-button-toggle value="right" [disabled]="isDisabled">
<mat-icon>format_align_right</mat-icon>
</mat-button-toggle>
<mat-button-toggle value="justify" [disabled]="isDisabled">
<mat-icon>format_align_justify</mat-icon>
</mat-button-toggle>
</mat-button-toggle-group>
</section>

Expand Down
13 changes: 2 additions & 11 deletions src/lib/button-toggle/_button-toggle-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,15 @@
@import '../core/theming/theming';
@import '../core/typography/typography-utils';

// Applies a focus style to an mat-button-toggle element for each of the supported palettes.
@mixin _mat-button-toggle-focus-color($theme) {
$background: map-get($theme, background);

.mat-button-toggle-focus-overlay {
background-color: mat-color($background, focused-button);
}
}

@mixin mat-button-toggle-theme($theme) {
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);

.mat-button-toggle {
color: mat-color($foreground, hint-text);

&.cdk-focused {
@include _mat-button-toggle-focus-color($theme);
.mat-button-toggle-focus-overlay {
background-color: mat-color($background, focused-button);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib/button-toggle/button-toggle-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

import {NgModule} from '@angular/core';
import {MatButtonToggleGroup, MatButtonToggleGroupMultiple, MatButtonToggle} from './button-toggle';
import {MatCommonModule} from '@angular/material/core';
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
import {UNIQUE_SELECTION_DISPATCHER_PROVIDER} from '@angular/cdk/collections';
import {A11yModule} from '@angular/cdk/a11y';


@NgModule({
imports: [MatCommonModule, A11yModule],
imports: [MatCommonModule, MatRippleModule, A11yModule],
exports: [
MatButtonToggleGroup,
MatButtonToggleGroupMultiple,
Expand Down
7 changes: 6 additions & 1 deletion src/lib/button-toggle/button-toggle.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<label [attr.for]="inputId" class="mat-button-toggle-label">
<label [attr.for]="inputId" class="mat-button-toggle-label" #label>
<input #input class="mat-button-toggle-input cdk-visually-hidden"
[type]="_type"
[id]="inputId"
Expand All @@ -15,3 +15,8 @@
</div>
</label>
<div class="mat-button-toggle-focus-overlay"></div>

<div class="mat-button-toggle-ripple" matRipple
[matRippleTrigger]="label"
[matRippleDisabled]="this.disableRipple || this.disabled">
</div>
17 changes: 14 additions & 3 deletions src/lib/button-toggle/button-toggle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ $mat-button-toggle-border-radius: 2px !default;
}
}


.mat-button-toggle-disabled .mat-button-toggle-label-content {
cursor: default;
}
Expand All @@ -39,8 +38,11 @@ $mat-button-toggle-border-radius: 2px !default;
white-space: nowrap;
position: relative;

&.cdk-keyboard-focused,
&.cdk-program-focused {
// Similar to components like the checkbox, slide-toggle and radio, we cannot show the focus
// overlay for `.cdk-program-focused` because mouse clicks on the <label> element would be always
// treated as programmatic focus.
// TODO(paul): support `program` as well. See https://github.com/angular/material2/issues/9889
&.cdk-keyboard-focused {
.mat-button-toggle-focus-overlay {
opacity: 1;
}
Expand Down Expand Up @@ -68,3 +70,12 @@ $mat-button-toggle-border-radius: 2px !default;
opacity: 0;
@include mat-fill;
}

.mat-button-toggle-ripple {
@include mat-fill;

// Disable pointer events for the ripple container, because the container will overlay the user
// content and we don't want to prevent mouse clicks that should toggle the state.
// Pointer events can be safely disabled because the ripple trigger element is the label element.
pointer-events: none;
}
31 changes: 29 additions & 2 deletions src/lib/button-toggle/button-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ComponentFixture,
TestBed,
} from '@angular/core/testing';
import {dispatchMouseEvent} from '@angular/cdk/testing';
import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
Expand Down Expand Up @@ -177,8 +178,32 @@ describe('MatButtonToggle with forms', () => {

expect(testComponent.modelValue).toBe('green');
}));
});

it('should show a ripple on label click', () => {
const groupElement = groupDebugElement.nativeElement;

expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0);

dispatchMouseEvent(buttonToggleLabels[0], 'mousedown');
dispatchMouseEvent(buttonToggleLabels[0], 'mouseup');

expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
});

it('should allow ripples to be disabled', () => {
const groupElement = groupDebugElement.nativeElement;

testComponent.disableRipple = true;
fixture.detectChanges();

expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0);

dispatchMouseEvent(buttonToggleLabels[0], 'mousedown');
dispatchMouseEvent(buttonToggleLabels[0], 'mouseup');

expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
});
});
});

describe('MatButtonToggle without forms', () => {
Expand Down Expand Up @@ -647,7 +672,8 @@ class ButtonTogglesInsideButtonToggleGroup {
@Component({
template: `
<mat-button-toggle-group [(ngModel)]="modelValue" (change)="lastEvent = $event">
<mat-button-toggle *ngFor="let option of options" [value]="option.value">
<mat-button-toggle *ngFor="let option of options" [value]="option.value"
[disableRipple]="disableRipple">
{{option.label}}
</mat-button-toggle>
</mat-button-toggle-group>
Expand All @@ -661,6 +687,7 @@ class ButtonToggleGroupWithNgModel {
{label: 'Blue', value: 'blue'},
];
lastEvent: MatButtonToggleChange;
disableRipple = false;
}

@Component({
Expand Down
18 changes: 16 additions & 2 deletions src/lib/button-toggle/button-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ import {
ViewEncapsulation,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {CanDisable, mixinDisabled} from '@angular/material/core';
import {
CanDisable,
CanDisableRipple,
mixinDisabled,
mixinDisableRipple
} from '@angular/material/core';

/** Acceptable types for a button toggle. */
export type ToggleType = 'checkbox' | 'radio';
Expand Down Expand Up @@ -225,6 +230,11 @@ export class MatButtonToggleGroupMultiple extends _MatButtonToggleGroupMixinBase
private _vertical: boolean = false;
}

// Boilerplate for applying mixins to the MatButtonToggle class.
/** @docs-private */
export class MatButtonToggleBase {}
export const _MatButtonToggleMixinBase = mixinDisableRipple(MatButtonToggleBase);

/** Single button inside of a toggle group. */
@Component({
moduleId: module.id,
Expand All @@ -235,6 +245,7 @@ export class MatButtonToggleGroupMultiple extends _MatButtonToggleGroupMixinBase
preserveWhitespaces: false,
exportAs: 'matButtonToggle',
changeDetection: ChangeDetectionStrategy.OnPush,
inputs: ['disableRipple'],
host: {
'[class.mat-button-toggle-standalone]': '!buttonToggleGroup && !buttonToggleGroupMultiple',
'[class.mat-button-toggle-checked]': 'checked',
Expand All @@ -243,7 +254,9 @@ export class MatButtonToggleGroupMultiple extends _MatButtonToggleGroupMixinBase
'[attr.id]': 'id',
}
})
export class MatButtonToggle implements OnInit, OnDestroy {
export class MatButtonToggle extends _MatButtonToggleMixinBase
implements OnInit, OnDestroy, CanDisableRipple {

/**
* Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will
* take precedence so this may be omitted.
Expand Down Expand Up @@ -331,6 +344,7 @@ export class MatButtonToggle implements OnInit, OnDestroy {
private _buttonToggleDispatcher: UniqueSelectionDispatcher,
private _elementRef: ElementRef,
private _focusMonitor: FocusMonitor) {
super();

this.buttonToggleGroup = toggleGroup;
this.buttonToggleGroupMultiple = toggleGroupMultiple;
Expand Down
1 change: 1 addition & 0 deletions src/lib/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ export class MatCheckbox extends _MatCheckboxMixinBase implements ControlValueAc

/** Function is called whenever the focus changes for the input element. */
private _onInputFocusChange(focusOrigin: FocusOrigin) {
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
if (!this._focusRipple && focusOrigin === 'keyboard') {
this._focusRipple = this.ripple.launch(0, 0, {persistent: true});
} else if (!focusOrigin) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/radio/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ export class MatRadioButton extends _MatRadioButtonMixinBase

/** Function is called whenever the focus changes for the input element. */
private _onInputFocusChange(focusOrigin: FocusOrigin) {
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
if (!this._focusRipple && focusOrigin === 'keyboard') {
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
} else if (!focusOrigin) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro

/** Function is called whenever the focus changes for the input element. */
private _onInputFocusChange(focusOrigin: FocusOrigin) {
// TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889
if (!this._focusRipple && focusOrigin === 'keyboard') {
// For keyboard focus show a persistent ripple as focus indicator.
this._focusRipple = this._ripple.launch(0, 0, {persistent: true});
Expand Down

0 comments on commit 53417d4

Please sign in to comment.