Skip to content

Commit cac72aa

Browse files
crisbetojelbourn
authored andcommitted
feat(dialog): add dialog content elements (#2090)
* 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.
1 parent 20ee360 commit cac72aa

11 files changed

+294
-74
lines changed

src/demo-app/demo-app-module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {RouterModule} from '@angular/router';
77
import {MaterialModule} from '@angular/material';
88
import {DEMO_APP_ROUTES} from './demo-app/routes';
99
import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
10-
import {JazzDialog, DialogDemo} from './dialog/dialog-demo';
10+
import {JazzDialog, ContentElementDialog, DialogDemo} from './dialog/dialog-demo';
1111
import {RippleDemo} from './ripple/ripple-demo';
1212
import {IconDemo} from './icon/icon-demo';
1313
import {GesturesDemo} from './gestures/gestures-demo';
@@ -65,6 +65,7 @@ import {InputContainerDemo} from './input/input-container-demo';
6565
InputDemo,
6666
InputContainerDemo,
6767
JazzDialog,
68+
ContentElementDialog,
6869
ListDemo,
6970
LiveAnnouncerDemo,
7071
MdCheckboxDemoNestedChecklist,
@@ -96,6 +97,7 @@ import {InputContainerDemo} from './input/input-container-demo';
9697
entryComponents: [
9798
DemoApp,
9899
JazzDialog,
100+
ContentElementDialog,
99101
RotiniPanel,
100102
ScienceJoke,
101103
SpagettiPanel,

src/demo-app/dialog/dialog-demo.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<h1>Dialog demo</h1>
22

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

56
<md-card class="demo-dialog-card">
67
<md-card-content>

src/demo-app/dialog/dialog-demo.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@ export class DialogDemo {
2424

2525
constructor(public dialog: MdDialog) { }
2626

27-
open() {
27+
openJazz() {
2828
this.dialogRef = this.dialog.open(JazzDialog, this.config);
2929

3030
this.dialogRef.afterClosed().subscribe(result => {
3131
this.lastCloseResult = result;
3232
this.dialogRef = null;
3333
});
3434
}
35+
36+
openContentElement() {
37+
this.dialog.open(ContentElementDialog, this.config);
38+
}
3539
}
3640

3741

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

4953
constructor(public dialogRef: MdDialogRef<JazzDialog>) { }
5054
}
55+
56+
57+
@Component({
58+
selector: 'demo-content-element-dialog',
59+
styles: [
60+
`img {
61+
max-width: 100%;
62+
}`
63+
],
64+
template: `
65+
<h2 md-dialog-title>Neptune</h2>
66+
67+
<md-dialog-content>
68+
<img src="https://upload.wikimedia.org/wikipedia/commons/5/56/Neptune_Full.jpg"/>
69+
70+
<p>
71+
Neptune is the eighth and farthest known planet from the Sun in the Solar System. In the
72+
Solar System, it is the fourth-largest planet by diameter, the third-most-massive planet,
73+
and the densest giant planet. Neptune is 17 times the mass of Earth and is slightly more
74+
massive than its near-twin Uranus, which is 15 times the mass of Earth and slightly larger
75+
than Neptune. Neptune orbits the Sun once every 164.8 years at an average distance of 30.1
76+
astronomical units (4.50×109 km). It is named after the Roman god of the sea and has the
77+
astronomical symbol ♆, a stylised version of the god Neptune's trident.
78+
</p>
79+
</md-dialog-content>
80+
81+
<md-dialog-actions>
82+
<button
83+
md-raised-button
84+
color="primary"
85+
md-dialog-close>Close</button>
86+
87+
<a
88+
md-button
89+
color="primary"
90+
href="https://en.wikipedia.org/wiki/Neptune"
91+
target="_blank">Read more on Wikipedia</a>
92+
</md-dialog-actions>
93+
`
94+
})
95+
export class ContentElementDialog { }

src/lib/dialog/README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ MdDialog is a service, which opens dialogs components in the view.
44

55
### Methods
66

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

1213
### Config
1314

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

2728
### Methods
2829

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

35+
### Directives
36+
| Name | Description |
37+
| --- | ------------ |
38+
| `md-dialog-title` | Marks the title of the dialog.
39+
| `md-dialog-content` | Scrollable content of the dialog.
40+
| `md-dialog-close` | When added to a `button`, makes the element act as a close button for the dialog.
41+
| `md-dialog-actions` | Wrapper for the set of actions at the bottom of a dialog. Typically contains buttons.
42+
3443
## Example
3544
The service can be injected in a component.
3645

@@ -62,8 +71,12 @@ export class PizzaComponent {
6271
@Component({
6372
selector: 'pizza-dialog',
6473
template: `
65-
<button type="button" (click)="dialogRef.close('yes')">Yes</button>
66-
<button type="button" (click)="dialogRef.close('no')">No</button>
74+
<h1 md-dialog-title>Would you like to order pizza?</h1>
75+
76+
<md-dialog-actions>
77+
<button (click)="dialogRef.close('yes')">Yes</button>
78+
<button md-dialog-close>No</button>
79+
</md-dialog-actions>
6780
`
6881
})
6982
export class PizzaDialog {

src/lib/dialog/dialog-container.scss

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/lib/dialog/dialog-container.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import 'rxjs/add/operator/first';
2222
moduleId: module.id,
2323
selector: 'md-dialog-container, mat-dialog-container',
2424
templateUrl: 'dialog-container.html',
25-
styleUrls: ['dialog-container.css'],
25+
styleUrls: ['dialog.css'],
2626
host: {
2727
'class': 'md-dialog-container',
2828
'[attr.role]': 'dialogConfig?.role',
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {Directive, Input} from '@angular/core';
2+
import {MdDialogRef} from './dialog-ref';
3+
4+
5+
/**
6+
* Button that will close the current dialog.
7+
*/
8+
@Directive({
9+
selector: 'button[md-dialog-close], button[mat-dialog-close]',
10+
host: {
11+
'(click)': 'dialogRef.close()',
12+
'[attr.aria-label]': 'ariaLabel'
13+
}
14+
})
15+
export class MdDialogClose {
16+
/** Screenreader label for the button. */
17+
@Input('aria-label') ariaLabel: string = 'Close dialog';
18+
19+
constructor(public dialogRef: MdDialogRef<any>) { }
20+
}
21+
22+
/**
23+
* Title of a dialog element. Stays fixed to the top of the dialog when scrolling.
24+
*/
25+
@Directive({
26+
selector: '[md-dialog-title], [mat-dialog-title]'
27+
})
28+
export class MdDialogTitle { }
29+
30+
31+
/**
32+
* Scrollable content container of a dialog.
33+
*/
34+
@Directive({
35+
selector: '[md-dialog-content], md-dialog-content, [mat-dialog-content], mat-dialog-content'
36+
})
37+
export class MdDialogContent { }
38+
39+
40+
/**
41+
* Container for the bottom action buttons in a dialog.
42+
* Stays fixed to the bottom when scrolling.
43+
*/
44+
@Directive({
45+
selector: '[md-dialog-actions], md-dialog-actions, [mat-dialog-actions], mat-dialog-actions'
46+
})
47+
export class MdDialogActions { }

src/lib/dialog/dialog.scss

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
@import '../core/style/elevation';
2+
@import '../core/a11y/a11y';
3+
4+
5+
$md-dialog-padding: 24px !default;
6+
$md-dialog-border-radius: 2px !default;
7+
$md-dialog-max-width: 80vw !default;
8+
$md-dialog-max-height: 65vh !default;
9+
10+
md-dialog-container {
11+
@include md-elevation(24);
12+
13+
display: block;
14+
padding: $md-dialog-padding;
15+
border-radius: $md-dialog-border-radius;
16+
box-sizing: border-box;
17+
overflow: auto;
18+
max-width: $md-dialog-max-width;
19+
20+
// The dialog container should completely fill its parent overlay element.
21+
width: 100%;
22+
height: 100%;
23+
24+
@include md-high-contrast {
25+
outline: solid 1px;
26+
}
27+
}
28+
29+
md-dialog-content, [md-dialog-content], mat-dialog-content, [mat-dialog-content] {
30+
display: block;
31+
margin: 0 $md-dialog-padding * -1;
32+
padding: 0 $md-dialog-padding;
33+
max-height: $md-dialog-max-height;
34+
overflow: auto;
35+
}
36+
37+
[md-dialog-title], [mat-dialog-title] {
38+
font-size: 20px;
39+
font-weight: bold;
40+
margin: 0 0 20px;
41+
display: block;
42+
}
43+
44+
md-dialog-actions, [md-dialog-actions], mat-dialog-actions, [mat-dialog-actions] {
45+
padding: $md-dialog-padding / 2 0;
46+
display: block;
47+
48+
&:last-child {
49+
// If the actions are the last element in a dialog, we need to pull them down
50+
// over the dialog padding, in order to avoid the action's padding stacking
51+
// with the dialog's.
52+
margin-bottom: -$md-dialog-padding;
53+
}
54+
}

src/lib/dialog/dialog.spec.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
} from '@angular/core/testing';
1010
import {By} from '@angular/platform-browser';
1111
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
12-
import {MdDialog, MdDialogModule} from './dialog';
12+
import {MdDialogModule} from './index';
13+
import {MdDialog} from './dialog';
1314
import {OverlayContainer} from '../core';
1415
import {MdDialogRef} from './dialog-ref';
1516
import {MdDialogContainer} from './dialog-container';
@@ -308,6 +309,41 @@ describe('MdDialog', () => {
308309
.toBe('dialog-trigger', 'Expected that the trigger was refocused after dialog close');
309310
}));
310311
});
312+
313+
describe('dialog content elements', () => {
314+
let dialogRef: MdDialogRef<ContentElementDialog>;
315+
316+
beforeEach(() => {
317+
dialogRef = dialog.open(ContentElementDialog);
318+
viewContainerFixture.detectChanges();
319+
});
320+
321+
it('should close the dialog when clicking on the close button', () => {
322+
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);
323+
324+
(overlayContainerElement.querySelector('button[md-dialog-close]') as HTMLElement).click();
325+
326+
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(0);
327+
});
328+
329+
it('should not close the dialog if [md-dialog-close] is applied on a non-button node', () => {
330+
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);
331+
332+
(overlayContainerElement.querySelector('div[md-dialog-close]') as HTMLElement).click();
333+
334+
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);
335+
});
336+
337+
it('should allow for a user-specified aria-label on the close button', () => {
338+
let button = overlayContainerElement.querySelector('button[md-dialog-close]');
339+
340+
dialogRef.componentInstance.closeButtonAriaLabel = 'Best close button ever';
341+
viewContainerFixture.detectChanges();
342+
343+
expect(button.getAttribute('aria-label')).toBe('Best close button ever');
344+
});
345+
346+
});
311347
});
312348

