From 7d08aff4b87a6dca7bccae334504d69927a3212c Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 23 Aug 2018 20:28:43 +0200 Subject: [PATCH] feat(snack-bar): align with 2018 material design spec Aligns the snack bar component with the latest Material Design spec. --- src/lib/snack-bar/_snack-bar-theme.scss | 4 +- src/lib/snack-bar/simple-snack-bar.scss | 7 +++- src/lib/snack-bar/simple-snack-bar.ts | 5 +-- src/lib/snack-bar/snack-bar-animations.ts | 27 ++++++-------- src/lib/snack-bar/snack-bar-container.scss | 43 ++++++---------------- src/lib/snack-bar/snack-bar-container.ts | 11 ++++-- src/lib/snack-bar/snack-bar.spec.ts | 39 ++++---------------- 7 files changed, 47 insertions(+), 89 deletions(-) diff --git a/src/lib/snack-bar/_snack-bar-theme.scss b/src/lib/snack-bar/_snack-bar-theme.scss index f9eb6b5bc983..43b743f427e1 100644 --- a/src/lib/snack-bar/_snack-bar-theme.scss +++ b/src/lib/snack-bar/_snack-bar-theme.scss @@ -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 { diff --git a/src/lib/snack-bar/simple-snack-bar.scss b/src/lib/snack-bar/simple-snack-bar.scss index 2f8d1a7011a4..83da6664f8b3 100644 --- a/src/lib/snack-bar/simple-snack-bar.scss +++ b/src/lib/snack-bar/simple-snack-bar.scss @@ -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; } @@ -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; } } diff --git a/src/lib/snack-bar/simple-snack-bar.ts b/src/lib/snack-bar/simple-snack-bar.ts index 0bfa2039bb62..327a2bb943f6 100644 --- a/src/lib/snack-bar/simple-snack-bar.ts +++ b/src/lib/snack-bar/simple-snack-bar.ts @@ -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'; /** @@ -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, diff --git a/src/lib/snack-bar/snack-bar-animations.ts b/src/lib/snack-bar/snack-bar-animations.ts index 3cc81bf03d58..af9404497d47 100644 --- a/src/lib/snack-bar/snack-bar-animations.ts +++ b/src/lib/snack-bar/snack-bar-animations.ts @@ -13,27 +13,24 @@ 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, hidden', 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, * => hidden', animate('75ms cubic-bezier(0.4, 0.0, 1, 1)', style({ + opacity: 0 + }))), ]) }; diff --git a/src/lib/snack-bar/snack-bar-container.scss b/src/lib/snack-bar/snack-bar-container.scss index 7a58a63c62dd..a6a101bcfe20 100644 --- a/src/lib/snack-bar/snack-bar-container.scss +++ b/src/lib/snack-bar/snack-bar-container.scss @@ -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; @@ -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%; } } diff --git a/src/lib/snack-bar/snack-bar-container.ts b/src/lib/snack-bar/snack-bar-container.ts index 1a1eec18c915..208e47c48807 100644 --- a/src/lib/snack-bar/snack-bar-container.ts +++ b/src/lib/snack-bar/snack-bar-container.ts @@ -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') || toState === 'hidden') { 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; @@ -113,14 +113,17 @@ 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 { - this._animationState = `hidden-${this.snackBarConfig.verticalPosition}`; + // Note: this one transitions to `hidden`, rather than `void`, in order to handle the case + // where multiple snack bars are opened in quick succession (e.g. two consecutive calls to + // `MatSnackBar.open`). + this._animationState = 'hidden'; return this._onExit; } diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts index bd676794f07f..80ccca669d85 100644 --- a/src/lib/snack-bar/snack-bar.spec.ts +++ b/src/lib/snack-bar/snack-bar.spec.ts @@ -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('hidden', `Expected the animation state would be 'hidden'.`); }); it('should set the animation state to complete on exit', () => { @@ -229,7 +229,7 @@ describe('MatSnackBar', () => { viewContainerFixture.detectChanges(); expect(snackBarRef.containerInstance._animationState) - .toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`); + .toBe('hidden', `Expected the animation state would be 'hidden'.`); }); it(`should set the old snack bar animation state to complete and the new snack bar animation @@ -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); @@ -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('hidden', `Expected the animation state would be 'hidden'.`); 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(() => { @@ -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(() => { @@ -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);