diff --git a/ionic/components.core.scss b/ionic/components.core.scss
index 7252f4a8ad6..e3a102a431e 100644
--- a/ionic/components.core.scss
+++ b/ionic/components.core.scss
@@ -26,6 +26,7 @@
"components/show-hide-when/show-hide-when",
"components/slides/slides",
"components/spinner/spinner",
+ "components/toast/toast",
"components/virtual-scroll/virtual-scroll";
diff --git a/ionic/components.ios.scss b/ionic/components.ios.scss
index aa02af24f17..b37c65aff0c 100644
--- a/ionic/components.ios.scss
+++ b/ionic/components.ios.scss
@@ -27,6 +27,7 @@
"components/select/select.ios",
"components/tabs/tabs.ios",
"components/toggle/toggle.ios",
+ "components/toast/toast.ios",
"components/toolbar/toolbar.ios";
diff --git a/ionic/components.md.scss b/ionic/components.md.scss
index 91911f4c626..4515f65aa16 100644
--- a/ionic/components.md.scss
+++ b/ionic/components.md.scss
@@ -27,6 +27,7 @@
"components/select/select.md",
"components/tabs/tabs.md",
"components/toggle/toggle.md",
+ "components/toast/toast.md",
"components/toolbar/toolbar.md";
diff --git a/ionic/components.ts b/ionic/components.ts
index 86455e54bb6..a961e2ab846 100644
--- a/ionic/components.ts
+++ b/ionic/components.ts
@@ -47,5 +47,6 @@ export * from './components/tabs/tabs'
export * from './components/tabs/tab'
export * from './components/tap-click/tap-click'
export * from './components/toggle/toggle'
+export * from './components/toast/toast'
export * from './components/toolbar/toolbar'
export * from './components/virtual-scroll/virtual-scroll'
diff --git a/ionic/components/toast/test/basic/e2e.ts b/ionic/components/toast/test/basic/e2e.ts
new file mode 100644
index 00000000000..cffef5a0be7
--- /dev/null
+++ b/ionic/components/toast/test/basic/e2e.ts
@@ -0,0 +1,8 @@
+
+it('should open action sheet', function() {
+ element(by.css('.e2eOpenActionSheet')).click();
+});
+
+it('should close with backdrop click', function() {
+ element(by.css('.backdrop')).click();
+});
diff --git a/ionic/components/toast/test/basic/index.ts b/ionic/components/toast/test/basic/index.ts
new file mode 100644
index 00000000000..b6341523ffa
--- /dev/null
+++ b/ionic/components/toast/test/basic/index.ts
@@ -0,0 +1,78 @@
+import {App, Page, Toast, NavController, Platform} from 'ionic-angular';
+
+@Page({
+ templateUrl: 'main.html'
+})
+class E2EPage {
+ constructor(
+ private nav: NavController,
+ private platform: Platform)
+ {}
+
+ showToast() {
+ const toast = Toast.create({
+ message: 'User was created successfully',
+ });
+
+ this.nav.present(toast);
+ }
+
+ showLongToast() {
+ const toast = Toast.create({
+ message: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ea voluptatibus quibusdam eum nihil optio, ullam accusamus magni, nobis suscipit reprehenderit, sequi quam amet impedit. Accusamus dolorem voluptates laborum dolor obcaecati.',
+ });
+
+ this.nav.present(toast);
+ }
+
+ showDismissDurationToast() {
+ const toast = Toast.create({
+ message: 'I am dismissed after 1.5 seconds',
+ duration: 1500
+ });
+
+ this.nav.present(toast);
+ }
+
+ showToastWithCloseButton() {
+ const toast = Toast.create({
+ message: 'Your internet connection appears to be offline. Data integrity is not gauranteed.',
+ showCloseButton: true,
+ closeButtonText: 'Ok'
+ });
+
+ this.nav.present(toast);
+ }
+
+}
+
+@Page({
+ template: `
+
+
+
+
+ Toast
+
+
+ Hi, I'm Bob, and I'm a modal.
+
+ `
+})
+class ToastPage {
+ constructor(private viewCtrl: ViewController) { }
+
+ dismiss() {
+ this.viewCtrl.dismiss();
+ }
+}
+
+
+@App({
+ template: ''
+})
+class E2EApp {
+ constructor() {
+ this.root = E2EPage;
+ }
+}
diff --git a/ionic/components/toast/test/basic/main.html b/ionic/components/toast/test/basic/main.html
new file mode 100644
index 00000000000..f6407aa6d20
--- /dev/null
+++ b/ionic/components/toast/test/basic/main.html
@@ -0,0 +1,11 @@
+
+ Toasts
+
+
+
+
+
+
+
+
+
diff --git a/ionic/components/toast/toast.ios.scss b/ionic/components/toast/toast.ios.scss
new file mode 100644
index 00000000000..1c6badde01e
--- /dev/null
+++ b/ionic/components/toast/toast.ios.scss
@@ -0,0 +1,39 @@
+// iOS Toast
+// --------------------------------------------------
+$toast-ios-text-align: left !default;
+$toast-ios-background: rgba(0, 0, 0, 0.70) !default;
+$toast-ios-border-radius: 0.65rem !default;
+
+$toast-ios-title-color: #fff !default;
+$toast-ios-title-font-size: 1.4rem !default;
+$toast-ios-title-padding: 1.5rem !default;
+
+ion-toast {
+ display: block;
+ height: $toast-width;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: $toast-width;
+ z-index: $z-index-overlay;
+}
+
+.toast-wrapper {
+ background: $toast-ios-background;
+ border-radius: $toast-ios-border-radius;
+ bottom: 10px;
+ display: block;
+ left: 10px;
+ margin: auto;
+ max-width: $toast-max-width;
+ position: absolute;
+ right: 10px;
+ transform: translate3d(0, 100%, 0);
+ z-index: $z-index-overlay-wrapper;
+}
+
+.toast-message {
+ color: $toast-ios-title-color;
+ font-size: $toast-ios-title-font-size;
+ padding: $toast-ios-title-padding;
+}
diff --git a/ionic/components/toast/toast.md.scss b/ionic/components/toast/toast.md.scss
new file mode 100644
index 00000000000..a50b34b29ff
--- /dev/null
+++ b/ionic/components/toast/toast.md.scss
@@ -0,0 +1,41 @@
+@import "../../globals.md";
+
+// Material Design Toast
+// --------------------------------------------------
+$toast-md-text-align: left !default;
+$toast-md-background: #333333 !default;
+$toast-md-group-margin-bottom: 8px !default;
+
+$toast-md-title-color: #fff !default;
+$toast-md-title-font-size: 1.5rem !default;
+$toast-md-title-padding: 19px 16px 17px !default;
+
+ion-toast {
+ display: block;
+ height: $toast-width;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: $toast-width;
+ z-index: $z-index-overlay;
+}
+
+.toast-wrapper {
+ background: $toast-md-background;
+ bottom: 0;
+ display: block;
+ left: 0;
+ margin: auto;
+ max-width: $toast-max-width;
+ position: absolute;
+ right: 0;
+ transform: translate3d(0, 100%, 0);
+ width: $toast-width;
+ z-index: $z-index-overlay-wrapper;
+}
+
+.toast-message {
+ color: $toast-md-title-color;
+ font-size: $toast-md-title-font-size;
+ padding: $toast-md-title-padding;
+}
diff --git a/ionic/components/toast/toast.scss b/ionic/components/toast/toast.scss
new file mode 100644
index 00000000000..3a09c7954e5
--- /dev/null
+++ b/ionic/components/toast/toast.scss
@@ -0,0 +1,43 @@
+@import "../../globals.ios";
+
+// Action Sheet
+// --------------------------------------------------
+
+$toast-width: 100% !default;
+$toast-max-width: 700px !default;
+
+ion-toast {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: $z-index-overlay;
+ display: block;
+ width: $toast-width;
+ height: $toast-width;
+}
+
+.toast-container {
+ display: flex;
+ align-items: center;
+
+ button {
+ font-size: 1.5rem;
+ padding: 19px 16px 17px;
+ }
+}
+
+.toast-message {
+ flex: 1;
+}
+
+.toast-wrapper {
+ bottom: 0;
+ display: block;
+ left: 0;
+ margin: auto;
+ max-width: $toast-max-width;
+ position: absolute;
+ right: 0;
+ transform: translate3d(0, 100%, 0);
+ z-index: $z-index-overlay-wrapper;
+}
diff --git a/ionic/components/toast/toast.ts b/ionic/components/toast/toast.ts
new file mode 100644
index 00000000000..b7d216b600e
--- /dev/null
+++ b/ionic/components/toast/toast.ts
@@ -0,0 +1,263 @@
+import {Component, ElementRef, Renderer} from 'angular2/core';
+import {NgClass, NgIf, NgFor} from 'angular2/common';
+
+import {Button} from '../button/button';
+import {Icon} from '../icon/icon';
+import {ActionSheet, ActionSheetOptions} from '../action-sheet/action-sheet';
+import {Animation} from '../../animations/animation';
+import {Transition, TransitionOptions} from '../../transitions/transition';
+
+import {Config} from '../../config/config';
+import {isPresent} from '../../util/util';
+import {NavParams} from '../nav/nav-params';
+import {NavController} from '../nav/nav-controller';
+import {ViewController} from '../nav/view-controller';
+
+/**
+ * @name Toast
+ * @description
+ * An Toast is a small message that appears in the lower part of the screen.
+ * It's useful for displaying success messages, error messages, etc.
+ * `title`, `subTitle` and `message`.
+ *
+ * @usage
+ * ```ts
+ * constructor(nav: NavController) {
+ * this.nav = nav;
+ * }
+ *
+ * presentToast() {
+ * let toast = Toast.create({
+ * message: 'User was added successfully',
+ * duration: 3000
+ * });
+ * this.nav.present(toast);
+ * }
+ * ```
+ *
+ * @demo /docs/v2/demos/toast/
+ */
+export class Toast extends ViewController {
+
+ constructor(opts: ToastOptions = {}) {
+ console.log('Toast Constructor');
+ opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true;
+
+ super(ToastCmp, opts);
+
+ this.viewType = 'toast';
+ this.isOverlay = false;
+
+ // by default, toasts should not fire lifecycle events of other views
+ // for example, when an toast enters, the current active view should
+ // not fire its lifecycle events because it's not conceptually leaving
+ this.fireOtherLifecycles = false;
+ }
+
+ /**
+ * @private
+ */
+ getTransitionName(direction: string) {
+ let key = 'toast' + (direction === 'back' ? 'Leave' : 'Enter');
+ return this._nav && this._nav.config.get(key);
+ }
+
+ /**
+ * @param {string} message Toast message content
+ */
+ setMessage(message: string) {
+ this.data.message = message;
+ }
+
+ /**
+ *
+ * Toast options
+ *
+ * | Property | Type | Description |
+ * |-----------------------|-----------|--------------------------------------------------------------------------- |
+ * | message | `string` | The message for the toast |
+ * | duration | `number` | The amount of time in milliseconds the toast should appear (optional) |
+ * | cssClass | `string` | Any additional class for the toast (optional) |
+ * | showCloseButton | `boolean` | Whether or not to show an optional button to close the toast. (optional) |
+ * | closeButtonText | `string` | Text to display in the close button. (optional) |
+ * | enableBackdropDismiss | `boolean` | Whether the the toast should be dismissed by tapping the backdrop (optional) |
+ *
+ * @param {object} opts Toast. See the tabel above
+ */
+ static create(opts: ToastOptions = {}) {
+ return new Toast(opts);
+ }
+
+}
+
+
+/**
+* @private
+*/
+@Component({
+ selector: 'ion-toast',
+ template: `
+
+
+
+
{{d.message}}
+
+
+
+ `,
+ host: {
+ 'role': 'dialog',
+ '[attr.aria-labelledby]': 'hdrId',
+ '[attr.aria-describedby]': 'descId'
+ },
+ directives: [NgIf, Icon, Button]
+})
+class ToastCmp {
+ private d: any;
+ private descId: string;
+ private hdrId: string;
+ private id: number;
+ private created: number;
+
+ constructor(
+ private _nav: NavController,
+ private _viewCtrl: ViewController,
+ private _config: Config,
+ private _elementRef: ElementRef,
+ params: NavParams,
+ renderer: Renderer
+ ) {
+
+ this.d = params.data;
+ this.created = Date.now();
+
+ if (this.d.cssClass) {
+ renderer.setElementClass(_elementRef.nativeElement, this.d.cssClass, true);
+ }
+
+ this.id = (++toastIds);
+
+ if (this.d.message) {
+ this.hdrId = 'acst-hdr-' + this.id;
+ }
+ }
+
+ onPageDidEnter() {
+ let activeElement: any = document.activeElement;
+ if (document.activeElement) {
+ activeElement.blur();
+ }
+
+ let focusableEle = this._elementRef.nativeElement.querySelector('button');
+
+ if (focusableEle) {
+ focusableEle.focus();
+ }
+
+ // if there's a `duration` set, automatically dismiss.
+ setTimeout(() => this.dismiss('backdrop'), this.d.duration ? this.d.duration : 3000)
+
+ }
+
+ click(button, dismissDelay?) {
+ if (!this.isEnabled()) {
+ return;
+ }
+ let shouldDismiss = true;
+
+ if (shouldDismiss) {
+ setTimeout(() => {
+ this.dismiss(button.role);
+ }, dismissDelay || this._config.get('pageTransitionDelay'));
+ }
+ }
+
+ bdClick() {
+ if (this.isEnabled() && this.d.enableBackdropDismiss) {
+ this.dismiss('backdrop');
+ }
+ }
+
+ dismiss(role): Promise {
+ return this._viewCtrl.dismiss(null, role);
+ }
+
+ isEnabled() {
+ let tm = this._config.getNumber('overlayCreatedDiff', 750);
+ return (this.created + tm < Date.now());
+ }
+
+}
+
+export interface ToastOptions {
+ title?: string;
+ cssClass?: string;
+ buttons?: Array;
+ duration?: number,
+ showCloseButton?: boolean;
+ closeButtonText?: string;
+ enableBackdropDismiss?: boolean;
+}
+
+class ToastSlideIn extends Transition {
+ constructor(enteringView, leavingView, opts: TransitionOptions) {
+ super(opts);
+
+ let ele = enteringView.pageRef().nativeElement;
+ let wrapper = new Animation(ele.querySelector('.toast-wrapper'));
+
+ wrapper.fromTo('translateY', '100%', '0%');
+ this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(wrapper);
+ }
+}
+
+class ToastSlideOut extends Transition {
+ constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+ super(opts);
+
+ let ele = leavingView.pageRef().nativeElement;
+ let wrapper = new Animation(ele.querySelector('.toast-wrapper'));
+
+ wrapper.fromTo('translateY', '0%', '100%');
+ this.easing('cubic-bezier(.36,.66,.04,1)').duration(300).add(wrapper);
+ }
+}
+
+class ToastMdSlideIn extends Transition {
+ constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+ super(opts);
+
+ let ele = enteringView.pageRef().nativeElement;
+ let backdrop = new Animation(ele.querySelector('.backdrop'));
+ let wrapper = new Animation(ele.querySelector('.toast-wrapper'));
+
+ backdrop.fromTo('opacity', 0, 0);
+ wrapper.fromTo('translateY', '100%', '0%');
+ this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(backdrop).add(wrapper)
+ }
+}
+
+class ToastMdSlideOut extends Transition {
+ constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+ super(opts);
+
+ let ele = leavingView.pageRef().nativeElement;
+ let wrapper = new Animation(ele.querySelector('.toast-wrapper'));
+ let backdrop = new Animation(ele.querySelector('.backdrop'));
+
+ wrapper.fromTo('translateY', '0%', '100%');
+ backdrop.fromTo('opacity', 0, 0);
+ this.easing('cubic-bezier(.36,.66,.04,1)').duration(450).add(backdrop).add(wrapper);
+ }
+}
+
+Transition.register('toast-slide-in', ToastSlideIn);
+Transition.register('toast-slide-out', ToastSlideOut);
+Transition.register('toast-md-slide-in', ToastMdSlideIn);
+Transition.register('toast-md-slide-out', ToastMdSlideOut);
+
+
+let toastIds = -1;
diff --git a/ionic/config/modes.ts b/ionic/config/modes.ts
index 590952479ab..55901bfcf8a 100644
--- a/ionic/config/modes.ts
+++ b/ionic/config/modes.ts
@@ -9,6 +9,9 @@ Config.setModeConfig('ios', {
actionSheetEnter: 'action-sheet-slide-in',
actionSheetLeave: 'action-sheet-slide-out',
+ toastEnter: 'toast-slide-in',
+ toastLeave: 'toast-slide-out',
+
alertEnter: 'alert-pop-in',
alertLeave: 'alert-pop-out',
@@ -41,6 +44,9 @@ Config.setModeConfig('md', {
actionSheetEnter: 'action-sheet-md-slide-in',
actionSheetLeave: 'action-sheet-md-slide-out',
+ toastEnter: 'toast-md-slide-in',
+ toastLeave: 'toast-md-slide-out',
+
alertEnter: 'alert-md-pop-in',
alertLeave: 'alert-md-pop-out',
@@ -76,6 +82,9 @@ Config.setModeConfig('wp', {
actionSheetEnter: 'action-sheet-wp-slide-in',
actionSheetLeave: 'action-sheet-wp-slide-out',
+ toastEnter: 'toast-wp-slide-in',
+ toastLeave: 'toast-wp-slide-out',
+
alertEnter: 'alert-wp-pop-in',
alertLeave: 'alert-wp-pop-out',