Skip to content

Commit

Permalink
fix(material/button-toggle): Add checkmark indicators with hideSingle…
Browse files Browse the repository at this point in the history
…SelectionIndicator and hideMultipleSelectionIndicator input and config options
  • Loading branch information
amysorto committed Feb 7, 2024
1 parent af7cd70 commit 75a77e0
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 8 deletions.
20 changes: 14 additions & 6 deletions src/dev-app/button-toggle/button-toggle-demo.html
Expand Up @@ -6,10 +6,18 @@
<mat-checkbox (change)="isDisabled = $event.checked">Disable Button Toggle Items</mat-checkbox>
</p>

<p>
<mat-checkbox (change)="hideSingleSelectionIndicator = $event.checked">Hide Single Selection Indicator</mat-checkbox>
</p>

<p>
<mat-checkbox (change)="hideMultipleSelectionIndicator = $event.checked">Hide Multiple Selection Indicator</mat-checkbox>
</p>

<h1>Exclusive Selection</h1>

<section>
<mat-button-toggle-group name="alignment" [vertical]="isVertical">
<mat-button-toggle-group name="alignment" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
<mat-button-toggle value="left" [disabled]="isDisabled">
<mat-icon>format_align_left</mat-icon>
</mat-button-toggle>
Expand All @@ -26,7 +34,7 @@ <h1>Exclusive Selection</h1>
</section>

<section>
<mat-button-toggle-group appearance="legacy" name="alignment" [vertical]="isVertical">
<mat-button-toggle-group appearance="legacy" name="alignment" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
<mat-button-toggle value="left" [disabled]="isDisabled">
<mat-icon>format_align_left</mat-icon>
</mat-button-toggle>
Expand All @@ -45,7 +53,7 @@ <h1>Exclusive Selection</h1>
<h1>Disabled Group</h1>

<section>
<mat-button-toggle-group name="checkbox" [vertical]="isVertical" [disabled]="isDisabled">
<mat-button-toggle-group name="checkbox" [vertical]="isVertical" [disabled]="isDisabled" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
<mat-button-toggle value="bold">
<mat-icon>format_bold</mat-icon>
</mat-button-toggle>
Expand All @@ -60,15 +68,15 @@ <h1>Disabled Group</h1>

