Skip to content

feat(md-snack-bar): Create initial MdSnackBar. #1296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/demo-app/demo-app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {MdCheckboxDemoNestedChecklist, CheckboxDemo} from './checkbox/checkbox-d
import {SelectDemo} from './select/select-demo';
import {SliderDemo} from './slider/slider-demo';
import {SidenavDemo} from './sidenav/sidenav-demo';
import {SnackBarDemo} from './snack-bar/snack-bar-demo';
import {PortalDemo, ScienceJoke} from './portal/portal-demo';
import {MenuDemo} from './menu/menu-demo';
import {TabsDemo} from './tabs/tab-group-demo';
Expand Down Expand Up @@ -61,6 +62,7 @@ import {TabsDemo} from './tabs/tab-group-demo';
LiveAnnouncerDemo,
MdCheckboxDemoNestedChecklist,
MenuDemo,
SnackBarDemo,
OverlayDemo,
PortalDemo,
ProgressBarDemo,
Expand Down
1 change: 1 addition & 0 deletions src/demo-app/demo-app/demo-app.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<a md-list-item [routerLink]="['sidenav']">Sidenav</a>
<a md-list-item [routerLink]="['slider']">Slider</a>
<a md-list-item [routerLink]="['slide-toggle']">Slide Toggle</a>
<a md-list-item [routerLink]="['snack-bar']">Snack Bar</a>
<a md-list-item [routerLink]="['tabs']">Tabs</a>
<a md-list-item [routerLink]="['toolbar']">Toolbar</a>
<a md-list-item [routerLink]="['tooltip']">Tooltip</a>
Expand Down
2 changes: 2 additions & 0 deletions src/demo-app/demo-app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {MenuDemo} from '../menu/menu-demo';
import {RippleDemo} from '../ripple/ripple-demo';
import {DialogDemo} from '../dialog/dialog-demo';
import {TooltipDemo} from '../tooltip/tooltip-demo';
import {SnackBarDemo} from '../snack-bar/snack-bar-demo';


