Skip to content

feat(dialog): add dialog content elements #2090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do these max-height and max-width come from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The max-width is the same that Material 1 used, the max-height is arbitrary. M1 has 80% for the max-height as well, although that's for the entire dialog, whereas here we only have it on the md-dialog-content.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to set a max-width and max-height at all, then? I could imagine some people wanting to do a full-screen dialog.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say so, otherwise the dialog content won't become scrollable. Also I'm not sure whether fullscreen dialogs are in the spec.

Copy link

@chpasha chpasha Dec 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually yes, there are fullscreen dialogs in material guidelines https://material.io/guidelines/components/dialogs.html#dialogs-full-screen-dialogs


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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment explaining what this style is for?

}
}
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