Skip to content

Commit ee18409

Browse files
committed
Allow snackbar position to be set to left or center.
1 parent e8ab0da commit ee18409

File tree

7 files changed

+351
-75
lines changed

7 files changed

+351
-75
lines changed

src/demo-app/snack-bar/snack-bar-demo.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ <h1>SnackBar demo</h1>
33
<div>
44
Message: <md-input-container><input mdInput type="text" [(ngModel)]="message"></md-input-container>
55
</div>
6+
<div>
7+
<div>Position in page: </div>
8+
<md-select [(ngModel)]="horizontalPosition">
9+
<md-option value="left">Left</md-option>
10+
<md-option value="center">Center</md-option>
11+
<md-option value="right">Right</md-option>
12+
</md-select>
13+
<md-select [(ngModel)]="verticalPosition">
14+
<md-option value="top">Top</md-option>
15+
<md-option value="bottom">Bottom</md-option>
16+
</md-select>
17+
</div>
618
<div>
719
<md-checkbox [(ngModel)]="action">
820
<p *ngIf="!action">Show button on snack bar</p>
@@ -27,7 +39,6 @@ <h1>SnackBar demo</h1>
2739
</md-input-container>
2840
</md-checkbox>
2941
</div>
30-
3142
<p>
3243
<md-checkbox [(ngModel)]="addExtraClass">Add extra class to container</md-checkbox>
3344
</p>

src/demo-app/snack-bar/snack-bar-demo.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import {Component, ViewEncapsulation} from '@angular/core';
2-
import {MdSnackBar, MdSnackBarConfig} from '@angular/material';
2+
import {
3+
MdSnackBar,
4+
MdSnackBarConfig,
5+
MdSnackBarHorizontalPosition,
6+
MdSnackBarVerticalPosition
7+
} from '@angular/material';
38

