Skip to content

Commit 89e9243

Browse files
committed
fix(toolbar): no longer auto-generate toolbar rows
Currently the toolbar always generates the first `<md-toolbar-row>`. This means that developers have no opportunity to set directives/attributes/classes on the first toolbar row (e.g with flex-layout). With this change, the toolbar won't auto-generate any `<md-toolbar-row>` element. The toolbar will have two different row modes: _Single row toolbar_ ```html <md-toolbar> First Tow </md-toolbar> ``` _Multiple rows toolbar_ ```html <md-toolbar> <md-toolbar-row>First Row</md-toolbar-row> <md-toolbar-row>Second Row</md-toolbar-row> </md-toolbar> ``` This means that mixing those two row modes is no longer possible and allowed ```html <md-toolbar> <span>First Row</span> <md-toolbar-row>Second Row</md-toolbar-row> </md-toolbar> ``` Fixes #6004. Fixes #1718.
1 parent 70bd5fc commit 89e9243

File tree

8 files changed

+172
-80
lines changed

8 files changed

+172
-80
lines changed

src/demo-app/toolbar/toolbar-demo.html

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,27 @@
3535

3636
<p>
3737
<md-toolbar color="accent">
38-
<span>Custom Toolbar</span>
39-
<md-toolbar-row>
40-
<span>Second Line</span>
41-
</md-toolbar-row>
38+
<md-toolbar-row>First Row</md-toolbar-row>
39+
<md-toolbar-row>Second Row</md-toolbar-row>
4240
</md-toolbar>
4341
</p>
4442

4543
<p>
4644
<md-toolbar color="primary">
47-
<span>Custom Toolbar</span>
45+
<md-toolbar-row>
46+
<span>First Row</span>
47+
</md-toolbar-row>
4848

4949
<md-toolbar-row>
50-
<span>Second Line</span>
50+
<span>Second Row</span>
5151

5252
<span class="demo-fill-remaining"></span>
5353

5454
<md-icon class="demo-toolbar-icon">verified_user</md-icon>
5555
</md-toolbar-row>
5656

5757
<md-toolbar-row>
58-
<span>Third Line</span>
58+
<span>Third Row</span>
5959

6060
<span class="demo-fill-remaining"></span>
6161

@@ -64,5 +64,4 @@
6464
</md-toolbar-row>
6565
</md-toolbar>
6666
</p>
67-
68-
</div>
67+
</div>

src/lib/toolbar/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
*/
88

99
import {NgModule} from '@angular/core';
10+
import {PlatformModule} from '@angular/cdk/platform';
1011
import {MdCommonModule} from '../core';
1112
import {MdToolbar, MdToolbarRow} from './toolbar';
1213

1314

1415
@NgModule({
15-
imports: [MdCommonModule],
16+
imports: [MdCommonModule, PlatformModule],
1617
exports: [MdToolbar, MdToolbarRow, MdCommonModule],
1718
declarations: [MdToolbar, MdToolbarRow],
1819
})

src/lib/toolbar/toolbar.html

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,2 @@
1-
<div class="mat-toolbar-layout">
2-
<md-toolbar-row>
3-
<ng-content></ng-content>
4-
</md-toolbar-row>
5-
<ng-content select="md-toolbar-row, mat-toolbar-row"></ng-content>
6-
</div>
1+
<ng-content></ng-content>
2+
<ng-content select="md-toolbar-row, mat-toolbar-row"></ng-content>

src/lib/toolbar/toolbar.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,38 @@
22

33
<!-- example(toolbar-overview) -->
44

5-
### Multiple rows
6-
Toolbars can have multiple rows using `<md-toolbar-row>` elements. Any content outside of an
7-
`<md-toolbar-row>` element are automatically placed inside of one at the beginning of the toolbar.
8-
Each toolbar row is a `display: flex` container.
5+
### Single row
6+
7+
In the most situations, a toolbar will be placed at the top of your application and will only
8+
have a single row that includes the title of your application.
99

