Skip to content

Commit 1e754a0

Browse files
crisbetojelbourn
authored andcommitted
feat(snack-bar): align with 2018 material design spec (#12634)
Aligns the snack bar component with the latest Material Design spec.
1 parent f3af763 commit 1e754a0

File tree

7 files changed

+47
-89
lines changed

7 files changed

+47
-89
lines changed

src/lib/snack-bar/_snack-bar-theme.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
$accent: map-get($theme, accent);
77

88
.mat-snack-bar-container {
9+
// Use the primary text on the dark theme, even though the lighter one uses
10+
// a secondary, because the contrast on the light primary text is poor.
11+
color: if($is-dark-theme, $dark-primary-text, $light-secondary-text);
912
background: if($is-dark-theme, map-get($mat-grey, 50), #323232);
10-
color: if($is-dark-theme, $dark-primary-text, $light-primary-text);
1113
}
1214

1315
.mat-simple-snackbar-action {

src/lib/snack-bar/simple-snack-bar.scss

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ $mat-snack-bar-button-vertical-margin:
1313
.mat-simple-snackbar {
1414
display: flex;
1515
justify-content: space-between;
16+
align-items: center;
17+
height: 100%;
1618
line-height: $mat-snack-bar-line-height;
1719
opacity: 1;
1820
}
@@ -22,16 +24,17 @@ $mat-snack-bar-button-vertical-margin:
2224
flex-direction: column;
2325
flex-shrink: 0;
2426
justify-content: space-around;
25-
margin: $mat-snack-bar-button-vertical-margin 0
27+
margin: $mat-snack-bar-button-vertical-margin $mat-snack-bar-button-horizontal-margin * -1
2628
$mat-snack-bar-button-vertical-margin $mat-snack-bar-button-horizontal-margin;
2729

2830
button {
2931
flex: 1;
3032
max-height: $mat-snack-bar-button-height;
33+
min-width: 0;
3134
}
3235

3336
[dir='rtl'] & {
34-
margin-left: 0;
37+
margin-left: -$mat-snack-bar-button-horizontal-margin;
3538
margin-right: $mat-snack-bar-button-horizontal-margin;
3639
}
3740
}

src/lib/snack-bar/simple-snack-bar.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import {Component, ViewEncapsulation, Inject, ChangeDetectionStrategy} from '@angular/core';
1010
import {MatSnackBarRef} from './snack-bar-ref';
1111
import {MAT_SNACK_BAR_DATA} from './snack-bar-config';
12-
import {matSnackBarAnimations} from './snack-bar-animations';
1312

1413

1514
/**
@@ -23,15 +22,13 @@ import {matSnackBarAnimations} from './snack-bar-animations';
2322
styleUrls: ['simple-snack-bar.css'],
2423
encapsulation: ViewEncapsulation.None,
2524
changeDetection: ChangeDetectionStrategy.OnPush,
26-
animations: [matSnackBarAnimations.contentFade],
2725
host: {
28-
'[@contentFade]': '',
2926
'class': 'mat-simple-snackbar',
3027
}
3128
})
3229
export class SimpleSnackBar {
3330
/** Data that was injected into the snack bar. */
34-
data: { message: string, action: string };
31+
data: {message: string, action: string};
3532

3633
constructor(
3734
public snackBarRef: MatSnackBarRef<SimpleSnackBar>,

src/lib/snack-bar/snack-bar-animations.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,24 @@ import {
1313
trigger,
1414
AnimationTriggerMetadata,
1515
} from '@angular/animations';
16-
import {AnimationCurves, AnimationDurations} from '@angular/material/core';
1716

1817
/** Animations used by the Material snack bar. */
1918
export const matSnackBarAnimations: {
20-
readonly contentFade: AnimationTriggerMetadata;
2119
readonly snackBarState: AnimationTriggerMetadata;
2220
} = {
23-
/** Animation that slides the dialog in and out of view and fades the opacity. */
24-
contentFade: trigger('contentFade', [
25-
transition(':enter', [
26-
style({opacity: '0'}),
27-
animate(`${AnimationDurations.COMPLEX} ${AnimationCurves.STANDARD_CURVE}`)
28-
])
29-
]),
30-
3121
/** Animation that shows and hides a snack bar. */
3222
snackBarState: trigger('state', [
33-
state('visible-top, visible-bottom', style({transform: 'translateY(0%)'})),
34-
transition('visible-top => hidden-top, visible-bottom => hidden-bottom',
35-
animate(`${AnimationDurations.EXITING} ${AnimationCurves.ACCELERATION_CURVE}`)),
36-
transition('void => visible-top, void => visible-bottom',
37-
animate(`${AnimationDurations.ENTERING} ${AnimationCurves.DECELERATION_CURVE}`)),
23+
state('void, hidden', style({
24+
transform: 'scale(0.8)',
25+
opacity: 0,
26+
})),
27+
state('visible', style({
28+
transform: 'scale(1)',
29+
opacity: 1,
30+
})),
31+
transition('* => visible', animate('150ms cubic-bezier(0, 0, 0.2, 1)')),
32+
transition('* => void, * => hidden', animate('75ms cubic-bezier(0.4, 0.0, 1, 1)', style({
33+
opacity: 0
34+
}))),
3835
])
3936
};
Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,25 @@
11
@import '../../cdk/a11y/a11y';
2+
@import '../core/style/elevation';
23

3-
$mat-snack-bar-padding: 14px 24px !default;
4-
$mat-snack-bar-min-width: 288px !default;
5-
$mat-snack-bar-max-width: 568px !default;
4+
$mat-snack-bar-padding: 14px 16px !default;
5+
$mat-snack-bar-min-height: 48px !default;
6+
$mat-snack-bar-min-width: 344px !default;
7+
$mat-snack-bar-max-width: 33vw !default;
68
$mat-snack-bar-spacing-margin: 24px !default;
9+
$mat-snack-bar-spacing-margin-handset: 8px !default;
710

811

912
.mat-snack-bar-container {
10-
border-radius: 2px;
13+
@include mat-elevation(6);
14+
border-radius: 4px;
1115
box-sizing: border-box;
1216
display: block;
1317
margin: $mat-snack-bar-spacing-margin;
1418
max-width: $mat-snack-bar-max-width;
1519
min-width: $mat-snack-bar-min-width;
1620
padding: $mat-snack-bar-padding;
17-
// Initial transformation is applied to start snack bar out of view, below its target position.
18-
// Note: it's preferred to use a series of transforms, instead of something like `calc()`, because
19-
// IE won't animate transforms that contain a `calc`.
20-
transform: translateY(100%) translateY($mat-snack-bar-spacing-margin);
21-
22-
/**
23-
* Removes margin of snack bars which are center positioned horizontally. This
24-
* is done to align snack bars to the edge of the view vertically to match spec.
25-
*/
26-
&.mat-snack-bar-center {
27-
margin: 0;
28-
transform: translateY(100%);
29-
}
30-
31-
/**
32-
* To allow for animations from a 'top' vertical position to animate in a downward
33-
* direction, set the translation to start the snack bar above the target position.
34-
*/
35-
&.mat-snack-bar-top {
36-
transform: translateY(-100%) translateY(#{-$mat-snack-bar-spacing-margin});
37-
38-
&.mat-snack-bar-center {
39-
transform: translateY(-100%);
40-
}
41-
}
21+
min-height: $mat-snack-bar-min-height;
22+
transform-origin: center;
4223

4324
@include cdk-high-contrast {
4425
border: solid 1px;
@@ -53,8 +34,8 @@ $mat-snack-bar-spacing-margin: 24px !default;
5334
width: 100%;
5435

5536
.mat-snack-bar-container {
56-
margin: 0;
57-
max-width: inherit;
37+
margin: $mat-snack-bar-spacing-margin-handset;
38+
max-width: 100%;
5839
width: 100%;
5940
}
6041
}

src/lib/snack-bar/snack-bar-container.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,11 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
9494
onAnimationEnd(event: AnimationEvent) {
9595
const {fromState, toState} = event;
9696

97-
if ((toState === 'void' && fromState !== 'void') || toState.startsWith('hidden')) {
97+
if ((toState === 'void' && fromState !== 'void') || toState === 'hidden') {
9898
this._completeExit();
9999
}
100100

101-
if (toState.startsWith('visible')) {
101+
if (toState === 'visible') {
102102
// Note: we shouldn't use `this` inside the zone callback,
103103
// because it can cause a memory leak.
104104
const onEnter = this._onEnter;
@@ -113,14 +113,17 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
113113
/** Begin animation of snack bar entrance into view. */
114114
enter(): void {
115115
if (!this._destroyed) {
116-
this._animationState = `visible-${this.snackBarConfig.verticalPosition}`;
116+
this._animationState = 'visible';
117117
this._changeDetectorRef.detectChanges();
118118
}
119119
}
120120

121121
/** Begin animation of the snack bar exiting from view. */
122122
exit(): Observable<void> {
123-
this._animationState = `hidden-${this.snackBarConfig.verticalPosition}`;
123+
// Note: this one transitions to `hidden`, rather than `void`, in order to handle the case
124+
// where multiple snack bars are opened in quick succession (e.g. two consecutive calls to
125+
// `MatSnackBar.open`).
126+
this._animationState = 'hidden';
124127
return this._onExit;
125128
}
126129

src/lib/snack-bar/snack-bar.spec.ts

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,12 @@ describe('MatSnackBar', () => {
214214

215215
viewContainerFixture.detectChanges();
216216
expect(snackBarRef.containerInstance._animationState)
217-
.toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
217+
.toBe('visible', `Expected the animation state would be 'visible'.`);
218218
snackBarRef.dismiss();
219219

220220
viewContainerFixture.detectChanges();
221221
expect(snackBarRef.containerInstance._animationState)
222-
.toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
222+
.toBe('hidden', `Expected the animation state would be 'hidden'.`);
223223
});
224224

225225
it('should set the animation state to complete on exit', () => {
@@ -229,7 +229,7 @@ describe('MatSnackBar', () => {
229229

230230
viewContainerFixture.detectChanges();
231231
expect(snackBarRef.containerInstance._animationState)
232-
.toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
232+
.toBe('hidden', `Expected the animation state would be 'hidden'.`);
233233
});
234234

235235
it(`should set the old snack bar animation state to complete and the new snack bar animation
@@ -240,7 +240,7 @@ describe('MatSnackBar', () => {
240240

241241
viewContainerFixture.detectChanges();
242242
expect(snackBarRef.containerInstance._animationState)
243-
.toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
243+
.toBe('visible', `Expected the animation state would be 'visible'.`);
244244

245245
let config2 = {viewContainerRef: testViewContainerRef};
246246
let snackBarRef2 = snackBar.open(simpleMessage, undefined, config2);
@@ -251,9 +251,9 @@ describe('MatSnackBar', () => {
251251

252252
expect(dismissCompleteSpy).toHaveBeenCalled();
253253
expect(snackBarRef.containerInstance._animationState)
254-
.toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
254+
.toBe('hidden', `Expected the animation state would be 'hidden'.`);
255255
expect(snackBarRef2.containerInstance._animationState)
256-
.toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
256+
.toBe('visible', `Expected the animation state would be 'visible'.`);
257257
}));
258258

259259
it('should open a new snackbar after dismissing a previous snackbar', fakeAsync(() => {
@@ -273,7 +273,7 @@ describe('MatSnackBar', () => {
273273
// Wait for the snackbar open animation to finish.
274274
flush();
275275
expect(snackBarRef.containerInstance._animationState)
276-
.toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
276+
.toBe('visible', `Expected the animation state would be 'visible'.`);
277277
}));
278278

279279
it('should remove past snackbars when opening new snackbars', fakeAsync(() => {
@@ -452,31 +452,6 @@ describe('MatSnackBar', () => {
452452
.toContain('custom-class', 'Expected class applied through the defaults to be applied.');
453453
}));
454454

455-
it('should position the snack bar correctly if no default position is defined', fakeAsync(() => {
456-
overlayContainer.ngOnDestroy();
457-
viewContainerFixture.destroy();
458-
459-
TestBed
460-
.resetTestingModule()
461-
.overrideProvider(MAT_SNACK_BAR_DEFAULT_OPTIONS, {
462-
deps: [],
463-
useFactory: () => ({politeness: 'polite'})
464-
})
465-
.configureTestingModule({imports: [MatSnackBarModule, NoopAnimationsModule]})
466-
.compileComponents();
467-
468-
inject([MatSnackBar, OverlayContainer], (sb: MatSnackBar, oc: OverlayContainer) => {
469-
snackBar = sb;
470-
overlayContainer = oc;
471-
overlayContainerElement = oc.getContainerElement();
472-
})();
473-
474-
const snackBarRef = snackBar.open(simpleMessage);
475-
flush();
476-
477-
expect(snackBarRef.containerInstance._animationState).toBe('visible-bottom');
478-
}));
479-
480455
describe('with custom component', () => {
481456
it('should open a custom component', () => {
482457
const snackBarRef = snackBar.openFromComponent(BurritosNotification);

0 commit comments

Comments
 (0)