Skip to content

Commit

Permalink
feat(dialog): add dialog content elements (#2090)
Browse files Browse the repository at this point in the history
* feat(dialog): add dialog content elements

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 #1624.
Fixes #2042.

* Rename the dialog directives file.

* Add the selectors for Material 1 compatibility.

* Remove the closeTop method and use the dialogRef instead.

* Add an aria-label to the close button and simplify the testing setup.

* Remove redundant element roles.

* Use the computed value on the dialog title font size.

* Remove the letter spacing override on the dialog title.

* Add a comment regarding the negative bottom margin on the dialog actions.
  • Loading branch information
crisbeto authored and jelbourn committed Dec 16, 2016
1 parent 20ee360 commit cac72aa
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 74 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 @@ -65,6 +65,7 @@ import {InputContainerDemo} from './input/input-container-demo';
InputDemo,
InputContainerDemo,
JazzDialog,
ContentElementDialog,
ListDemo,
LiveAnnouncerDemo,
MdCheckboxDemoNestedChecklist,
Expand Down Expand Up @@ -96,6 +97,7 @@ import {InputContainerDemo} from './input/input-container-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
24 changes: 0 additions & 24 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 @@ -22,7 +22,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
47 changes: 47 additions & 0 deletions src/lib/dialog/dialog-content-directives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {Directive, Input} from '@angular/core';
import {MdDialogRef} from './dialog-ref';


/**
* Button that will close the current dialog.
*/
@Directive({
selector: 'button[md-dialog-close], button[mat-dialog-close]',
host: {
'(click)': 'dialogRef.close()',
'[attr.aria-label]': 'ariaLabel'
}
})
export class MdDialogClose {
/** Screenreader label for the button. */
@Input('aria-label') ariaLabel: string = 'Close dialog';

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

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


/**
* Scrollable content container of a dialog.
*/
@Directive({
selector: '[md-dialog-content], md-dialog-content, [mat-dialog-content], mat-dialog-content'
})
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, [mat-dialog-actions], mat-dialog-actions'
})
export class MdDialogActions { }
54 changes: 54 additions & 0 deletions src/lib/dialog/dialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@import '../core/style/elevation';
@import '../core/a11y/a11y';


$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%;

@include md-high-contrast {
outline: solid 1px;
}
}

md-dialog-content, [md-dialog-content], mat-dialog-content, [mat-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], [mat-dialog-title] {
font-size: 20px;
font-weight: bold;
margin: 0 0 20px;
display: block;
}

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

&:last-child {
// If the actions are the last element in a dialog, we need to pull them down
// over the dialog padding, in order to avoid the action's padding stacking
// with the dialog's.
margin-bottom: -$md-dialog-padding;
}
}
62 changes: 59 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 @@ -308,6 +309,41 @@ describe('MdDialog', () => {
.toBe('dialog-trigger', 'Expected that the trigger was refocused after dialog close');
}));
});

describe('dialog content elements', () => {
let dialogRef: MdDialogRef<ContentElementDialog>;

beforeEach(() => {
dialogRef = dialog.open(ContentElementDialog);
viewContainerFixture.detectChanges();
});

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

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

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

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

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

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

it('should allow for a user-specified aria-label on the close button', () => {
let button = overlayContainerElement.querySelector('button[md-dialog-close]');

dialogRef.componentInstance.closeButtonAriaLabel = 'Best close button ever';
viewContainerFixture.detectChanges();

expect(button.getAttribute('aria-label')).toBe('Best close button ever');
});

});
});


Expand All @@ -334,13 +370,33 @@ 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 [aria-label]="closeButtonAriaLabel">Close</button>
<div md-dialog-close>Should not close</div>
</md-dialog-actions>
`
})
class ContentElementDialog {
closeButtonAriaLabel: string;
}

// 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 cac72aa

Please sign in to comment.