1010
```html
1111
<md-toolbar>
12-
<span>First Row</span>
13-
12+
<span>My Application</span>
13+
</md-toolbar>
14+
```
15+
16+
### Multiple rows
17+
18+
The Material Design specifications describe that toolbars can also have multiple rows. Creating
19+
toolbars with multiple rows in Angular Material can be done by placing `<md-toolbar-row>` elements
20+
inside of a `<md-toolbar>`.
21+
22+
```html
23+
<md-toolbar>
1424
<md-toolbar-row>
15-
<span>Second Row</span>
25+
<span>First Row</span>
1626
</md-toolbar-row>
1727

1828
<md-toolbar-row>
19-
<span>Third Row</span>
29+
<span>Second Row</span>
2030
</md-toolbar-row>
2131
</md-toolbar>
2232
```
2333

34+
**Note**: Placing content outside of a `<md-toolbar-row>` when multiple rows are specified, is not
35+
supported.
36+
2437
### Positioning toolbar content
2538
The toolbar does not perform any positioning of its content. This gives the user full power to
2639
position the content as it suits their application.

src/lib/toolbar/toolbar.scss

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,39 @@ $mat-toolbar-height-desktop: 64px !default;
44
$mat-toolbar-height-mobile-portrait: 56px !default;
55
$mat-toolbar-height-mobile-landscape: 48px !default;
66

7-
$mat-toolbar-padding: 16px !default;
7+
$mat-toolbar-row-padding: 16px !default;
88

99

