From 72362791d2f527065821b70604415578c61689b2 Mon Sep 17 00:00:00 2001
From: Justin DuJardin <justin@dujardinconsulting.com>
Date: Fri, 25 Dec 2015 14:09:16 -0800
Subject: [PATCH] feat(dialog): support basic alert and confirm dialogs

---
 examples/all.ts                               |   2 +
 examples/components/dialog/basic_usage.html   |  33 ++
 examples/components/dialog/basic_usage.scss   |  14 +
 examples/components/dialog/basic_usage.ts     | 117 +++++++
 ng2-material/all.scss                         |   1 +
 ng2-material/all.ts                           |   2 +
 ng2-material/components/dialog/dialog.scss    |  94 +++++-
 ng2-material/components/dialog/dialog.ts      | 296 +++++-------------
 .../components/dialog/dialog_basic.ts         |  31 ++
 .../components/dialog/dialog_config.ts        |  59 ++++
 .../components/dialog/dialog_container.ts     |  64 ++++
 ng2-material/components/dialog/dialog_ref.ts  |  82 +++++
 ng2-material/core/style/shadows.scss          |  33 ++
 13 files changed, 595 insertions(+), 233 deletions(-)
 create mode 100644 examples/components/dialog/basic_usage.html
 create mode 100644 examples/components/dialog/basic_usage.scss
 create mode 100644 examples/components/dialog/basic_usage.ts
 create mode 100644 ng2-material/components/dialog/dialog_basic.ts
 create mode 100644 ng2-material/components/dialog/dialog_config.ts
 create mode 100644 ng2-material/components/dialog/dialog_container.ts
 create mode 100644 ng2-material/components/dialog/dialog_ref.ts

diff --git a/examples/all.ts b/examples/all.ts
index abd884e3..9d113712 100644
--- a/examples/all.ts
+++ b/examples/all.ts
@@ -7,6 +7,7 @@ import CardBasicUsage from './components/card/basic_usage';
 import CardInlineActions from './components/card/inline_actions';
 import ButtonBasicUsage from './components/button/basic_usage';
 import CardActionButtons from './components/card/action_buttons';
+import DialogBasicUsage from './components/dialog/basic_usage';
 import ToolbarBasicUsage from './components/toolbar/basic_usage';
 import ToolbarScrollShrink from './components/toolbar/scroll_shrink';
 import ProgressLinearBasicUsage from './components/progress_linear/basic_usage';