<h1>Multiple Selection</h1>
<section>
<mat-button-toggle-group multiple [vertical]="isVertical">
<mat-button-toggle-group multiple [vertical]="isVertical" [hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator">
<mat-button-toggle>Flour</mat-button-toggle>
<mat-button-toggle>Eggs</mat-button-toggle>
<mat-button-toggle>Sugar</mat-button-toggle>
<mat-button-toggle [disabled]="isDisabled">Milk</mat-button-toggle>
</mat-button-toggle-group>
</section>
<section>
<mat-button-toggle-group appearance="legacy" multiple [vertical]="isVertical">
<mat-button-toggle-group appearance="legacy" multiple [vertical]="isVertical" [hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator">
<mat-button-toggle>Flour</mat-button-toggle>
<mat-button-toggle>Eggs</mat-button-toggle>
<mat-button-toggle>Sugar</mat-button-toggle>
Expand All @@ -82,7 +90,7 @@ <h1>Single Toggle</h1>

<h1>Dynamic Exclusive Selection</h1>
<section>
<mat-button-toggle-group name="pies" [(ngModel)]="favoritePie" [vertical]="isVertical">
<mat-button-toggle-group name="pies" [(ngModel)]="favoritePie" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
@for (pie of pieOptions; track pie) {
<mat-button-toggle [value]="pie">{{pie}}</mat-button-toggle>
}
Expand Down
2 changes: 2 additions & 0 deletions src/dev-app/button-toggle/button-toggle-demo.ts
Expand Up @@ -23,6 +23,8 @@ import {MatIconModule} from '@angular/material/icon';
export class ButtonToggleDemo {
isVertical = false;
isDisabled = false;
hideSingleSelectionIndicator = false;
hideMultipleSelectionIndicator = false;
favoritePie = 'Apple';
pieOptions = ['Apple', 'Cherry', 'Pecan', 'Lemon'];
}
18 changes: 18 additions & 0 deletions src/material/button-toggle/button-toggle.html
Expand Up @@ -9,6 +9,24 @@
[attr.aria-labelledby]="ariaLabelledby"
(click)="_onButtonClick()">
<span class="mat-button-toggle-label-content">
<!-- Render checkmark at the beginning for single-selection. -->
@if (buttonToggleGroup && checked && !buttonToggleGroup.multiple && !buttonToggleGroup.hideSingleSelectionIndicator) {
<mat-pseudo-checkbox
class="mat-mdc-option-pseudo-checkbox"
[disabled]="disabled"
state="checked"
aria-hidden="true"
appearance="minimal"></mat-pseudo-checkbox>
}
<!-- Render checkmark at the beginning for multiple-selection. -->
@if (buttonToggleGroup && checked && buttonToggleGroup.multiple && !buttonToggleGroup.hideMultipleSelectionIndicator) {
<mat-pseudo-checkbox
class="mat-mdc-option-pseudo-checkbox"
[disabled]="disabled"
state="checked"
aria-hidden="true"
appearance="minimal"></mat-pseudo-checkbox>
}
<ng-content></ng-content>
</span>
</button>
Expand Down
9 changes: 9 additions & 0 deletions src/material/button-toggle/button-toggle.scss
Expand Up @@ -9,6 +9,7 @@

$standard-padding: 0 12px !default;
$legacy-padding: 0 16px !default;
$checkmark-padding: 12px !default;

// TODO(crisbeto): these variables aren't used anymore and should be removed.
$legacy-height: 36px !default;
Expand Down Expand Up @@ -90,6 +91,14 @@ $_standard-tokens: (
.mat-icon svg {
vertical-align: top;
}

.mat-pseudo-checkbox {
margin-right: $checkmark-padding;
[dir='rtl'] & {
margin-right: 0;
margin-left: $checkmark-padding;
}
}
}

.mat-button-toggle-checked {
Expand Down
62 changes: 62 additions & 0 deletions src/material/button-toggle/button-toggle.spec.ts
Expand Up @@ -5,6 +5,7 @@ import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/t
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {
MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS,
MatButtonToggle,
MatButtonToggleChange,
MatButtonToggleGroup,
Expand Down Expand Up @@ -546,6 +547,13 @@ describe('MatButtonToggle without forms', () => {
expect(groupInstance.value).toBeFalsy();
expect(groupInstance.selected).toBeFalsy();
}));

it('should show checkmark indicator by default', () => {
buttonToggleLabelElements[0].click();
fixture.detectChanges();

expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(1);
});
});

describe('with initial value and change event', () => {
Expand Down Expand Up @@ -701,6 +709,14 @@ describe('MatButtonToggle without forms', () => {
groupInstance.value = 'not-an-array';
}).toThrowError(/Value must be an array/);
});

it('should show checkmark indicator by default', () => {
buttonToggleLabelElements[0].click();
buttonToggleLabelElements[1].click();
fixture.detectChanges();

expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(2);
});
});

describe('as standalone', () => {
Expand Down Expand Up @@ -876,6 +892,52 @@ describe('MatButtonToggle without forms', () => {
});
});

describe('with tokens to hide checkmark selection indicators', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
MatButtonToggleModule,
ButtonTogglesInsideButtonToggleGroup,
ButtonTogglesInsideButtonToggleGroupMultiple,
],
providers: [
{
provide: MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS,
useValue: {
hideSingleSelectionIndicator: true,
hideMultipleSelectionIndicator: true,
},
},
],
});

TestBed.compileComponents();
});

it('should hide checkmark indicator for single selection', () => {
const fixture = TestBed.createComponent(ButtonTogglesInsideButtonToggleGroup);
fixture.detectChanges();

fixture.debugElement.query(By.css('button')).nativeElement.click();
fixture.detectChanges();

expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(0);
});

