-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.demo-button-label-input { | ||
display: inline-block; | ||
} |
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 {} |
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); | ||
} | ||
|
||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './snack-bar'; |
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> |
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; | ||
} | ||
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({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; } | ||
} |
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<template portalHost></template> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added MdLiveAnnouncer, I have |
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; | ||
} |
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'); | ||
} | ||
} |
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'); | ||
} | ||
} |
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(); | ||
} | ||
} |
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done