Skip to content

Commit

Permalink
fix(material/button-toggle): animate checkbox
Browse files Browse the repository at this point in the history
Currently the checkbox inside button toggle is a bit jarring. These changes add an animation to it.
  • Loading branch information
crisbeto committed Nov 15, 2024
1 parent f1c4173 commit bebc398
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 24 deletions.
24 changes: 10 additions & 14 deletions src/material/button-toggle/button-toggle.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,19 @@
[attr.aria-disabled]="disabled && disabledInteractive ? 'true' : null"
(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"
@if (buttonToggleGroup && (
!buttonToggleGroup.multiple && !buttonToggleGroup.hideSingleSelectionIndicator ||
buttonToggleGroup.multiple && !buttonToggleGroup.hideMultipleSelectionIndicator)
) {
<div
class="mat-button-toggle-checkbox-wrapper"
[class.mat-button-toggle-checkbox-wrapper-checked]="checked">
<mat-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>
appearance="minimal"/>
</div>
}
<ng-content></ng-content>
</span>
Expand Down
32 changes: 25 additions & 7 deletions src/material/button-toggle/button-toggle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

$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 @@ -104,13 +103,32 @@ $_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-checkbox-wrapper {
$checkmark-size: 18px !default;
$checkmark-padding: 12px !default;

display: inline-flex;
justify-content: flex-start;
align-items: center;
width: 0;
height: $checkmark-size;
overflow: hidden;
box-sizing: border-box;

&.mat-button-toggle-checkbox-wrapper-checked {
width: $checkmark-size + $checkmark-padding;
}

.mat-button-toggle-animations-enabled & {
transition: width 150ms 45ms cubic-bezier(0.4, 0, 0.2, 1);
}

// Disable the transition in vertical mode since it looks weird.
// There should be a limited amount of usages anyway.
.mat-button-toggle-vertical & {
transition: none;
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/material/button-toggle/button-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,9 @@ describe('MatButtonToggle without forms', () => {
buttonToggleLabelElements[0].click();
fixture.detectChanges();

expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(1);
expect(document.querySelectorAll('.mat-button-toggle-checkbox-wrapper-checked').length).toBe(
1,
);
});
});

Expand Down Expand Up @@ -763,7 +765,9 @@ describe('MatButtonToggle without forms', () => {
buttonToggleLabelElements[1].click();
fixture.detectChanges();

expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(2);
expect(document.querySelectorAll('.mat-button-toggle-checkbox-wrapper-checked').length).toBe(
2,
);
});
});

Expand Down
11 changes: 10 additions & 1 deletion src/material/button-toggle/button-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
booleanAttribute,
inject,
HostAttributeToken,
ANIMATION_MODULE_TYPE,
} from '@angular/core';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
Expand Down Expand Up @@ -560,7 +561,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
private _focusMonitor = inject(FocusMonitor);
private _idGenerator = inject(_IdGenerator);

private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true});
private _checked = false;

/**
Expand Down Expand Up @@ -699,6 +700,14 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
}

ngAfterViewInit() {
// This serves two purposes:
// 1. We don't want the animation to fire on the first render for pre-checked toggles so we
// delay adding the class until the view is rendered.
// 2. We don't want animation if the `NoopAnimationsModule` is provided.
if (this._animationMode !== 'NoopAnimations') {
this._elementRef.nativeElement.classList.add('mat-button-toggle-animations-enabled');
}

this._focusMonitor.monitor(this._elementRef, true);
}

Expand Down

0 comments on commit bebc398

Please # to comment.