export const DEMO_APP_ROUTES: Routes = [
Expand Down Expand Up @@ -56,4 +57,5 @@ export const DEMO_APP_ROUTES: Routes = [
{path: 'ripple', component: RippleDemo},
{path: 'dialog', component: DialogDemo},
{path: 'tooltip', component: TooltipDemo},
{path: 'snack-bar', component: SnackBarDemo},
];
15 changes: 15 additions & 0 deletions src/demo-app/snack-bar/snack-bar-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<h1>SnackBar demo</h1>
<div>
<div>Message: <md-input type="text" [(ngModel)]="message"></md-input></div>
<div>
<md-checkbox [(ngModel)]="action">
<p *ngIf="!action">Show button on snack bar</p>
<md-input type="text" class="demo-button-label-input"
*ngIf="action"
placeholder="Snack bar action label"
[(ngModel)]="actionButtonLabel"></md-input>
</md-checkbox>
</div>
</div>

<button md-raised-button (click)="open()">OPEN</button>
3 changes: 3 additions & 0 deletions src/demo-app/snack-bar/snack-bar-demo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.demo-button-label-input {
display: inline-block;
}
31 changes: 31 additions & 0 deletions src/demo-app/snack-bar/snack-bar-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {Component, ViewContainerRef} from '@angular/core';
import {MdSnackBar, MdSnackBarConfig} from '@angular/material';

@Component({
moduleId: module.id,
selector: 'snack-bar-demo',
templateUrl: 'snack-bar-demo.html',
})
export class SnackBarDemo {
message: string = 'Snack Bar opened.';
actionButtonLabel: string = 'Retry';
action: boolean = false;

constructor(
public snackBar: MdSnackBar,
public viewContainerRef: ViewContainerRef) { }

open() {
let config = new MdSnackBarConfig(this.viewContainerRef);
this.snackBar.open(this.message, this.action && this.actionButtonLabel, config);
}
}


@Component({
moduleId: module.id,
selector: 'demo-snack',
templateUrl: 'snack-bar-demo.html',
styleUrls: ['./snack-bar-demo.css'],
})
export class DemoSnack {}
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './select/index';
export * from './sidenav/index';
export * from './slider/index';
export * from './slide-toggle/index';
export * from './snack-bar/index';
export * from './tabs/index';
export * from './toolbar/index';
export * from './tooltip/index';
3 changes: 3 additions & 0 deletions src/lib/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {MdIconModule} from './icon/index';
import {MdProgressCircleModule} from './progress-circle/index';
import {MdProgressBarModule} from './progress-bar/index';
import {MdInputModule} from './input/index';
import {MdSnackBarModule} from './snack-bar/snack-bar';
import {MdTabsModule} from './tabs/index';
import {MdToolbarModule} from './toolbar/index';
import {MdTooltipModule} from './tooltip/index';
Expand All @@ -49,6 +50,7 @@ const MATERIAL_MODULES = [
MdSidenavModule,
MdSliderModule,
MdSlideToggleModule,
MdSnackBarModule,
MdTabsModule,
MdToolbarModule,
MdTooltipModule,
Expand Down Expand Up @@ -83,6 +85,7 @@ const MATERIAL_MODULES = [
MdRadioModule.forRoot(),
MdSliderModule.forRoot(),
MdSlideToggleModule.forRoot(),
MdSnackBarModule.forRoot(),
MdTooltipModule.forRoot(),
OverlayModule.forRoot(),
],
Expand Down
38 changes: 38 additions & 0 deletions src/lib/snack-bar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# MdSnackBar
`MdSnackBar` is a service, which opens snack bar notifications in the view.

### Methods

| Name | Description |
| --- | --- |
| `open(message: string,<br> actionLabel: string, config: MdSnackBarConfig): MdSnackBarRef<SimpleSnackBar>` | Creates and opens a simple snack bar noticiation matching material spec. |
| `openFromComponent(component: ComponentType<T>, config: MdSnackBarConfig): MdSnackBarRef<T>` | Creates and opens a snack bar noticiation with a custom component as content. |

### Config

| Key | Description |
| --- | --- |
| `viewContainerRef: ViewContainerRef` | The view container ref to attach the snack bar to. |
| `role: AriaLivePoliteness = 'assertive'` | The politeness level to announce the snack bar with. |
| `announcementMessage: string` | The message to announce with the snack bar. |


### Example
The service can be injected in a component.
```ts
@Component({
selector: 'my-component'
providers: [MdSnackBar]
})
export class MyComponent {

constructor(snackBar: MdSnackBar
viewContainerRef: ViewContainerRef) {}

failedAttempt() {
config = new MdSnackBarConfig(this.viewContainerRef);
this.snackBar.open('It didn\'t quite work!', 'Try Again', config);
}

}
```
1 change: 1 addition & 0 deletions src/lib/snack-bar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './snack-bar';
3 changes: 3 additions & 0 deletions src/lib/snack-bar/simple-snack-bar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<span class="md-simple-snackbar-message">{{message}}</span>
<button md-button class="md-simple-snackbar-action"
*ngIf="hasAction" (click)="dismiss()">{{action}}</button>
28 changes: 28 additions & 0 deletions src/lib/snack-bar/simple-snack-bar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
md-simple-snackbar {
display: flex;
justify-content: space-between;
}

.md-simple-snackbar-message {
box-sizing: border-box;
border: none;
color: white;
font-family: Roboto, 'Helvetica Neue', sans-serif;
font-size: 14px;
line-height: 20px;
outline: none;
text-decoration: none;
word-break: break-all;
}

.md-simple-snackbar-action {
box-sizing: border-box;
color: white;
float: right;
font-weight: 600;
line-height: 20px;
margin: -5px 0 0 48px;
min-width: initial;
padding: 5px;
text-transform: uppercase;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you refactor this to be like:

md-simple-snackbar {
  display: flex;
  justify-content: space-between;
}

.md-simple-snackbar-message {
  ...
}

.md-simple-snackbar-action {
  ...
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

32 changes: 32 additions & 0 deletions src/lib/snack-bar/simple-snack-bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {Component} from '@angular/core';
import {MdSnackBarRef} from './snack-bar-ref';


/**
* A component used to open as the default snack bar, matching material spec.
* This should only be used internally by the snack bar service.
*/
@Component({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add class description explaining what this component is used for, also mentioning that it is internal only.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

moduleId: module.id,
selector: 'simple-snack-bar',
templateUrl: 'simple-snack-bar.html',
styleUrls: ['simple-snack-bar.css'],
})
export class SimpleSnackBar {
/** The message to be shown in the snack bar. */
message: string;

/** The label for the button in the snack bar. */
action: string;

/** The instance of the component making up the content of the snack bar. */
snackBarRef: MdSnackBarRef<SimpleSnackBar>;

/** Dismisses the snack bar. */
dismiss(): void {
this.snackBarRef.dismiss();
}

/** If the action button should be shown. */
get hasAction(): boolean { return !!this.action; }
}
18 changes: 18 additions & 0 deletions src/lib/snack-bar/snack-bar-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {ViewContainerRef} from '@angular/core';
import {AriaLivePoliteness} from '../core';


export class MdSnackBarConfig {
/** The politeness level for the MdAriaLiveAnnouncer announcement. */
politeness: AriaLivePoliteness = 'assertive';

/** Message to be announced by the MdAriaLiveAnnouncer */
announcementMessage: string;

/** The view container to place the overlay for the snack bar into. */
viewContainerRef: ViewContainerRef;

constructor(viewContainerRef: ViewContainerRef) {
this.viewContainerRef = viewContainerRef;
}
}
1 change: 1 addition & 0 deletions src/lib/snack-bar/snack-bar-container.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<template portalHost></template>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a wrapper div with the following:

<div aria-live="assertive" aria-atomic="true" role="alert">

Eventually this needs to use MdLiveAnnouncer, but this can suffice for now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MdLiveAnnouncer, I have role="alert", but not the others currently.

19 changes: 19 additions & 0 deletions src/lib/snack-bar/snack-bar-container.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@import '../core/style/elevation';

$md-snack-bar-padding: 14px 24px !default;
$md-snack-bar-height: 20px !default;
$md-snack-bar-min-width: 288px !default;
$md-snack-bar-max-width: 568px !default;


:host {
@include md-elevation(24);
background: #323232;
border-radius: 2px;
display: block;
height: $md-snack-bar-height;
max-width: $md-snack-bar-max-width;
min-width: $md-snack-bar-min-width;
overflow: hidden;
padding: $md-snack-bar-padding;
}
47 changes: 47 additions & 0 deletions src/lib/snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
Component,
ComponentRef,
ViewChild
} from '@angular/core';
import {
BasePortalHost,
ComponentPortal,
TemplatePortal,
PortalHostDirective
} from '../core';
import {MdSnackBarConfig} from './snack-bar-config';
import {MdSnackBarContentAlreadyAttached} from './snack-bar-errors';


/**
* Internal component that wraps user-provided snack bar content.
*/
@Component({
moduleId: module.id,
selector: 'snack-bar-container',
templateUrl: 'snack-bar-container.html',
styleUrls: ['snack-bar-container.css'],
host: {
'role': 'alert'
}
})
export class MdSnackBarContainer extends BasePortalHost {
/** The portal host inside of this container into which the snack bar content will be loaded. */
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;

/** The snack bar configuration. */
snackBarConfig: MdSnackBarConfig;

/** Attach a portal as content to this snack bar container. */
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
if (this._portalHost.hasAttached()) {
throw new MdSnackBarContentAlreadyAttached();
}

return this._portalHost.attachComponentPortal(portal);
}

attachTemplatePortal(portal: TemplatePortal): Map<string, any> {
throw Error('Not yet implemented');
}
}
8 changes: 8 additions & 0 deletions src/lib/snack-bar/snack-bar-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {MdError} from '../core';


export class MdSnackBarContentAlreadyAttached extends MdError {
constructor() {
super('Attempting to attach snack bar content after content is already attached');
}
}
35 changes: 35 additions & 0 deletions src/lib/snack-bar/snack-bar-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {OverlayRef} from '../core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';

// TODO(josephperrott): Implement onAction observable.


/**
* Reference to a snack bar dispatched from the snack bar service.
*/
export class MdSnackBarRef<T> {
/** The instance of the component making up the content of the snack bar. */
readonly instance: T;

/** Subject for notifying the user that the snack bar has closed. */
private _afterClosed: Subject<any> = new Subject();

constructor(instance: T, private _overlayRef: OverlayRef) {
// Sets the readonly instance of the snack bar content component.
this.instance = instance;
}

/** Dismisses the snack bar. */
dismiss(): void {
if (!this._afterClosed.closed) {
this._overlayRef.dispose();
this._afterClosed.complete();
}
}

/** Gets an observable that is notified when the snack bar is finished closing. */
afterDismissed(): Observable<void> {
return this._afterClosed.asObservable();
}
}
Loading