49
@Component({
510
moduleId: module.id,
@@ -15,12 +20,16 @@ export class SnackBarDemo {
1520
setAutoHide: boolean = true;
1621
autoHide: number = 10000;
1722
addExtraClass: boolean = false;
23+
horizontalPosition: MdSnackBarHorizontalPosition = 'center';
24+
verticalPosition: MdSnackBarVerticalPosition = 'bottom';
1825

1926
constructor(public snackBar: MdSnackBar) { }
2027

2128
open() {
2229
let config = new MdSnackBarConfig();
23-
config.duration = this.autoHide;
30+
config.verticalPosition = this.verticalPosition;
31+
config.horizontalPosition = this.horizontalPosition;
32+
config.duration = this.setAutoHide ? this.autoHide : 0;
2433
config.extraClasses = this.addExtraClass ? ['party'] : null;
2534
this.snackBar.open(this.message, this.action && this.actionButtonLabel, config);
2635
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import {ViewContainerRef} from '@angular/core';
22
import {AriaLivePoliteness} from '../core';
33

4+
/** Possible values for horizontalPosition on MdSnackBarConfig. */
5+
export type MdSnackBarHorizontalPosition = 'left'|'center'|'right';
6+
7+
/** Possible values for verticalPosition on MdSnackBarConfig. */
8+
export type MdSnackBarVerticalPosition = 'top'|'bottom';
9+
410
/**
511
* Configuration used when opening a snack-bar.
612
*/
@@ -19,4 +25,10 @@ export class MdSnackBarConfig {
1925

2026
/** Extra CSS classes to be added to the snack bar container. */
2127
extraClasses?: string[];
28+
29+
/** The horizontal position to place the snack bar. */
30+
horizontalPosition?: MdSnackBarHorizontalPosition = 'center';
31+
32+
/** The vertical position to place the snack bar. */
33+
verticalPosition?: MdSnackBarVerticalPosition = 'bottom';
2234
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
$mat-snack-bar-padding: 14px 24px !default;
55
$mat-snack-bar-min-width: 288px !default;
66
$mat-snack-bar-max-width: 568px !default;
7+
$mat-snack-bar-spacing-margin: 24px !default;
78

89

910
:host {
@@ -12,12 +13,29 @@ $mat-snack-bar-max-width: 568px !default;
1213
border-radius: 2px;
1314
box-sizing: content-box;
1415
display: block;
16+
margin: $mat-snack-bar-spacing-margin;
1517
max-width: $mat-snack-bar-max-width;
1618
min-width: $mat-snack-bar-min-width;
1719
padding: $mat-snack-bar-padding;
18-
// Initial transformation is applied to start snack bar out of view.
20+
// Initial transformation is applied to start snack bar out of view, below its target position.
1921
transform: translateY(100%);
2022

23+
/**
24+
* Removes margin of snack bars which are center positioned horizontally. This
25+
* is done to align snack bars to the edge of the view vertically to match spec.
26+
*/
27+
&.mat-snack-bar-center {
28+
margin: 0;
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%);
37+
}
38+
2139
@include cdk-high-contrast {
2240
border: solid 1px;
2341
}

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

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {Subject} from 'rxjs/Subject';
2727

2828

2929

30-
export type SnackBarState = 'initial' | 'visible' | 'complete' | 'void';
30+
export type SnackBarState = 'visible' | 'hidden' | 'void';
3131

3232
// TODO(jelbourn): we can't use constants from animation.ts here because you can't use
3333
// a text interpolation in anything that is analyzed statically with ngc (for AoT compile).
@@ -45,16 +45,22 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)';
4545
styleUrls: ['snack-bar-container.css'],
4646
host: {
4747
'role': 'alert',
48-
'[@state]': 'animationState',
48+
'[@state]': 'getAnimationState()',
4949
'(@state.done)': 'onAnimationEnd($event)'
5050
},
5151
animations: [
5252
trigger('state', [
53-
state('initial', style({transform: 'translateY(100%)'})),
54-
state('visible', style({transform: 'translateY(0%)'})),
55-
state('complete', style({transform: 'translateY(100%)'})),
56-
transition('visible => complete', animate(HIDE_ANIMATION)),
57-
transition('initial => visible, void => visible', animate(SHOW_ANIMATION)),
53+
// Animation from top.
54+
state('visible-top', style({transform: 'translateY(0%)'})),
55+
state('hidden-top', style({transform: 'translateY(-100%)'})),
56+
transition('visible-top => hidden-top', animate(HIDE_ANIMATION)),
57+
transition('void => visible-top', animate(SHOW_ANIMATION)),
58+
// Animation from bottom.
59+
state('visible-bottom', style({transform: 'translateY(0%)'})),
60+
state('hidden-bottom', style({transform: 'translateY(100%)'})),
61+
transition('visible-bottom => hidden-bottom', animate(HIDE_ANIMATION)),
62+
transition('void => visible-bottom',
63+
animate(SHOW_ANIMATION)),
5864
])
5965
],
6066
})
@@ -69,7 +75,7 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
6975
private onEnter: Subject<any> = new Subject();
7076

7177
/** The state of the snack bar animations. */
72-
animationState: SnackBarState = 'initial';
78+
private animationState: SnackBarState;
7379

7480
/** The snack bar configuration. */
7581
snackBarConfig: MdSnackBarConfig;
@@ -81,6 +87,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
8187
super();
8288
}
8389

90+
/**
91+
* Gets the current animation state both combining one of the possibilities from
92+
* SnackBarState and the vertical location.
93+
*/
94+
getAnimationState(): string {
95+
return `${this.animationState}-${this.snackBarConfig.verticalPosition}`;
96+
}
97+
8498
/** Attach a component portal as content to this snack bar container. */
8599
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
86100
if (this._portalHost.hasAttached()) {
@@ -95,6 +109,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
95109
}
96110
}
97111

112+
if (this.snackBarConfig.horizontalPosition === 'center') {
113+
this._renderer.addClass(this._elementRef.nativeElement, 'mat-snack-bar-center');
114+
}
115+
116+
if (this.snackBarConfig.verticalPosition === 'top') {
117+
this._renderer.addClass(this._elementRef.nativeElement, 'mat-snack-bar-top');
118+
}
119+
98120
return this._portalHost.attachComponentPortal(portal);
99121
}
100122

@@ -105,15 +127,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
105127

106128
/** Handle end of animations, updating the state of the snackbar. */
107129
onAnimationEnd(event: AnimationEvent) {
108-
if (event.toState === 'void' || event.toState === 'complete') {
130+
if (event.toState === 'void' || event.toState.startsWith('hidden')) {
109131
this._completeExit();
110132
}
111133

112-
if (event.toState === 'visible') {
134+
if (event.toState.startsWith('visible')) {
113135
// Note: we shouldn't use `this` inside the zone callback,
114136
// because it can cause a memory leak.
115137
const onEnter = this.onEnter;
116-
117138
this._ngZone.run(() => {
118139
onEnter.next();
119140
onEnter.complete();
@@ -134,7 +155,7 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
134155

135156
/** Begin animation of the snack bar exiting from view. */
136157
exit(): Observable<void> {
137-
this.animationState = 'complete';
158+
this.animationState = 'hidden';
138159
return this._onExit();
139160
}
140161

0 commit comments

Comments
 (0)