313349

@@ -334,13 +370,33 @@ class PizzaMsg {
334370
constructor(public dialogRef: MdDialogRef<PizzaMsg>) { }
335371
}
336372

373+
@Component({
374+
template: `
375+
<h1 md-dialog-title>This is the title</h1>
376+
<md-dialog-content>Lorem ipsum dolor sit amet.</md-dialog-content>
377+
<md-dialog-actions>
378+
<button md-dialog-close [aria-label]="closeButtonAriaLabel">Close</button>
379+
<div md-dialog-close>Should not close</div>
380+
</md-dialog-actions>
381+
`
382+
})
383+
class ContentElementDialog {
384+
closeButtonAriaLabel: string;
385+
}
386+
337387
// Create a real (non-test) NgModule as a workaround for
338388
// https://github.com/angular/angular/issues/10760
339-
const TEST_DIRECTIVES = [ComponentWithChildViewContainer, PizzaMsg, DirectiveWithViewContainer];
389+
const TEST_DIRECTIVES = [
390+
ComponentWithChildViewContainer,
391+
PizzaMsg,
392+
DirectiveWithViewContainer,
393+
ContentElementDialog
394+
];
395+
340396
@NgModule({
341397
imports: [MdDialogModule],
342398
exports: TEST_DIRECTIVES,
343399
declarations: TEST_DIRECTIVES,
344-
entryComponents: [ComponentWithChildViewContainer, PizzaMsg],
400+
entryComponents: [ComponentWithChildViewContainer, PizzaMsg, ContentElementDialog],
345401
})
346402
class DialogTestModule { }

0 commit comments

Comments
 (0)