Skip to content

Commit

Permalink
feat(snack-bar): align with 2018 material design spec
Browse files Browse the repository at this point in the history
Aligns the snack bar component with the latest Material Design spec.
  • Loading branch information
crisbeto committed Aug 18, 2018
1 parent fceb7af commit 4e9c779
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 89 deletions.
4 changes: 3 additions & 1 deletion src/lib/snack-bar/_snack-bar-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
$accent: map-get($theme, accent);

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

.mat-simple-snackbar-action {
Expand Down
7 changes: 5 additions & 2 deletions src/lib/snack-bar/simple-snack-bar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ $mat-snack-bar-button-vertical-margin:
.mat-simple-snackbar {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
line-height: $mat-snack-bar-line-height;
opacity: 1;
}
Expand All @@ -22,16 +24,17 @@ $mat-snack-bar-button-vertical-margin:
flex-direction: column;
flex-shrink: 0;
justify-content: space-around;
margin: $mat-snack-bar-button-vertical-margin 0
margin: $mat-snack-bar-button-vertical-margin $mat-snack-bar-button-horizontal-margin * -1
$mat-snack-bar-button-vertical-margin $mat-snack-bar-button-horizontal-margin;

button {
flex: 1;
max-height: $mat-snack-bar-button-height;
min-width: 0;
}

[dir='rtl'] & {
margin-left: 0;
margin-left: -$mat-snack-bar-button-horizontal-margin;
margin-right: $mat-snack-bar-button-horizontal-margin;
}
}
5 changes: 1 addition & 4 deletions src/lib/snack-bar/simple-snack-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import {Component, ViewEncapsulation, Inject, ChangeDetectionStrategy} from '@angular/core';
import {MatSnackBarRef} from './snack-bar-ref';
import {MAT_SNACK_BAR_DATA} from './snack-bar-config';
import {matSnackBarAnimations} from './snack-bar-animations';


/**
Expand All @@ -23,15 +22,13 @@ import {matSnackBarAnimations} from './snack-bar-animations';
styleUrls: ['simple-snack-bar.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [matSnackBarAnimations.contentFade],
host: {
'[@contentFade]': '',
'class': 'mat-simple-snackbar',
}
})
export class SimpleSnackBar {
/** Data that was injected into the snack bar. */
data: { message: string, action: string };
data: {message: string, action: string};