@@ -21,6 +22,7 @@ import TabsDynamicTabs from './components/tabs/dynamic_tabs';
 export const DEMO_DIRECTIVES: Type[] = CONST_EXPR([
   CardBasicUsage, CardInlineActions, CardActionButtons,
   ButtonBasicUsage,
+  DialogBasicUsage,
   RadioBasicUsage,
   SwitchBasicUsage,
   TabsDynamicHeight,
diff --git a/examples/components/dialog/basic_usage.html b/examples/components/dialog/basic_usage.html
new file mode 100644
index 00000000..6a1f1f7a
--- /dev/null
+++ b/examples/components/dialog/basic_usage.html
@@ -0,0 +1,33 @@
+<div class="md-padding" id="popupContainer">
+  <p class="inset">
+    Open a dialog over the app's content. Press escape or click outside to close the dialog and
+    send focus back to the triggering button.
+  </p>
+
+  <div class="dialog-demo-content" layout="row" layout-wrap layout-margin>
+    <button md-raised-button class="md-primary" (click)="showAlert($event)" flex="100" flex-gt-md="auto">
+      Alert Dialog
+    </button>
+    <button md-raised-button class="md-primary" (click)="showConfirm($event)" flex="100" flex-gt-md="auto">
+      Confirm Dialog
+    </button>
+    <button md-raised-button class="md-primary" (click)="showAdvanced($event)" flex="100" flex-gt-md="auto">
+      Custom Dialog
+    </button>
+    <div hide-gt-sm layout="row" layout-align="center center" flex="100">
+        <md-checkbox [(checked)]="customFullscreen" aria-label="Fullscreen Custom Dialog">Custom Dialog Fullscreen</md-checkbox>
+      </div>
+    <button md-raised-button class="md-primary" (click)="showTabDialog($event)" flex="100" flex-gt-md="auto">
+      Tab Dialog
+    </button>
+  </div>
+  <p class="footer">Note: The <b>Confirm</b> dialog does not use <code>config.clickOutsideToClose(true)</code>.</p>
+
+  <div *ngIf="status">
+    <br/>
+    <b layout="row" layout-align="center center" class="md-padding">
+      {{status}}
+    </b>
+  </div>
+
+</div>
diff --git a/examples/components/dialog/basic_usage.scss b/examples/components/dialog/basic_usage.scss
new file mode 100644
index 00000000..1e721c78
--- /dev/null
+++ b/examples/components/dialog/basic_usage.scss
@@ -0,0 +1,14 @@
+#popupContainer {
+    position:relative;
+}
+
+.footer {
+    width:100%;
+    text-align: center;
+    margin-left:20px;
+}
+ .footer, .footer > code {
+    font-size: 0.8em;
+    margin-top:50px;
+}
+
diff --git a/examples/components/dialog/basic_usage.ts b/examples/components/dialog/basic_usage.ts
new file mode 100644
index 00000000..d42215a9
--- /dev/null
+++ b/examples/components/dialog/basic_usage.ts
@@ -0,0 +1,117 @@
+import {View, Component} from 'angular2/core';
+import {MATERIAL_DIRECTIVES,MdDialog} from 'ng2-material/all';
+import {ElementRef} from "angular2/core";
+import {Input} from "angular2/core";
+import {DOM} from "angular2/src/platform/dom/dom_adapter";
+import {MdDialogConfig} from "ng2-material/components/dialog/dialog_config";
+import {MdDialogBasic} from "ng2-material/components/dialog/dialog_basic";
+import {MdDialogRef} from "ng2-material/components/dialog/dialog_ref";
+
+
+function hasMedia(size: string) {
+  // TODO: Implement as $mdMedia
+  return true;
+}
+
+@Component({selector: 'dialog-basic-usage'})
+@View({
+  templateUrl: 'examples/components/dialog/basic_usage.html',
+  styleUrls: ['examples/components/dialog/basic_usage.css'],
+  directives: [MATERIAL_DIRECTIVES]
+})
+export default class DialogBasicUsage {
+
+  status = '  ';
+  customFullscreen = hasMedia('xs') || hasMedia('sm');
+
+  constructor(public dialog: MdDialog, public element: ElementRef) {
+
+  }
+
+  showAlert(ev) {
+    let config = new MdDialogConfig()
+      .parent(DOM.query('#popupContainer'))
+      .textContent('You can specify some description text in here')
+      .title('This is an alert title')
+      .ok('Got it!')
+      .targetEvent(ev);
+    this.dialog.open(MdDialogBasic, this.element, config);
+    //// Appending dialog to document.body to cover sidenav in docs app
+    //// Modal dialogs should fully cover application
+    //// to prevent interaction outside of dialog
+    //this.dialog.show(
+    //  this.dialog.alert()
+    //    .parent(angular.element(document.querySelector('#popupContainer')))
+    //    .clickOutsideToClose(true)
+    //    .title('This is an alert title')
+    //    .textContent('You can specify some description text in here.')
+    //    .ariaLabel('Alert Dialog Demo')
+    //    .ok('Got it!')
+    //    .targetEvent(ev)
+    //);
+  };
+
+  showConfirm(ev) {
+    let config = new MdDialogConfig()
+      .textContent('All of the banks have agreed to forgive you your debts.')
+      .clickOutsideToClose(false)
+      .title('Would you like to delete your debt?')
+      .ariaLabel('Lucky day')
+      .ok('Please do it!')
+      .cancel('Sounds like a scam')
+      .targetEvent(ev);
+    this.dialog
+      .open(MdDialogBasic, this.element, config)
+      .then((ref: MdDialogRef) => {
+        ref.whenClosed.then((result) => {
+          if (result) {
+            this.status = 'You decided to get rid of your debt.';
+          }
+          else {
+            this.status = 'You decided to keep your debt.';
+          }
+        })
+      });
+  };
+
+  showAdvanced(ev) {
+    //var useFullScreen = ($mdMedia('sm') || $mdMedia('xs')) && this.customFullscreen;
+    //
+    //this.dialog.show({
+    //    controller: DialogController,
+    //    templateUrl: 'dialog1.tmpl.html',
+    //    parent: angular.element(document.body),
+    //    targetEvent: ev,
+    //    clickOutsideToClose: true,
+    //    fullscreen: useFullScreen
+    //  })
+    //  .then((answer) => {
+    //    $scope.status = 'You said the information was "' + answer + '".';
+    //  }, () =>{
+    //    $scope.status = 'You cancelled the dialog.';
+    //  });
+    //
+    //
+    //$scope.$watch(() =>{
+    //  return $mdMedia('xs') || $mdMedia('sm');
+    //}, (wantsFullScreen) => {
+    //  this.customFullscreen = (wantsFullScreen === true);
+    //});
+
+  };
+
+  showTabDialog(ev) {
+    //this.dialog.show({
+    //    controller: DialogController,
+    //    templateUrl: 'tabDialog.tmpl.html',
+    //    parent: angular.element(document.body),
+    //    targetEvent: ev,
+    //    clickOutsideToClose: true
+    //  })
+    //  .then((answer) => {
+    //    this.status = 'You said the information was "' + answer + '".';
+    //  }, () =>{
+    //    this.status = 'You cancelled the dialog.';
+    //  });
+  };
+}
diff --git a/ng2-material/all.scss b/ng2-material/all.scss
index a74753bc..c16d3cfe 100644
--- a/ng2-material/all.scss
+++ b/ng2-material/all.scss
@@ -6,6 +6,7 @@
 @import "core/style/default-theme";
 
 
+@import "components/backdrop/backdrop";
 @import "components/button/button";
 @import "components/card/card";
 @import "components/content/content";
diff --git a/ng2-material/all.ts b/ng2-material/all.ts
index 03c7df31..b178344b 100644
--- a/ng2-material/all.ts
+++ b/ng2-material/all.ts
@@ -11,6 +11,7 @@ import {MdContent} from './components/content/content';
 export * from './components/content/content';
 
 export * from './components/dialog/dialog';
+import {MdDialog} from './components/dialog/dialog';
 
 import {MdDivider} from './components/divider/divider';
 export * from './components/divider/divider';
@@ -109,6 +110,7 @@ export class MaterialTemplateResolver extends UrlResolver {
  * Collection of Material Design component providers.
  */
 export const MATERIAL_PROVIDERS: any[] = [
+  MdDialog,
   MdRadioDispatcher,
   provide(UrlResolver, {useValue: new MaterialTemplateResolver()})
 ];
diff --git a/ng2-material/components/dialog/dialog.scss b/ng2-material/components/dialog/dialog.scss
index 50c6771c..6a481158 100644
--- a/ng2-material/components/dialog/dialog.scss
+++ b/ng2-material/components/dialog/dialog.scss
@@ -1,27 +1,91 @@
+@import "../../core/style/variables";
+@import "../../core/style/shadows";
+@import "../../core/style/default-theme";
+
 .md-dialog {
-  position: absolute;
+  position: fixed;
   z-index: 80;
 
   /** Center the dialog. */
   top: 50%;
   left: 50%;
-  transform: translate(-50%, -50%);
+  min-width: 300px;
+  min-height: 100px;
+
+  padding: $baseline-grid * 3;
+
+  box-shadow: $whiteframe-shadow-13dp;
+  display: flex;
+  flex-direction: column;
+
+  opacity: 0;
+  transition: $swift-ease-out;
+  transform: translate3d(-50%, -50%, 0) scale(0.2);
+
+  order: 1;
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+
+  &:not([layout=row]) > * > *:first-child:not(.md-subheader) {
+    margin-top: 0;
+  }
+
+  &:focus {
+    outline: none;
+  }
+
+  &.md-active {
+    opacity: 1;
+    transition: $swift-ease-out;
+    transform: translate3d(-50%, -50%, 0) scale(1.0);
+  }
+
+  &.md-dialog-absolute {
+    position: absolute;
+  }
 
-  width: 300px;
-  height: 300px;
+  .md-actions, md-dialog-actions {
+    display: flex;
+    order: 2;
+    box-sizing: border-box;
+    align-items: center;
+    justify-content: flex-end;
+    padding-top: $baseline-grid * 3;
+    padding-right: $baseline-grid;
+    padding-left: $baseline-grid * 2;
 
-  background-color: white;
-  border: 1px solid black;
-  box-shadow: 0 4px 4px;;
+    // Align md-actions outside of the padding of .md-dialog
+    margin-bottom: -$baseline-grid * 3;
+    margin-left: -$baseline-grid * 3;
+    margin-right: -$baseline-grid * 3;
+
+    right: -$baseline-grid * 3;
+    min-height: $baseline-grid * 6.5;
+    overflow: hidden;
+
+    [md-button], [md-raised-button] {
+      margin-bottom: $baseline-grid;
+      margin-left: $baseline-grid;
+      margin-right: 0;
+      margin-top: $baseline-grid;
+    }
+  }
 
-  padding: 20px;
 }
 
-.md-backdrop {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(0, 0, 0, 0.12);
+// Theme
+
+$dialog-border-radius: 4px !default;
+
+.md-dialog {
+  border-radius: $dialog-border-radius;
+  background-color: md-color($md-background, lighter); //'{{background-color}}';
+
+  &.md-content-overflow {
+    .md-actions, md-dialog-actions {
+      border-top-color: md-color($md-foreground, divider); //'{{foreground-4}}';
+    }
+  }
 }
+
+
diff --git a/ng2-material/components/dialog/dialog.ts b/ng2-material/components/dialog/dialog.ts
index 446fccaf..c817b00e 100644
--- a/ng2-material/components/dialog/dialog.ts
+++ b/ng2-material/components/dialog/dialog.ts
@@ -1,26 +1,23 @@
 import {
-  bind,
   provide,
-  forwardRef,
-  Component,
   ComponentRef,
-  Directive,
   DynamicComponentLoader,
   ElementRef,
-  Host,
   Injectable,
   ResolvedProvider,
-  SkipSelf,
-  Injector,
-  View,
-  ViewEncapsulation
+  Injector
 } from 'angular2/core';
 
-import {ObservableWrapper, Promise, PromiseWrapper} from 'angular2/src/facade/async';
+import {Promise} from 'angular2/src/facade/async';
 import {isPresent, Type} from 'angular2/src/facade/lang';
-import {DOM} from 'angular2/src/platform/dom/dom_adapter';
-import {MouseEvent, KeyboardEvent} from 'angular2/src/facade/browser';
-import {KeyCodes} from '../../core/key_codes';
+import {MdDialogRef} from './dialog_ref';
+import {MdDialogConfig} from './dialog_config';
+import {MdDialogContainer} from './dialog_container';
+import {MdBackdrop} from "../backdrop/backdrop";
+import {DOM} from "angular2/src/platform/dom/dom_adapter";
+import {Renderer} from "angular2/core";
+import {Animate} from '../../core/util/animate';
+
 
 // TODO(jelbourn): Opener of dialog can control where it is rendered.
 // TODO(jelbourn): body scrolling is disabled while dialog is open.
@@ -31,17 +28,26 @@ import {KeyCodes} from '../../core/key_codes';
 // TODO(jelbourn): Pre-built `alert` and `confirm` dialogs.
 // TODO(jelbourn): Animate dialog out of / into opening element.
 
+/**
+ * Any components that are launched through MdDialog should implement this
+ * interface. The `dialog` will be injected into the component instance to
+ * allow dismissing or interacting with the dialog reference.
+ */
+export interface IDialogComponent {
+  dialog: MdDialogRef;
+}
+
 /**
  * Service for opening modal dialogs.
  */
 @Injectable()
 export class MdDialog {
-  componentLoader: DynamicComponentLoader;
-
-  constructor(loader: DynamicComponentLoader) {
-    this.componentLoader = loader;
+  constructor(public componentLoader: DynamicComponentLoader,
+              public renderer: Renderer) {
   }
 
+  private _defaultContainer = DOM.query('body');
+
   /**
    * Opens a modal dialog.
    * @param type The component to open.
@@ -52,66 +58,74 @@ export class MdDialog {
   open(type: Type, elementRef: ElementRef, options: MdDialogConfig = null): Promise<MdDialogRef> {
     var config = isPresent(options) ? options : new MdDialogConfig();
 
+
     // Create the dialogRef here so that it can be injected into the content component.
     var dialogRef = new MdDialogRef();
+    config.dialogRef(dialogRef);
 
     var bindings = Injector.resolve([provide(MdDialogRef, {useValue: dialogRef})]);
 
-    var backdropRefPromise = this._openBackdrop(elementRef, bindings);
+    var backdropRefPromise = this._openBackdrop(elementRef, bindings, options);
 
     // First, load the MdDialogContainer, into which the given component will be loaded.
     return this.componentLoader.loadNextToLocation(MdDialogContainer, elementRef)
-        .then(containerRef => {
-          // TODO(tbosch): clean this up when we have custom renderers
-          // (https://github.com/angular/angular/issues/1807)
-          // TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
-          // directly on the document body (also needed for web workers stuff).
-          // Create a DOM node to serve as a physical host element for the dialog.
-          var dialogElement = containerRef.location.nativeElement;
-          DOM.appendChild(DOM.query('body'), dialogElement);
-
-          // TODO(jelbourn): Do this with hostProperties (or another rendering abstraction) once
-          // ready.
-          if (isPresent(config.width)) {
-            DOM.setStyle(dialogElement, 'width', config.width);
-          }
-          if (isPresent(config.height)) {
-            DOM.setStyle(dialogElement, 'height', config.height);
-          }
-
-          dialogRef.containerRef = containerRef;
-
-          // Now load the given component into the MdDialogContainer.
-          return this.componentLoader.loadNextToLocation(type, containerRef.instance.contentRef,
-                                                         bindings)
-              .then(contentRef => {
-
-                // Wrap both component refs for the container and the content so that we can return
-                // the `instance` of the content but the dispose method of the container back to the
-                // opener.
-                dialogRef.contentRef = contentRef;
-                containerRef.instance.dialogRef = dialogRef;
-
-                backdropRefPromise.then(backdropRef => {
-                  dialogRef.whenClosed.then((_) => { backdropRef.dispose(); });
-                });
-
-                return dialogRef;
+      .then(containerRef => {
+        // TODO(tbosch): clean this up when we have custom renderers
+        // (https://github.com/angular/angular/issues/1807)
+        // TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
+        // directly on the document body (also needed for web workers stuff).
+        // Create a DOM node to serve as a physical host element for the dialog.
+        var dialogElement = containerRef.location.nativeElement;
+
+        DOM.appendChild(config.container || this._defaultContainer, dialogElement);
+
+        this.renderer.setElementClass(containerRef.location, 'md-dialog-absolute', !!options.container);
+
+        if (isPresent(config.width)) {
+          this.renderer.setElementStyle(containerRef.location, 'width', config.width);
+        }
+        if (isPresent(config.height)) {
+          this.renderer.setElementStyle(containerRef.location, 'height', config.height);
+        }
+
+        dialogRef.containerRef = containerRef;
+
+        // Now load the given component into the MdDialogContainer.
+        return this.componentLoader.loadNextToLocation(type, containerRef.instance.contentRef, bindings)
+          .then((contentRef: ComponentRef) => {
+            Object.keys(config.context).forEach((key) => {
+              contentRef.instance[key] = config.context[key];
+            });
+
+            // Wrap both component refs for the container and the content so that we can return
+            // the `instance` of the content but the dispose method of the container back to the
+            // opener.
+            dialogRef.contentRef = contentRef;
+            containerRef.instance.dialogRef = dialogRef;
+
+            backdropRefPromise.then(backdropRef => {
+              dialogRef.backdropRef = backdropRef;
+              dialogRef.whenClosed.then((_) => {
+                backdropRef.dispose();
               });
-        });
+            });
+
+            return Animate.enter(dialogElement, 'md-active').then(() => dialogRef);
+          });
+      });
   }
 
   /** Loads the dialog backdrop (transparent overlay over the rest of the page). */
-  _openBackdrop(elementRef: ElementRef, bindings: ResolvedProvider[]): Promise<ComponentRef> {
+  _openBackdrop(elementRef: ElementRef, bindings: ResolvedProvider[], options: MdDialogConfig): Promise<ComponentRef> {
     return this.componentLoader.loadNextToLocation(MdBackdrop, elementRef, bindings)
-        .then((componentRef) => {
-          // TODO(tbosch): clean this up when we have custom renderers
-          // (https://github.com/angular/angular/issues/1807)
-          var backdropElement = componentRef.location.nativeElement;
-          DOM.addClass(backdropElement, 'md-backdrop');
-          DOM.appendChild(DOM.query('body'), backdropElement);
-          return componentRef;
-        });
+      .then((componentRef) => {
+        let backdrop: MdBackdrop = componentRef.instance;
+        backdrop.clickClose = options.clickClose;
+        this.renderer.setElementClass(componentRef.location, 'md-backdrop', true);
+        this.renderer.setElementClass(componentRef.location, 'md-backdrop-absolute', !!options.container);
+        DOM.appendChild(options.container || this._defaultContainer, componentRef.location.nativeElement);
+        return componentRef;
+      });
   }
 
   alert(message: string, okMessage: string): Promise<any> {
@@ -122,157 +136,3 @@ export class MdDialog {
     throw 'Not implemented';
   }
 }
-
-
-/**
- * Reference to an opened dialog.
- */
-export class MdDialogRef {
-  // Reference to the MdDialogContainer component.
-  containerRef: ComponentRef;
-
-  // Reference to the Component loaded as the dialog content.
-  _contentRef: ComponentRef;
-
-  // Whether the dialog is closed.
-  isClosed: boolean;
-
-  // Deferred resolved when the dialog is closed. The promise for this deferred is publicly exposed.
-  whenClosedDeferred: any;
-
-  // Deferred resolved when the content ComponentRef is set. Only used internally.
-  contentRefDeferred: any;
-
-  constructor() {
-    this._contentRef = null;
-    this.containerRef = null;
-    this.isClosed = false;
-
-    this.contentRefDeferred = PromiseWrapper.completer();
-    this.whenClosedDeferred = PromiseWrapper.completer();
-  }
-
-  set contentRef(value: ComponentRef) {
-    this._contentRef = value;
-    this.contentRefDeferred.resolve(value);
-  }
-
-  /** Gets the component instance for the content of the dialog. */
-  get instance() {
-    if (isPresent(this._contentRef)) {
-      return this._contentRef.instance;
-    }
-
-    // The only time one could attempt to access this property before the value is set is if an
-    // access occurs during
-    // the constructor of the very instance they are trying to get (which is much more easily
-    // accessed as `this`).
-    throw "Cannot access dialog component instance *from* that component's constructor.";
-  }
-
-
-  /** Gets a promise that is resolved when the dialog is closed. */
-  get whenClosed(): Promise<any> {
-    return this.whenClosedDeferred.promise;
-  }
-
-  /** Closes the dialog. This operation is asynchronous. */
-  close(result: any = null) {
-    this.contentRefDeferred.promise.then((_) => {
-      if (!this.isClosed) {
-        this.isClosed = true;
-        this.containerRef.dispose();
-        this.whenClosedDeferred.resolve(result);
-      }
-    });
-  }
-}
-
-/** Confiuration for a dialog to be opened. */
-export class MdDialogConfig {
-  width: string;
-  height: string;
-
-  constructor() {
-    // Default configuration.
-    this.width = null;
-    this.height = null;
-  }
-}
-
-/**
- * Container for user-provided dialog content.
- */
-@Component({
-  selector: 'md-dialog-container',
-  host: {
-    'class': 'md-dialog',
-    'tabindex': '0',
-    '(body:keydown)': 'documentKeypress($event)',
-  },
-})
-@View({
-  encapsulation: ViewEncapsulation.None,
-  template: `
-    <md-dialog-content></md-dialog-content>
-    <div tabindex="0" (focus)="wrapFocus()"></div>`,
-  directives: [forwardRef(() => MdDialogContent)]
-})
-class MdDialogContainer {
-  // Ref to the dialog content. Used by the DynamicComponentLoader to load the dialog content.
-  contentRef: ElementRef;
-
-  // Ref to the open dialog. Used to close the dialog based on certain events.
-  dialogRef: MdDialogRef;
-
-  constructor() {
-    this.contentRef = null;
-    this.dialogRef = null;
-  }
-
-  wrapFocus() {
-    // Return the focus to the host element. Blocked on #1251.
-  }
-
-  documentKeypress(event: KeyboardEvent) {
-    if (event.keyCode == KeyCodes.ESCAPE) {
-      this.dialogRef.close();
-    }
-  }
-}
-
-/**
- * Simple decorator used only to communicate an ElementRef to the parent MdDialogContainer as the
- * location
- * for where the dialog content will be loaded.
- */
-@Directive({
-  selector: 'md-dialog-content',
-})
-class MdDialogContent {
-  constructor(@Host() @SkipSelf() dialogContainer: MdDialogContainer, elementRef: ElementRef) {
-    dialogContainer.contentRef = elementRef;
-  }
-}
-
-/** Component for the dialog "backdrop", a transparent overlay over the rest of the page. */
-@Component({
-  selector: 'md-backdrop',
-  host: {
-    '(click)': 'onClick()',
-  },
-})
-@View({template: '', encapsulation: ViewEncapsulation.None})
-class MdBackdrop {
-  dialogRef: MdDialogRef;
-
-  constructor(dialogRef: MdDialogRef) {
-    this.dialogRef = dialogRef;
-  }
-
-  onClick() {
-    // TODO(jelbourn): Use MdDialogConfig to capture option for whether dialog should close on
-    // clicking outside.
-    this.dialogRef.close();
-  }
-}
diff --git a/ng2-material/components/dialog/dialog_basic.ts b/ng2-material/components/dialog/dialog_basic.ts
new file mode 100644
index 00000000..726ab826
--- /dev/null
+++ b/ng2-material/components/dialog/dialog_basic.ts
@@ -0,0 +1,31 @@
+
+import {NgIf} from "angular2/common";
+import {MdButton} from "ng2-material/components/button/button";
+import {View} from "angular2/core";
+import {Component} from "angular2/core";
+import {IDialogComponent} from "./dialog";
+import {MdDialogRef} from "ng2-material/components/dialog/dialog_ref";
+import {Input} from "angular2/core";
+@Component({selector: 'md-dialog-basic'})
+@View({
+  template: `
+  <h2>{{ title }}</h2>
+  <p>{{ textContent }}</p>
+  <md-dialog-actions>
+    <button md-button *ngIf="cancel != ''" type="button" (click)="dialog.close(false)">
+      <span>{{ cancel }}</span>
+    </button>
+    <button md-button *ngIf="ok != ''" class="md-primary" type="button" (click)="dialog.close(true)">
+      <span>{{ ok }}</span>
+    </button>
+  </md-dialog-actions>`,
+  directives: [MdButton, NgIf]
+})
+export class MdDialogBasic implements IDialogComponent {
+  dialog: MdDialogRef;
+  @Input() title: string = '';
+  @Input() textContent: string = '';
+  @Input() cancel: string = '';
+  @Input() ok: string = '';
+  @Input() type: string = 'alert';
+}
diff --git a/ng2-material/components/dialog/dialog_config.ts b/ng2-material/components/dialog/dialog_config.ts
new file mode 100644
index 00000000..d317dd34
--- /dev/null
+++ b/ng2-material/components/dialog/dialog_config.ts
@@ -0,0 +1,59 @@
+
+
+import {MdDialogRef} from "./dialog_ref";
+
+/** Configuration for a dialog to be opened. */
+export class MdDialogConfig {
+  width: string = null;
+  height: string = null;
+  container: HTMLElement = null;
+  sourceEvent: Event = null;
+  clickClose: boolean = true;
+  context: any = {};
+
+  parent(element: HTMLElement): MdDialogConfig {
+    this.container = element;
+    return this;
+  }
+
+  clickOutsideToClose(enabled: boolean): MdDialogConfig {
+    this.clickClose = enabled;
+    return this;
+  }
+
+  title(text: string): MdDialogConfig {
+    this.context.title = text;
+    return this;
+  }
+
+  textContent(text: string): MdDialogConfig {
+    this.context.textContent = text;
+    return this;
+  }
+
+  ariaLabel(text: string): MdDialogConfig {
+    this.context.ariaLabel = text;
+    return this;
+  }
+
+  ok(text: string): MdDialogConfig {
+    this.context.ok = text;
+    return this;
+  }
+
+  cancel(text: string): MdDialogConfig {
+    this.context.cancel = text;
+    return this;
+  }
+
+  targetEvent(ev: Event): MdDialogConfig {
+    this.sourceEvent = ev;
+    return this;
+  }
+
+  dialogRef(ref: MdDialogRef): MdDialogConfig {
+    this.context.dialog = ref;
+    return this;
+  }
+
+}
diff --git a/ng2-material/components/dialog/dialog_container.ts b/ng2-material/components/dialog/dialog_container.ts
new file mode 100644
index 00000000..aa403142
--- /dev/null
+++ b/ng2-material/components/dialog/dialog_container.ts
@@ -0,0 +1,64 @@
+import {ViewEncapsulation} from "angular2/core";
+import {View} from "angular2/core";
+import {Component} from "angular2/core";
+import {ElementRef} from "angular2/core";
+import {MdDialogRef} from "./dialog_ref";
+import {KeyCodes} from "../../core/key_codes";
+import {forwardRef} from "angular2/core";
+import {Directive} from "angular2/core";
+import {Host} from "angular2/core";
+import {SkipSelf} from "angular2/core";
+
+/**
+ * Container for user-provided dialog content.
+ */
+@Component({
+  selector: 'md-dialog-container',
+  host: {
+    'class': 'md-dialog',
+    'tabindex': '0',
+    '(body:keydown)': 'documentKeypress($event)',
+  },
+})
+@View({
+  encapsulation: ViewEncapsulation.None,
+  template: `
+    <md-dialog-content></md-dialog-content>
+    <div tabindex="0" (focus)="wrapFocus()"></div>`,
+  directives: [forwardRef(() => MdDialogContent)]
+})
+export class MdDialogContainer {
+  // Ref to the dialog content. Used by the DynamicComponentLoader to load the dialog content.
+  contentRef: ElementRef;
+
+  // Ref to the open dialog. Used to close the dialog based on certain events.
+  dialogRef: MdDialogRef;
+
+  constructor() {
+    this.contentRef = null;
+    this.dialogRef = null;
+  }
+
+  wrapFocus() {
+    // Return the focus to the host element. Blocked on #1251.
+  }
+
+  documentKeypress(event: KeyboardEvent) {
+    if (event.keyCode == KeyCodes.ESCAPE) {
+      this.dialogRef.close();
+    }
+  }
+}
+
+/**
+ * Simple decorator used only to communicate an ElementRef to the parent MdDialogContainer as the
+ * location for where the dialog content will be loaded.
+ */
+@Directive({
+  selector: 'md-dialog-content'
+})
+export class MdDialogContent {
+  constructor(@Host() @SkipSelf() dialogContainer: MdDialogContainer, elementRef: ElementRef) {
+    dialogContainer.contentRef = elementRef;
+  }
+}
diff --git a/ng2-material/components/dialog/dialog_ref.ts b/ng2-material/components/dialog/dialog_ref.ts
new file mode 100644
index 00000000..735358c2
--- /dev/null
+++ b/ng2-material/components/dialog/dialog_ref.ts
@@ -0,0 +1,82 @@
+import {ComponentRef} from "angular2/core";
+import {PromiseWrapper} from "angular2/src/facade/promise";
+import {Animate} from "../../core/util/animate";
+import {isPresent} from "angular2/src/facade/lang";
+
+/**
+ * Reference to an opened dialog.
+ */
+export class MdDialogRef {
+  // Reference to the MdDialogContainer component.
+  containerRef: ComponentRef;
+
+  // Reference to the MdBackdrop component.
+  _backdropRef: ComponentRef;
+
+  // Reference to the Component loaded as the dialog content.
+  _contentRef: ComponentRef;
+
+  // Whether the dialog is closed.
+  isClosed: boolean;
+
+  // Deferred resolved when the dialog is closed. The promise for this deferred is publicly exposed.
+  whenClosedDeferred: any;
+
+  // Deferred resolved when the content ComponentRef is set. Only used internally.
+  contentRefDeferred: any;
+
+  constructor() {
+    this._contentRef = null;
+    this.containerRef = null;
+    this.isClosed = false;
+
+    this.contentRefDeferred = PromiseWrapper.completer();
+    this.whenClosedDeferred = PromiseWrapper.completer();
+  }
+
+  set backdropRef(value: ComponentRef) {
+    this._backdropRef = value;
+    let subscription = this._backdropRef.instance.onHiding.subscribe(() => {
+      this.close();
+      subscription.unsubscribe();
+    });
+  }
+
+  set contentRef(value: ComponentRef) {
+    this._contentRef = value;
+    this.contentRefDeferred.resolve(value);
+  }
+
+  /** Gets the component instance for the content of the dialog. */
+  get instance() {
+    if (isPresent(this._contentRef)) {
+      return this._contentRef.instance;
+    }
+
+    // The only time one could attempt to access this property before the value is set is if an
+    // access occurs during
+    // the constructor of the very instance they are trying to get (which is much more easily
+    // accessed as `this`).
+    throw "Cannot access dialog component instance *from* that component's constructor.";
+  }
+
+
+  /** Gets a promise that is resolved when the dialog is closed. */
+  get whenClosed(): Promise<any> {
+    return this.whenClosedDeferred.promise;
+  }
+
+  /** Closes the dialog. This operation is asynchronous. */
+  close(result: any = null): Promise<void> {
+    return Animate.leave(this.containerRef.location.nativeElement, 'md-active').then(() => {
+      this._backdropRef.instance.hide();
+      return this.contentRefDeferred.promise.then((_) => {
+        if (!this.isClosed) {
+          this.isClosed = true;
+          this.containerRef.dispose();
+          this.whenClosedDeferred.resolve(result);
+        }
+      });
+    });
+  }
+}
diff --git a/ng2-material/core/style/shadows.scss b/ng2-material/core/style/shadows.scss
index 24267e02..d71471eb 100644
--- a/ng2-material/core/style/shadows.scss
+++ b/ng2-material/core/style/shadows.scss
@@ -13,3 +13,36 @@ $md-shadow-bottom-z-2: 0 4px 8px 0 rgba(0, 0, 0, 0.4);
   box-shadow: $whiteframe-shadow-z2;
   z-index: $whiteframe-zindex-z2;
 }
+
+// Whiteframes
+
+$shadow-key-umbra-opacity:      0.2;
+$shadow-key-penumbra-opacity:   0.14;
+$shadow-ambient-shadow-opacity: 0.12;
+
+// NOTE(shyndman): gulp-sass seems to be failing if I split the shadow defs across
+//    multiple lines. Ugly. Sorry.
+$whiteframe-shadow-1dp: 0px 1px 3px 0px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 1px 1px 0px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 2px 1px -1px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-2dp: 0px 1px 5px 0px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 2px 2px 0px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 3px 1px -2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-3dp: 0px 1px 8px 0px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 3px 4px 0px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 3px 3px -2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-4dp: 0px 2px 4px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 4px 5px 0px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 1px 10px 0px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-5dp: 0px 3px 5px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 5px 8px 0px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 1px 14px 0px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-6dp: 0px 3px 5px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 6px 10px 0px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 1px 18px 0px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-7dp: 0px 4px 5px -2px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 7px 10px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 2px 16px 1px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-8dp: 0px 5px 5px -3px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 8px 10px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 3px 14px 2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-9dp: 0px 5px 6px -3px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 9px 12px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 3px 16px 2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-10dp: 0px 6px 6px -3px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 10px 14px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 4px 18px 3px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-11dp: 0px 6px 7px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 11px 15px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 4px 20px 3px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-12dp: 0px 7px 8px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 12px 17px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 5px 22px 4px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-13dp: 0px 7px 8px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 13px 19px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 5px 24px 4px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-14dp: 0px 7px 9px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 14px 21px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 5px 26px 4px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-15dp: 0px 8px 9px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 15px 22px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 6px 28px 5px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-16dp: 0px 8px 10px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 16px 24px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 6px 30px 5px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-17dp: 0px 8px 11px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 17px 26px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 6px 32px 5px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-18dp: 0px 9px 11px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 18px 28px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 7px 34px 6px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-19dp: 0px 9px 12px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 19px 29px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 7px 36px 6px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-20dp: 0px 10px 13px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 20px 31px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 8px 38px 7px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-21dp: 0px 10px 13px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 21px 33px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 8px 40px 7px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-22dp: 0px 10px 14px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 22px 35px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 8px 42px 7px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-23dp: 0px 11px 14px -7px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 23px 36px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 9px 44px 8px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;
+$whiteframe-shadow-24dp: 0px 11px 15px -7px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0px 24px 38px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0px 9px 46px 8px rgba(0, 0, 0, $shadow-ambient-shadow-opacity) !default;