it('should hide checkmark indicator for multiple selection', () => {
const fixture = TestBed.createComponent(ButtonTogglesInsideButtonToggleGroupMultiple);
fixture.detectChanges();

// Check all button toggles in the group
fixture.debugElement
.queryAll(By.css('button'))
.forEach(toggleButton => toggleButton.nativeElement.click());
fixture.detectChanges();

expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(0);
});
});

it('should not throw on init when toggles are repeated and there is an initial value', () => {
const fixture = TestBed.createComponent(RepeatedButtonTogglesWithPreselectedValue);

Expand Down
43 changes: 41 additions & 2 deletions src/material/button-toggle/button-toggle.ts
Expand Up @@ -33,7 +33,7 @@ import {
booleanAttribute,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {MatRipple} from '@angular/material/core';
import {MatRipple, MatPseudoCheckbox} from '@angular/material/core';

/**
* @deprecated No longer used.
Expand All @@ -54,6 +54,10 @@ export interface MatButtonToggleDefaultOptions {
* setting an appearance on a button toggle or group.
*/
appearance?: MatButtonToggleAppearance;
/** Whetehr icon indicators should be hidden for single-selection button toggle groups. */
hideSingleSelectionIndicator?: boolean;
/** Whether icon indicators should be hidden for multiple-selection button toggle groups. */
hideMultipleSelectionIndicator?: boolean;
}

/**
Expand All @@ -62,8 +66,19 @@ export interface MatButtonToggleDefaultOptions {
*/
export const MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS = new InjectionToken<MatButtonToggleDefaultOptions>(
'MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS',
{
providedIn: 'root',
factory: MAT_BUTTON_TOGGLE_GROUP_DEFAULT_OPTIONS_FACTORY,
},
);

export function MAT_BUTTON_TOGGLE_GROUP_DEFAULT_OPTIONS_FACTORY(): MatButtonToggleDefaultOptions {
return {
hideSingleSelectionIndicator: false,
hideMultipleSelectionIndicator: false,
};
}

/**
* Injection token that can be used to reference instances of `MatButtonToggleGroup`.
* It serves as alternative token to the actual `MatButtonToggleGroup` class which
Expand Down Expand Up @@ -215,6 +230,28 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
@Output() readonly change: EventEmitter<MatButtonToggleChange> =
new EventEmitter<MatButtonToggleChange>();

/** Whether checkmark indicator for single-selection button toggle groups is hidden. */
@Input({transform: booleanAttribute})
get hideSingleSelectionIndicator(): boolean {
return this._hideSingleSelectionIndicator;
}
set hideSingleSelectionIndicator(value: boolean) {
this._hideSingleSelectionIndicator = value;
this._markButtonsForCheck();
}
private _hideSingleSelectionIndicator: boolean;

/** Whether checkmark indicator for multiple-selection button toggle groups is hidden. */
@Input({transform: booleanAttribute})
get hideMultipleSelectionIndicator(): boolean {
return this._hideMultipleSelectionIndicator;
}
set hideMultipleSelectionIndicator(value: boolean) {
this._hideMultipleSelectionIndicator = value;
this._markButtonsForCheck();
}
private _hideMultipleSelectionIndicator: boolean;

constructor(
private _changeDetector: ChangeDetectorRef,
@Optional()
Expand All @@ -223,6 +260,8 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
) {
this.appearance =
defaultOptions && defaultOptions.appearance ? defaultOptions.appearance : 'standard';
this.hideSingleSelectionIndicator = defaultOptions?.hideSingleSelectionIndicator ?? false;
this.hideMultipleSelectionIndicator = defaultOptions?.hideMultipleSelectionIndicator ?? false;
}

ngOnInit() {
Expand Down Expand Up @@ -401,7 +440,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
'role': 'presentation',
},
standalone: true,
imports: [MatRipple],
imports: [MatRipple, MatPseudoCheckbox],
})
export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
private _checked = false;
Expand Down

0 comments on commit 75a77e0

Please sign in to comment.