constructor(
public snackBarRef: MatSnackBarRef<SimpleSnackBar>,
Expand Down
25 changes: 10 additions & 15 deletions src/lib/snack-bar/snack-bar-animations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,22 @@ import {
trigger,
AnimationTriggerMetadata,
} from '@angular/animations';
import {AnimationCurves, AnimationDurations} from '@angular/material/core';

/** Animations used by the Material snack bar. */
export const matSnackBarAnimations: {
readonly contentFade: AnimationTriggerMetadata;
readonly snackBarState: AnimationTriggerMetadata;
} = {
/** Animation that slides the dialog in and out of view and fades the opacity. */
contentFade: trigger('contentFade', [
transition(':enter', [
style({opacity: '0'}),
animate(`${AnimationDurations.COMPLEX} ${AnimationCurves.STANDARD_CURVE}`)
])
]),

/** Animation that shows and hides a snack bar. */
snackBarState: trigger('state', [
state('visible-top, visible-bottom', style({transform: 'translateY(0%)'})),
transition('visible-top => hidden-top, visible-bottom => hidden-bottom',
animate(`${AnimationDurations.EXITING} ${AnimationCurves.ACCELERATION_CURVE}`)),
transition('void => visible-top, void => visible-bottom',
animate(`${AnimationDurations.ENTERING} ${AnimationCurves.DECELERATION_CURVE}`)),
state('void', style({
transform: 'scale(0.8)',
opacity: 0,
})),
state('visible', style({
transform: 'scale(1)',
opacity: 1,
})),
transition('* => visible', animate('150ms cubic-bezier(0, 0, 0.2, 1)')),
transition('* => void', animate('75ms cubic-bezier(0.4, 0.0, 1, 1)', style({opacity: 0}))),
])
};
43 changes: 12 additions & 31 deletions src/lib/snack-bar/snack-bar-container.scss
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
@import '../../cdk/a11y/a11y';
@import '../core/style/elevation';

$mat-snack-bar-padding: 14px 24px !default;
$mat-snack-bar-min-width: 288px !default;
$mat-snack-bar-max-width: 568px !default;
$mat-snack-bar-padding: 14px 16px !default;
$mat-snack-bar-min-height: 48px !default;
$mat-snack-bar-min-width: 344px !default;
$mat-snack-bar-max-width: 33vw !default;
$mat-snack-bar-spacing-margin: 24px !default;
$mat-snack-bar-spacing-margin-handset: 8px !default;


.mat-snack-bar-container {
border-radius: 2px;
@include mat-elevation(6);
border-radius: 4px;
box-sizing: border-box;
display: block;
margin: $mat-snack-bar-spacing-margin;
max-width: $mat-snack-bar-max-width;
min-width: $mat-snack-bar-min-width;
padding: $mat-snack-bar-padding;
// Initial transformation is applied to start snack bar out of view, below its target position.
// Note: it's preferred to use a series of transforms, instead of something like `calc()`, because
// IE won't animate transforms that contain a `calc`.
transform: translateY(100%) translateY($mat-snack-bar-spacing-margin);

/**
* Removes margin of snack bars which are center positioned horizontally. This
* is done to align snack bars to the edge of the view vertically to match spec.
*/
&.mat-snack-bar-center {
margin: 0;
transform: translateY(100%);
}

/**
* To allow for animations from a 'top' vertical position to animate in a downward
* direction, set the translation to start the snack bar above the target position.
*/
&.mat-snack-bar-top {
transform: translateY(-100%) translateY(#{-$mat-snack-bar-spacing-margin});

&.mat-snack-bar-center {
transform: translateY(-100%);
}
}
min-height: $mat-snack-bar-min-height;
transform-origin: center;

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

.mat-snack-bar-container {
margin: 0;
max-width: inherit;
margin: $mat-snack-bar-spacing-margin-handset;
max-width: 100%;
width: 100%;
}
}
8 changes: 4 additions & 4 deletions src/lib/snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
onAnimationEnd(event: AnimationEvent) {
const {fromState, toState} = event;

if ((toState === 'void' && fromState !== 'void') || toState.startsWith('hidden')) {
if (toState === 'void' && fromState !== 'void') {
this._completeExit();
}

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

/** Begin animation of the snack bar exiting from view. */
exit(): Observable<void> {
this._animationState = `hidden-${this.snackBarConfig.verticalPosition}`;
this._animationState = 'void';
return this._onExit;
}

Expand Down
39 changes: 7 additions & 32 deletions src/lib/snack-bar/snack-bar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,12 @@ describe('MatSnackBar', () => {

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

viewContainerFixture.detectChanges();
expect(snackBarRef.containerInstance._animationState)
.toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
.toBe('void', `Expected the animation state would be 'void'.`);
});

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

viewContainerFixture.detectChanges();
expect(snackBarRef.containerInstance._animationState)
.toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
.toBe('void', `Expected the animation state would be 'void'.`);
});

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

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

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

expect(dismissCompleteSpy).toHaveBeenCalled();
expect(snackBarRef.containerInstance._animationState)
.toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
.toBe('void', `Expected the animation state would be 'void'.`);
expect(snackBarRef2.containerInstance._animationState)
.toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
.toBe('visible', `Expected the animation state would be 'visible'.`);
}));

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

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

it('should position the snack bar correctly if no default position is defined', fakeAsync(() => {
overlayContainer.ngOnDestroy();
viewContainerFixture.destroy();

TestBed
.resetTestingModule()
.overrideProvider(MAT_SNACK_BAR_DEFAULT_OPTIONS, {
deps: [],
useFactory: () => ({politeness: 'polite'})
})
.configureTestingModule({imports: [MatSnackBarModule, NoopAnimationsModule]})
.compileComponents();

inject([MatSnackBar, OverlayContainer], (sb: MatSnackBar, oc: OverlayContainer) => {
snackBar = sb;
overlayContainer = oc;
overlayContainerElement = oc.getContainerElement();
})();

const snackBarRef = snackBar.open(simpleMessage);
flush();

expect(snackBarRef.containerInstance._animationState).toBe('visible-bottom');
}));

describe('with custom component', () => {
it('should open a custom component', () => {
const snackBarRef = snackBar.openFromComponent(BurritosNotification);
Expand Down

0 comments on commit 4e9c779

Please sign in to comment.