Skip to content

Commit

Permalink
feat(dialog): add dialog content elements
Browse files Browse the repository at this point in the history
Adds the following dialog-specific directives:
* `md-dialog-close` - Closes the current dialog.
* `md-dialog-title` - Title of a dialog.
* `md-dialog-content` - Scrollable content for a dialog.
* `md-dialog-actions` - Container for the bottom buttons in a dialog.

Fixes angular#1624.
Fixes angular#2042.
  • Loading branch information
crisbeto committed Dec 6, 2016
1 parent 3fd3117 commit 2dfaa9b
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 69 deletions.
4 changes: 3 additions & 1 deletion src/demo-app/demo-app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {RouterModule} from '@angular/router';
import {MaterialModule} from '@angular/material';
import {DEMO_APP_ROUTES} from './demo-app/routes';
import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
import {JazzDialog, DialogDemo} from './dialog/dialog-demo';
import {JazzDialog, ContentElementDialog, DialogDemo} from './dialog/dialog-demo';
import {RippleDemo} from './ripple/ripple-demo';
import {IconDemo} from './icon/icon-demo';
import {GesturesDemo} from './gestures/gestures-demo';
Expand Down Expand Up @@ -61,6 +61,7 @@ import {PlatformDemo} from './platform/platform-demo';
IconDemo,
InputDemo,
JazzDialog,
ContentElementDialog,
ListDemo,
LiveAnnouncerDemo,
MdCheckboxDemoNestedChecklist,
Expand Down Expand Up @@ -92,6 +93,7 @@ import {PlatformDemo} from './platform/platform-demo';
entryComponents: [
DemoApp,
JazzDialog,
ContentElementDialog,
RotiniPanel,
ScienceJoke,
SpagettiPanel,
Expand Down
3 changes: 2 additions & 1 deletion src/demo-app/dialog/dialog-demo.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<h1>Dialog demo</h1>

<button md-raised-button color="primary" (click)="open()" [disabled]="dialogRef">Open dialog</button>
<button md-raised-button color="primary" (click)="openJazz()" [disabled]="dialogRef">Open dialog</button>
<button md-raised-button color="accent" (click)="openContentElement()">Open dialog with content elements</button>

<md-card class="demo-dialog-card">
<md-card-content>
Expand Down
47 changes: 46 additions & 1 deletion src/demo-app/dialog/dialog-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ export class DialogDemo {

constructor(public dialog: MdDialog) { }

open() {
openJazz() {
this.dialogRef = this.dialog.open(JazzDialog, this.config);

this.dialogRef.afterClosed().subscribe(result => {
this.lastCloseResult = result;
this.dialogRef = null;
});
}

openContentElement() {
this.dialog.open(ContentElementDialog, this.config);
}
}


Expand All @@ -48,3 +52,44 @@ export class JazzDialog {

constructor(public dialogRef: MdDialogRef<JazzDialog>) { }
}


@Component({
selector: 'demo-content-element-dialog',
styles: [
`img {
max-width: 100%;
}`
],
template: `
<h2 md-dialog-title>Neptune</h2>
<md-dialog-content>
<img src="https://upload.wikimedia.org/wikipedia/commons/5/56/Neptune_Full.jpg"/>
<p>
Neptune is the eighth and farthest known planet from the Sun in the Solar System. In the
Solar System, it is the fourth-largest planet by diameter, the third-most-massive planet,
and the densest giant planet. Neptune is 17 times the mass of Earth and is slightly more
massive than its near-twin Uranus, which is 15 times the mass of Earth and slightly larger
than Neptune. Neptune orbits the Sun once every 164.8 years at an average distance of 30.1
astronomical units (4.50×109 km). It is named after the Roman god of the sea and has the
astronomical symbol ♆, a stylised version of the god Neptune's trident.
</p>
</md-dialog-content>
<md-dialog-actions>
<button
md-raised-button
color="primary"
md-dialog-close>Close</button>
<a
md-button
color="primary"
href="https://en.wikipedia.org/wiki/Neptune"
target="_blank">Read more on Wikipedia</a>
</md-dialog-actions>
`
})
export class ContentElementDialog { }
29 changes: 21 additions & 8 deletions src/lib/dialog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ MdDialog is a service, which opens dialogs components in the view.

### Methods

| Name | Description |
| --- | --- |
| Name | Description |
| ---- | ----------- |
| `open(component: ComponentType<T>, config: MdDialogConfig): MdDialogRef<T>` | Creates and opens a dialog matching material spec. |
| `closeAll(): void` | Closes all of the dialogs that are currently open. |
| `closeTop(): void` | Closes the topmost of the open dialogs. |

### Config

| Key | Description |
| --- | --- |
| Key | Description |
| --- | ------------ |
| `role: DialogRole = 'dialog'` | The ARIA role of the dialog element. Possible values are `dialog` and `alertdialog`. Optional. |
| `disableClose: boolean = false` | Whether to prevent the user from closing a dialog by clicking on the backdrop or pressing escape. Optional. |
| `width: string = ''` | Width of the dialog. Takes any valid CSS value. Optional. |
Expand All @@ -26,11 +27,19 @@ A reference to the dialog created by the MdDialog `open` method.

### Methods

| Name | Description |
| --- | --- |
| Name | Description |
| ---- | ----------- |
| `close(dialogResult?: any)` | Closes the dialog, pushing a value to the afterClosed observable. |
| `afterClosed(): Observable<any>` | Returns an observable which will emit the dialog result, passed to the `close` method above. |

### Directives
| Name | Description |
| --- | ------------ |
| `md-dialog-title` | Marks the title of the dialog.
| `md-dialog-content` | Scrollable content of the dialog.
| `md-dialog-close` | When added to a `button`, makes the element act as a close button for the dialog.
| `md-dialog-actions` | Wrapper for the set of actions at the bottom of a dialog. Typically contains buttons.

## Example
The service can be injected in a component.

Expand Down Expand Up @@ -62,8 +71,12 @@ export class PizzaComponent {
@Component({
selector: 'pizza-dialog',
template: `
<button type="button" (click)="dialogRef.close('yes')">Yes</button>
<button type="button" (click)="dialogRef.close('no')">No</button>
<h1 md-dialog-title>Would you like to order pizza?</h1>
<md-dialog-actions>
<button (click)="dialogRef.close('yes')">Yes</button>
<button md-dialog-close>No</button>
</md-dialog-actions>
`
})
export class PizzaDialog {
Expand Down
19 changes: 0 additions & 19 deletions src/lib/dialog/dialog-container.scss

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/dialog/dialog-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import 'rxjs/add/operator/first';
moduleId: module.id,
selector: 'md-dialog-container, mat-dialog-container',
templateUrl: 'dialog-container.html',
styleUrls: ['dialog-container.css'],
styleUrls: ['dialog.css'],
host: {
'class': 'md-dialog-container',
'[attr.role]': 'dialogConfig?.role',
Expand Down
49 changes: 49 additions & 0 deletions src/lib/dialog/dialog-directives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {Directive} from '@angular/core';
import {MdDialog} from './dialog';


/**
* Button that will close the current dialog.
*/
@Directive({
selector: 'button[md-dialog-close]',
host: {
'(click)': 'dialog.closeTop()'
}
})
export class MdDialogClose {
constructor(public dialog: MdDialog) { }
}

/**
* Title of a dialog element. Stays fixed to the top of the dialog when scrolling.
*/
@Directive({
selector: '[md-dialog-title]',
host: {
role: 'heading'
}
})
export class MdDialogTitle { }


/**
* Scrollable content container of a dialog.
*/
@Directive({
selector: '[md-dialog-content], md-dialog-content',
host: {
role: 'main'
}
})
export class MdDialogContent { }


/**
* Container for the bottom action buttons in a dialog.
* Stays fixed to the bottom when scrolling.
*/
@Directive({
selector: '[md-dialog-actions], md-dialog-actions'
})
export class MdDialogActions { }
47 changes: 47 additions & 0 deletions src/lib/dialog/dialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@import '../core/style/elevation';


$md-dialog-padding: 24px !default;
$md-dialog-border-radius: 2px !default;
$md-dialog-max-width: 80vw !default;
$md-dialog-max-height: 65vh !default;

md-dialog-container {
@include md-elevation(24);

display: block;
padding: $md-dialog-padding;
border-radius: $md-dialog-border-radius;
box-sizing: border-box;
overflow: auto;
max-width: $md-dialog-max-width;

// The dialog container should completely fill its parent overlay element.
width: 100%;
height: 100%;
}

md-dialog-content, [md-dialog-content] {
display: block;
margin: 0 $md-dialog-padding * -1;
padding: 0 $md-dialog-padding;
max-height: $md-dialog-max-height;
overflow: auto;
}

[md-dialog-title] {
font-size: rem(2);
font-weight: bold;
letter-spacing: 0.005em;
margin: 0 0 rem(2);
display: block;
}

md-dialog-actions, [md-dialog-actions] {
padding: $md-dialog-padding / 2 0;
display: block;

&:last-child {
margin-bottom: -$md-dialog-padding;
}
}
78 changes: 75 additions & 3 deletions src/lib/dialog/dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
import {MdDialog, MdDialogModule} from './dialog';
import {MdDialogModule} from './index';
import {MdDialog} from './dialog';
import {OverlayContainer} from '../core';
import {MdDialogRef} from './dialog-ref';
import {MdDialogContainer} from './dialog-container';
Expand Down Expand Up @@ -226,6 +227,21 @@ describe('MdDialog', () => {
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
});

it('should close the top dialog', () => {
dialog.open(PizzaMsg);
dialog.open(PizzaMsg);

expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);

dialog.closeTop();

expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(1);

dialog.closeTop();

expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
});

describe('disableClose option', () => {
it('should prevent closing via clicks on the backdrop', () => {
dialog.open(PizzaMsg, {
Expand Down Expand Up @@ -308,6 +324,44 @@ describe('MdDialog', () => {
.toBe('dialog-trigger', 'Expected that the trigger was refocused after dialog close');
}));
});

describe('dialog content elements', () => {
let fixture: ComponentFixture<ContentElementDialog>;
let dialogElement: HTMLElement;

beforeEach(() => {
fixture = TestBed.createComponent(ContentElementDialog);
dialogElement = fixture.debugElement.nativeElement;
dialog.open(ContentElementDialog);
fixture.detectChanges();
});

it('close the dialog when clicking on the close button', () => {
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);

(dialogElement.querySelector('button[md-dialog-close]') as HTMLElement).click();

expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(0);
});

it('close not close the dialog if [md-dialog-close] is applied on a non-button node', () => {
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);

(dialogElement.querySelector('div[md-dialog-close]') as HTMLElement).click();

expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);
});

it('should add a role to the dialog title', () => {
let header = dialogElement.querySelector('[md-dialog-title]');
expect(header.getAttribute('role')).toBe('heading');
});

it('should add a role to the dialog content', () => {
let content = dialogElement.querySelector('md-dialog-content');
expect(content.getAttribute('role')).toBe('main');
});
});
});


Expand All @@ -334,13 +388,31 @@ class PizzaMsg {
constructor(public dialogRef: MdDialogRef<PizzaMsg>) { }
}

@Component({
template: `
<h1 md-dialog-title>This is the title</h1>
<md-dialog-content>Lorem ipsum dolor sit amet.</md-dialog-content>
<md-dialog-actions>
<button md-dialog-close>Close</button>
<div md-dialog-close>Should not close</div>
</md-dialog-actions>
`
})
class ContentElementDialog {}

// Create a real (non-test) NgModule as a workaround for
// https://github.com/angular/angular/issues/10760
const TEST_DIRECTIVES = [ComponentWithChildViewContainer, PizzaMsg, DirectiveWithViewContainer];
const TEST_DIRECTIVES = [
ComponentWithChildViewContainer,
PizzaMsg,
DirectiveWithViewContainer,
ContentElementDialog
];

@NgModule({
imports: [MdDialogModule],
exports: TEST_DIRECTIVES,
declarations: TEST_DIRECTIVES,
entryComponents: [ComponentWithChildViewContainer, PizzaMsg],
entryComponents: [ComponentWithChildViewContainer, PizzaMsg, ContentElementDialog],
})
class DialogTestModule { }
Loading

0 comments on commit 2dfaa9b

Please sign in to comment.