Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit 279fe07

Browse files
authored
feat(snackbar): Implement MDC Snackbar Service (#226)
* Re-architect MDC Snackbar as service * Write documentation and examples * Write unit tests Closes #225
1 parent fae29fc commit 279fe07

26 files changed

+1536
-145
lines changed

src/demo-app/components/snackbar-demo/snackbar-demo.component.html

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,150 @@
11
<div fxLayout="column" fxLayoutAlign="start start" class="mdc-padding">
22
<h1 mdc-typography-display1>Snackbar</h1>
3-
<div mdc-typography-subheading2>The MDC Snackbar component is a spec-aligned snackbar component adhering to the Material Design snackbars requirements.</div>
4-
<div class="info-banner" mdc-typography-subheading1><![CDATA[import { MdcSnackbarModule } from '@angular-mdc/web';]]></div>
3+
<div mdc-typography-subheading2>The MDC Snackbar is a spec-aligned snackbar component adhering to the Material Design snackbars requirements.</div>
4+
<div class="info-banner" mdc-typography-subheading1><![CDATA[import { MdcSnackbar } from '@angular-mdc/web';]]></div>
55
<div class="docs-api">
66
<table>
77
<thead>
88
<tr>
9-
<th>Component</th>
9+
<th>Service</th>
1010
</tr>
1111
</thead>
1212
<tbody>
1313
<tr>
14-
<td>mdc-snackbar</td>
14+
<td>MdcSnackbar</td>
1515
</tr>
1616
</tbody>
1717
<thead>
1818
<tr>
19-
<th>Properties</th>
20-
<th>Description</th>
19+
<th>Methods</th>
2120
</tr>
2221
</thead>
2322
<tbody>
2423
<tr>
25-
<td>@Input() alignStart: boolean</td>
26-
<td>Set snackbar message as start-aligned.</td>
24+
<td>show(message: string)</td>
25+
<td>Show snackbar message.</td>
26+
</tr>
27+
<tr>
28+
<td>show(message: string, action: string)</td>
29+
<td>Show snackbar message and action.</td>
2730
</tr>
2831
<tr>
29-
<td>@Input() dismissOnAction: boolean</td>
30-
<td>If you want the snackbar to remain visible until the timeout is reached (regardless of whether the user pressed the action button or not) you can set the dismissesOnAction property to false.</td>
32+
<td><pre><code><![CDATA[show(
33+
message: string,
34+
action: string,
35+
{
36+
config
37+
})
38+
]]></code></pre></td>
39+
<td>
40+
<pre><code><![CDATA[config {
41+
timeout?: number = 2750;
42+
actionHandler?: Function;
43+
multiline?: boolean = false;
44+
actionOnBottom?: boolean = false;
45+
align?: string = 'center';
46+
dismissOnAction?: boolean = true;
47+
focusAction?: boolean = false;
48+
}]]></code></pre>
49+
</td>
3150
</tr>
3251
</tbody>
3352
<thead>
3453
<tr>
35-
<th>Methods</th>
36-
<th>Parameters</th>
54+
<th>Config</th>
3755
</tr>
3856
</thead>
3957
<tbody>
4058
<tr>
41-
<td>show(data: SnackbarMessage)</td>
42-
<td>
43-
<pre><code><![CDATA[interface SnackbarMessage {
44-
message: string;
45-
timeout?: number; - Amount of time in milliseconds to show the snackbar (default 2750).
46-
actionHandler?: Function; - Function to execute when the action is clicked.
47-
actionText?: string; - Text to display for the action button.
48-
multiline?: boolean; - Whether to show the snackbar with space for multiple lines of text.
49-
actionOnBottom?: boolean; - Whether to show the action below the multiple lines of text.
50-
}]]></code></pre>
51-
</td>
59+
<td>timeout: number (Default 2750)</td>
60+
<td>The amount of time in milliseconds to show the snackbar.</td>
61+
</tr>
62+
<tr>
63+
<td>actionHandler: Function</td>
64+
<td>The function to execute when the action is clicked.</td>
65+
</tr>
66+
<tr>
67+
<td>multiline: boolean</td>
68+
<td>Whether to show the snackbar with space for multiple lines of text.</td>
69+
</tr>
70+
<tr>
71+
<td>actionOnBottom: boolean</td>
72+
<td>Whether to show the action below the multiple lines of text.</td>
73+
</tr>
74+
<tr>
75+
<td>align: string (Default 'center')</td>
76+
<td>Alignment of the snackbar on screen. Valid values - 'start' | 'center'</td>
77+
</tr>
78+
<tr>
79+
<td>dismissOnAction: boolean</td>
80+
<td>Dismisses snackbar when action button is clicked.</td>
81+
</tr>
82+
<tr>
83+
<td>focusAction: boolean</td>
84+
<td>Sets focus to the action button.</td>
5285
</tr>
5386
</tbody>
5487
</table>
5588
</div>
5689
<div fxLayout="column" class="mdc-padding">
5790
<mdc-form-field>
58-
<mdc-checkbox [(ngModel)]="message.multiline"></mdc-checkbox>
91+
<mdc-checkbox [(ngModel)]="multiline"></mdc-checkbox>
5992
<label>Multiline</label>
6093
</mdc-form-field>
6194
<mdc-form-field>
62-
<mdc-checkbox [(ngModel)]="message.actionOnBottom" [disabled]="!message.multiline"></mdc-checkbox>
95+
<mdc-checkbox [(ngModel)]="alignStart"></mdc-checkbox>
96+
<label>Start align</label>
97+
</mdc-form-field>
98+
<mdc-form-field>
99+
<mdc-checkbox [(ngModel)]="actionOnBottom" [disabled]="!multiline"></mdc-checkbox>
63100
<label>Action on Bottom (requires multiline)</label>
64101
</mdc-form-field>
65102
<mdc-form-field>
66-
<mdc-checkbox [(ngModel)]="snack.dismissOnAction"></mdc-checkbox>
103+
<mdc-checkbox [(ngModel)]="dismissOnAction"></mdc-checkbox>
67104
<label>Dismiss on Action Click</label>
68105
</mdc-form-field>
106+
<mdc-form-field>
107+
<mdc-checkbox [(ngModel)]="focusAction"></mdc-checkbox>
108+
<label>Focus on Action button</label>
109+
</mdc-form-field>
110+
111+
<div fxLayout="column">
112+
<mdc-textfield [(ngModel)]="message" label="Message" [required]="true"></mdc-textfield>
113+
<mdc-textfield [(ngModel)]="action" label="Action"></mdc-textfield>
114+
</div>
115+
<div class="mdc-padding" fxLayout="column" fxFlexOffset="2">
116+
<button mdc-button [primary]="true" [raised]="true" (click)="show();">Show Snackbar</button>
117+
</div>
69118
</div>
70-
<div fxLayout="column" fxFlexOffset="1">
71-
<mdc-textfield [(ngModel)]="message.message" label="Message text" [required]="true"></mdc-textfield>
72-
<mdc-textfield [(ngModel)]="message.actionText" label="Action button text"></mdc-textfield>
73-
</div>
74-
<div class="mdc-padding" fxLayout="column" fxFlexOffset="2">
75-
<button mdc-button [primary]="true" [raised]="true" (click)="show();">Show Snackbar</button>
76-
</div>
77-
<div class="mdc-padding" fxLayout="column" fxFlexOffset="2">
78-
<button mdc-button [primary]="true" [raised]="true" (click)="showStartAligned();">Show Start Aligned</button>
79-
</div>
119+
<pre style="background:#000;color:#f8f8f8"><code><![CDATA[import { Component } from '@angular/core';
120+
121+
import { MdcSnackbar } from '@angular-mdc/web';
122+
123+
@Component({
124+
selector: 'snackbar-demo',
125+
templateUrl: './snackbar-demo.component.html',
126+
providers: [MdcSnackbar],
127+
})
128+
export class SnackbarDemoComponent {
129+
message = 'Message deleted';
130+
action = 'Undo';
131+
multiline = false;
132+
dismissOnAction: boolean = true;
133+
alignStart: boolean;
134+
focusAction = false;
135+
actionOnBottom = false;
136+
137+
constructor(public snackbar: MdcSnackbar) { }
138+
139+
show() {
140+
let snackbarRef = this.snackbar.show(this.message, this.action, {
141+
align: this.alignStart ? 'start' : '',
142+
multiline: this.multiline,
143+
dismissOnAction: this.dismissOnAction,
144+
focusAction: this.focusAction,
145+
actionOnBottom: this.actionOnBottom,
146+
});
147+
}
148+
}
149+
]]></code></pre>
80150
</div>
81-
<mdc-snackbar #snack></mdc-snackbar>
82-
<mdc-snackbar #snackStart [alignStart]="true"></mdc-snackbar>
Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
1-
import {
2-
Component,
3-
ViewChild
4-
} from '@angular/core';
1+
import { Component } from '@angular/core';
52

6-
import { SnackbarMessage, MdcSnackbarComponent } from '../../../lib/public_api';
3+
import { MdcSnackbar } from '../../../lib/public_api';
74

85
@Component({
96
selector: 'snackbar-demo',
10-
templateUrl: './snackbar-demo.component.html'
7+
templateUrl: './snackbar-demo.component.html',
8+
providers: [MdcSnackbar],
119
})
1210
export class SnackbarDemoComponent {
13-
message: SnackbarMessage = {
14-
message: 'Message deleted',
15-
actionText: 'Undo',
16-
};
17-
@ViewChild('snack') snack: MdcSnackbarComponent;
18-
@ViewChild('snackStart') snackStart: MdcSnackbarComponent;
11+
message = 'Message deleted';
12+
action = 'Undo';
13+
multiline = false;
14+
dismissOnAction: boolean = true;
15+
alignStart: boolean;
16+
focusAction = false;
17+
actionOnBottom = false;
1918

20-
show() {
21-
this.snack.show(this.message);
22-
}
19+
constructor(public snackbar: MdcSnackbar) { }
2320

24-
showStartAligned() {
25-
this.snackStart.show(this.message);
21+
show() {
22+
let snackbarRef = this.snackbar.show(this.message, this.action, {
23+
align: this.alignStart ? 'start' : '',
24+
multiline: this.multiline,
25+
dismissOnAction: this.dismissOnAction,
26+
focusAction: this.focusAction,
27+
actionOnBottom: this.actionOnBottom,
28+
});
2629
}
2730
}

src/lib/cdk/overlay/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NgModule, Provider } from '@angular/core';
2+
3+
import { Overlay } from './overlay';
4+
import { OVERLAY_CONTAINER_PROVIDER } from './overlay-container';
5+
import { PortalModule } from '../portal';
6+
7+
export const OVERLAY_PROVIDERS: Provider[] = [
8+
Overlay,
9+
OVERLAY_CONTAINER_PROVIDER,
10+
];
11+
12+
@NgModule({
13+
imports: [PortalModule],
14+
providers: [OVERLAY_PROVIDERS],
15+
})
16+
export class OverlayModule { }
17+
18+
export { Overlay } from './overlay';
19+
export { OverlayRef } from './overlay-ref';
20+
export { OverlayContainer } from './overlay-container';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {
10+
Injectable,
11+
OnDestroy,
12+
Optional,
13+
SkipSelf,
14+
} from '@angular/core';
15+
16+
@Injectable()
17+
export class OverlayContainer implements OnDestroy {
18+
protected containerElement_: HTMLElement;
19+
20+
ngOnDestroy() {
21+
if (this.containerElement_ && this.containerElement_.parentNode) {
22+
this.containerElement_.parentNode.removeChild(this.containerElement_);
23+
}
24+
}
25+
26+
getContainerElement(): HTMLElement {
27+
if (!this.containerElement_) { this._createContainer(); }
28+
return this.containerElement_;
29+
}
30+
31+
protected _createContainer(): void {
32+
let container = document.createElement('div');
33+
34+
document.body.appendChild(container);
35+
this.containerElement_ = container;
36+
}
37+
}
38+
39+
export function OVERLAY_CONTAINER_PROVIDER_FACTORY(parentContainer: OverlayContainer) {
40+
return parentContainer || new OverlayContainer();
41+
}
42+
43+
export const OVERLAY_CONTAINER_PROVIDER = {
44+
// If there is already an OverlayContainer available, use that. Otherwise, provide a new one.
45+
provide: OverlayContainer,
46+
deps: [[new Optional(), new SkipSelf(), OverlayContainer]],
47+
useFactory: OVERLAY_CONTAINER_PROVIDER_FACTORY
48+
};

0 commit comments

Comments
 (0)