1010
@mixin mat-toolbar-height($height) {
11-
.mat-toolbar {
11+
.mat-toolbar-multiple-rows {
1212
min-height: $height;
1313
}
14-
.mat-toolbar-row {
14+
.mat-toolbar-row, .mat-toolbar-single-row {
1515
height: $height;
1616
}
1717
}
1818

19-
.mat-toolbar {
19+
.mat-toolbar-row, .mat-toolbar-single-row {
2020
display: flex;
2121
box-sizing: border-box;
22-
width: 100%;
23-
padding: 0 $mat-toolbar-padding;
24-
flex-direction: column;
2522

26-
.mat-toolbar-row {
27-
display: flex;
28-
box-sizing: border-box;
23+
padding: 0 $mat-toolbar-row-padding;
24+
width: 100%;
2925

30-
width: 100%;
26+
// Flexbox Vertical Alignment
27+
flex-direction: row;
28+
align-items: center;
3129

32-
// Flexbox Vertical Alignment
33-
flex-direction: row;
34-
align-items: center;
30+
// Per Material specs a toolbar cannot have multiple lines inside of a single row.
31+
// Disable text wrapping inside of the toolbar. Developers are still able to overwrite it.
32+
white-space: nowrap;
33+
}
3534

36-
// Per Material specs a toolbar cannot have multiple lines inside of a single row.
37-
// Disable text wrapping inside of the toolbar. Developers are still able to overwrite it.
38-
white-space: nowrap;
39-
}
35+
.mat-toolbar-multiple-rows {
36+
display: flex;
37+
box-sizing: border-box;
38+
flex-direction: column;
39+
width: 100%;
4040
}
4141

4242
// Set the default height for the toolbar.

src/lib/toolbar/toolbar.spec.ts

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,105 @@ import {TestBed, async, ComponentFixture} from '@angular/core/testing';
33
import {By} from '@angular/platform-browser';
44
import {MdToolbarModule} from './index';
55

6-
76
describe('MdToolbar', () => {
87

9-
let fixture: ComponentFixture<TestApp>;
10-
let testComponent: TestApp;
11-
let toolbarElement: HTMLElement;
12-
138
beforeEach(async(() => {
149
TestBed.configureTestingModule({
1510
imports: [MdToolbarModule],
16-
declarations: [TestApp],
11+
declarations: [ToolbarSingleRow, ToolbarMultipleRows, ToolbarMixedRowModes],
1712
});
1813

1914
TestBed.compileComponents();
2015
}));
2116

22-
beforeEach(() => {
23-
fixture = TestBed.createComponent(TestApp);
24-
testComponent = fixture.debugElement.componentInstance;
25-
toolbarElement = fixture.debugElement.query(By.css('md-toolbar')).nativeElement;
26-
});
17+
describe('with single row', () => {
18+
let fixture: ComponentFixture<ToolbarSingleRow>;
19+
let testComponent: ToolbarSingleRow;
20+
let toolbarElement: HTMLElement;
21+
22+
beforeEach(() => {
23+
fixture = TestBed.createComponent(ToolbarSingleRow);
24+
testComponent = fixture.debugElement.componentInstance;
25+
toolbarElement = fixture.debugElement.query(By.css('.mat-toolbar')).nativeElement;
26+
});
27+
28+
it('should apply class based on color attribute', () => {
29+
testComponent.toolbarColor = 'primary';
30+
fixture.detectChanges();
2731

28-
it('should apply class based on color attribute', () => {
29-
testComponent.toolbarColor = 'primary';
30-
fixture.detectChanges();
32+
expect(toolbarElement.classList.contains('mat-primary')).toBe(true);
3133

32-
expect(toolbarElement.classList.contains('mat-primary')).toBe(true);
34+
testComponent.toolbarColor = 'accent';
35+
fixture.detectChanges();
3336

34-
testComponent.toolbarColor = 'accent';
35-
fixture.detectChanges();
37+
expect(toolbarElement.classList.contains('mat-primary')).toBe(false);
38+
expect(toolbarElement.classList.contains('mat-accent')).toBe(true);
3639

37-
expect(toolbarElement.classList.contains('mat-primary')).toBe(false);
38-
expect(toolbarElement.classList.contains('mat-accent')).toBe(true);
40+
testComponent.toolbarColor = 'warn';
41+
fixture.detectChanges();
42+
43+
expect(toolbarElement.classList.contains('mat-accent')).toBe(false);
44+
expect(toolbarElement.classList.contains('mat-warn')).toBe(true);
45+
});
3946

40-
testComponent.toolbarColor = 'warn';
41-
fixture.detectChanges();
47+
it('should set the toolbar role on the host', () => {
48+
expect(toolbarElement.getAttribute('role')).toBe('toolbar');
49+
});
4250

43-
expect(toolbarElement.classList.contains('mat-accent')).toBe(false);
44-
expect(toolbarElement.classList.contains('mat-warn')).toBe(true);
51+
it('should not wrap the first row contents inside of a generated element', () => {
52+
expect(toolbarElement.firstElementChild!.tagName).toBe('SPAN',
53+
'Expected the <span> element of the first row to be a direct child of the toolbar');
54+
});
4555
});
4656

47-
it('should set the toolbar role on the host', () => {
48-
expect(toolbarElement.getAttribute('role')).toBe('toolbar');
57+
describe('with multiple rows', () => {
58+
59+
it('should project each toolbar-row element inside of the toolbar', () => {
60+
const fixture = TestBed.createComponent(ToolbarMultipleRows);
61+
fixture.detectChanges();
62+
63+
expect(fixture.debugElement.queryAll(By.css('.mat-toolbar > .mat-toolbar-row')).length)
64+
.toBe(2, 'Expected one toolbar row to be present while no content is projected.');
65+
});
66+
67+
it('should throw an error if different toolbar modes are mixed', () => {
68+
expect(() => {
69+
const fixture = TestBed.createComponent(ToolbarMixedRowModes);
70+
fixture.detectChanges();
71+
}).toThrowError(/attempting to combine different/i);
72+
});
4973
});
5074

5175
});
5276

5377

54-
@Component({template: `<md-toolbar [color]="toolbarColor">Test Toolbar</md-toolbar>`})
55-
class TestApp {
78+
@Component({
79+
template: `
80+
<md-toolbar [color]="toolbarColor">
81+
<span>First Row</span>
82+
</md-toolbar>
83+
`
84+
})
85+
class ToolbarSingleRow {
5686
toolbarColor: string;
5787
}
88+
89+
@Component({
90+
template: `
91+
<md-toolbar>
92+
<md-toolbar-row>First Row</md-toolbar-row>
93+
<md-toolbar-row>Second Row</md-toolbar-row>
94+
</md-toolbar>
95+
`
96+
})
97+
class ToolbarMultipleRows {}
98+
99+
@Component({
100+
template: `
101+
<md-toolbar>
102+
First Row
103+
<md-toolbar-row>Second Row</md-toolbar-row>
104+
</md-toolbar>
105+
`
106+
})
107+
class ToolbarMixedRowModes {}

src/lib/toolbar/toolbar.ts

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@ import {
1313
Directive,
1414
ElementRef,
1515
Renderer2,
16+
ContentChildren,
17+
QueryList,
18+
AfterViewInit,
19+
isDevMode,
1620
} from '@angular/core';
1721
import {CanColor, mixinColor} from '../core/common-behaviors/color';
18-
19-
20-
@Directive({
21-
selector: 'md-toolbar-row, mat-toolbar-row',
22-
host: {'class': 'mat-toolbar-row'},
23-
})
24-
export class MdToolbarRow {}
22+
import {Platform} from '@angular/cdk/platform';
2523

2624
// Boilerplate for applying mixins to MdToolbar.
2725
/** @docs-private */
@@ -30,6 +28,11 @@ export class MdToolbarBase {
3028
}
3129
export const _MdToolbarMixinBase = mixinColor(MdToolbarBase);
3230

31+
@Directive({
32+
selector: 'md-toolbar-row, mat-toolbar-row',
33+
host: {'class': 'mat-toolbar-row'},
34+
})
35+
export class MdToolbarRow {}
3336

3437
@Component({
3538
moduleId: module.id,
@@ -39,15 +42,43 @@ export const _MdToolbarMixinBase = mixinColor(MdToolbarBase);
3942
inputs: ['color'],
4043
host: {
4144
'class': 'mat-toolbar',
42-
'role': 'toolbar'
45+
'role': 'toolbar',
46+
'[class.mat-toolbar-multiple-rows]': 'this._toolbarRows.length',
47+
'[class.mat-toolbar-single-row]': '!this._toolbarRows.length'
4348
},
4449
changeDetection: ChangeDetectionStrategy.OnPush,
4550
encapsulation: ViewEncapsulation.None
4651
})
47-
export class MdToolbar extends _MdToolbarMixinBase implements CanColor {
52+
export class MdToolbar extends _MdToolbarMixinBase implements CanColor, AfterViewInit {
53+
54+
/** Reference to all toolbar row elements that have been projected. */
55+
@ContentChildren(MdToolbarRow) _toolbarRows: QueryList<MdToolbarRow>;
4856

49-
constructor(renderer: Renderer2, elementRef: ElementRef) {
57+
constructor(renderer: Renderer2, elementRef: ElementRef, private _platform: Platform) {
5058
super(renderer, elementRef);
5159
}
5260

61+
ngAfterViewInit() {
62+
if (!isDevMode() || !this._platform.isBrowser || !this._toolbarRows.length) {
63+
return;
64+
}
65+
66+
const isCombinedUsage = [].slice.call(this._elementRef.nativeElement.childNodes)
67+
.filter(node => !(node.classList && node.classList.contains('mat-toolbar-row')))
68+
.some(node => node.textContent.trim());
69+
70+
if (isCombinedUsage) {
71+
throwToolbarMixedModesError();
72+
}
73+
}
74+
}
75+
76+
/**
77+
* Throws an exception when attempting to combine the different toolbar row modes.
78+
* @docs-private
79+
*/
80+
export function throwToolbarMixedModesError() {
81+
throw Error('MdToolbar: Attempting to combine different toolbar modes. ' +
82+
'Either specify multiple `<md-toolbar-row>` elements explicitly or just place content ' +
83+
'inside of a `<md-toolbar>` for a single row.');
5384
}

src/material-examples/toolbar-multirow/toolbar-multirow-example.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<md-toolbar color="primary">
2-
<span>Custom Toolbar</span>
2+
<md-toolbar-row>
3+
<span>Custom Toolbar</span>
4+
</md-toolbar-row>
35

46
<md-toolbar-row>
57
<span>Second Line</span>

0 commit comments

Comments
 (0)