Skip to content

Commit

Permalink
Allow snackbar position on the screen.
Browse files Browse the repository at this point in the history
  • Loading branch information
josephperrott committed Sep 1, 2017
1 parent 1b6b270 commit 5f96375
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 41 deletions.
15 changes: 14 additions & 1 deletion src/demo-app/snack-bar/snack-bar-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ <h1>SnackBar demo</h1>
<div>
Message: <md-form-field><input mdInput type="text" [(ngModel)]="message"></md-form-field>
</div>
<div>
<div>Position in page: </div>
<md-select [(ngModel)]="horizontalPosition">
<md-option value="start">Start</md-option>
<md-option value="end">End</md-option>
<md-option value="left">Left</md-option>
<md-option value="right">Right</md-option>
<md-option value="center">Center</md-option>
</md-select>
<md-select [(ngModel)]="verticalPosition">
<md-option value="top">Top</md-option>
<md-option value="bottom">Bottom</md-option>
</md-select>
</div>
<div>
<md-checkbox [(ngModel)]="action">
<p *ngIf="!action">Show button on snack bar</p>
Expand All @@ -27,7 +41,6 @@ <h1>SnackBar demo</h1>
</md-form-field>
</md-checkbox>
</div>

<p>
<md-checkbox [(ngModel)]="addExtraClass">Add extra class to container</md-checkbox>
</p>
Expand Down
15 changes: 13 additions & 2 deletions src/demo-app/snack-bar/snack-bar-demo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import {Component, ViewEncapsulation} from '@angular/core';
import {MdSnackBar, MdSnackBarConfig} from '@angular/material';
import {
MdSnackBar,
MdSnackBarConfig,
MdSnackBarHorizontalPosition,
MdSnackBarVerticalPosition,
Dir,
} from '@angular/material';

@Component({
moduleId: module.id,
Expand All @@ -15,13 +21,18 @@ export class SnackBarDemo {
setAutoHide: boolean = true;
autoHide: number = 10000;
addExtraClass: boolean = false;
horizontalPosition: MdSnackBarHorizontalPosition = 'center';
verticalPosition: MdSnackBarVerticalPosition = 'bottom';

constructor(public snackBar: MdSnackBar) { }
constructor(public snackBar: MdSnackBar, private dir: Dir) { }

open() {
let config = new MdSnackBarConfig();
config.verticalPosition = this.verticalPosition;
config.horizontalPosition = this.horizontalPosition;
config.duration = this.autoHide;
config.extraClasses = this.addExtraClass ? ['party'] : undefined;
config.direction = this.dir.value;
this.snackBar.open(this.message, this.action ? this.actionButtonLabel : undefined, config);
}
}
12 changes: 12 additions & 0 deletions src/lib/snack-bar/snack-bar-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import {Direction} from '@angular/cdk/bidi';

export const MD_SNACK_BAR_DATA = new InjectionToken<any>('MdSnackBarData');

/** Possible values for horizontalPosition on MdSnackBarConfig. */
export type MdSnackBarHorizontalPosition = 'start' | 'center' | 'end' | 'left' | 'right';

/** Possible values for verticalPosition on MdSnackBarConfig. */
export type MdSnackBarVerticalPosition = 'top' | 'bottom';

/**
* Configuration used when opening a snack-bar.
*/
Expand All @@ -36,4 +42,10 @@ export class MdSnackBarConfig {

/** Data being injected into the child component. */
data?: any = null;

/** The horizontal position to place the snack bar. */
horizontalPosition?: MdSnackBarHorizontalPosition = 'center';

/** The vertical position to place the snack bar. */
verticalPosition?: MdSnackBarVerticalPosition = 'bottom';
}
20 changes: 19 additions & 1 deletion src/lib/snack-bar/snack-bar-container.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,36 @@
$mat-snack-bar-padding: 14px 24px !default;
$mat-snack-bar-min-width: 288px !default;
$mat-snack-bar-max-width: 568px !default;
$mat-snack-bar-spacing-margin: 24px !default;


.mat-snack-bar-container {
border-radius: 2px;
box-sizing: content-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.
// Initial transformation is applied to start snack bar out of view, below its target position.
transform: translateY(100%);

/**
* 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;
}

/**
* 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%);
}

@include cdk-high-contrast {
border: solid 1px;
}
Expand Down
45 changes: 34 additions & 11 deletions src/lib/snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {Subject} from 'rxjs/Subject';
import {MdSnackBarConfig} from './snack-bar-config';


export type SnackBarState = 'initial' | 'visible' | 'complete' | 'void';
export type SnackBarState = 'visible' | 'hidden' | 'void';

// TODO(jelbourn): we can't use constants from animation.ts here because you can't use
// a text interpolation in anything that is analyzed statically with ngc (for AoT compile).
Expand All @@ -59,15 +59,22 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)';
host: {
'role': 'alert',
'class': 'mat-snack-bar-container',
'[@state]': 'animationState',
'[@state]': 'getAnimationState()',
'(@state.done)': 'onAnimationEnd($event)'
},
animations: [
trigger('state', [
state('void, initial, complete', style({transform: 'translateY(100%)'})),
state('visible', style({transform: 'translateY(0%)'})),
transition('visible => complete', animate(HIDE_ANIMATION)),
transition('initial => visible, void => visible', animate(SHOW_ANIMATION)),
// Animation from top.
state('visible-top', style({transform: 'translateY(0%)'})),
state('hidden-top', style({transform: 'translateY(-100%)'})),
transition('visible-top => hidden-top', animate(HIDE_ANIMATION)),
transition('void => visible-top', animate(SHOW_ANIMATION)),
// Animation from bottom.
state('visible-bottom', style({transform: 'translateY(0%)'})),
state('hidden-bottom', style({transform: 'translateY(100%)'})),
transition('visible-bottom => hidden-bottom', animate(HIDE_ANIMATION)),
transition('void => visible-bottom',
animate(SHOW_ANIMATION)),
])
],
})
Expand All @@ -85,7 +92,7 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
_onEnter: Subject<any> = new Subject();

/** The state of the snack bar animations. */
animationState: SnackBarState = 'initial';
private _animationState: SnackBarState;

/** The snack bar configuration. */
snackBarConfig: MdSnackBarConfig;
Expand All @@ -98,6 +105,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
super();
}

/**
* Gets the current animation state both combining one of the possibilities from
* SnackBarState and the vertical location.
*/
getAnimationState(): string {
return `${this._animationState}-${this.snackBarConfig.verticalPosition}`;
}

/** Attach a component portal as content to this snack bar container. */
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
if (this._portalHost.hasAttached()) {
Expand All @@ -112,6 +127,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
}
}

if (this.snackBarConfig.horizontalPosition === 'center') {
this._renderer.addClass(this._elementRef.nativeElement, 'mat-snack-bar-center');
}

if (this.snackBarConfig.verticalPosition === 'top') {
this._renderer.addClass(this._elementRef.nativeElement, 'mat-snack-bar-top');
}

return this._portalHost.attachComponentPortal(portal);
}

Expand All @@ -122,11 +145,11 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {

/** Handle end of animations, updating the state of the snackbar. */
onAnimationEnd(event: AnimationEvent) {
if (event.toState === 'void' || event.toState === 'complete') {
if (event.toState === 'void' || event.toState.startsWith('hidden')) {
this._completeExit();
}

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

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

Expand Down
Loading

0 comments on commit 5f96375

Please sign in to comment.