diff --git a/src/demo-app/toolbar/toolbar-demo.html b/src/demo-app/toolbar/toolbar-demo.html index e6d3f30c5172..8a4cf6147de4 100644 --- a/src/demo-app/toolbar/toolbar-demo.html +++ b/src/demo-app/toolbar/toolbar-demo.html @@ -35,19 +35,19 @@

- Custom Toolbar - - Second Line - + First Row + Second Row

- Custom Toolbar + + First Row + - Second Line + Second Row @@ -55,7 +55,7 @@ - Third Line + Third Row @@ -64,5 +64,4 @@

- - \ No newline at end of file + diff --git a/src/lib/toolbar/index.ts b/src/lib/toolbar/index.ts index eb66854ca316..2b8be90eca30 100644 --- a/src/lib/toolbar/index.ts +++ b/src/lib/toolbar/index.ts @@ -7,12 +7,13 @@ */ import {NgModule} from '@angular/core'; +import {PlatformModule} from '@angular/cdk/platform'; import {MdCommonModule} from '../core'; import {MdToolbar, MdToolbarRow} from './toolbar'; @NgModule({ - imports: [MdCommonModule], + imports: [MdCommonModule, PlatformModule], exports: [MdToolbar, MdToolbarRow, MdCommonModule], declarations: [MdToolbar, MdToolbarRow], }) diff --git a/src/lib/toolbar/toolbar.html b/src/lib/toolbar/toolbar.html index e81e16e5d708..1cde768ee658 100644 --- a/src/lib/toolbar/toolbar.html +++ b/src/lib/toolbar/toolbar.html @@ -1,6 +1,2 @@ -
- - - - -
+ + diff --git a/src/lib/toolbar/toolbar.md b/src/lib/toolbar/toolbar.md index eb7189923d0f..b2da2cb7e223 100644 --- a/src/lib/toolbar/toolbar.md +++ b/src/lib/toolbar/toolbar.md @@ -2,25 +2,38 @@ -### Multiple rows -Toolbars can have multiple rows using `` elements. Any content outside of an -`` element are automatically placed inside of one at the beginning of the toolbar. -Each toolbar row is a `display: flex` container. +### Single row + +In the most situations, a toolbar will be placed at the top of your application and will only +have a single row that includes the title of your application. ```html - First Row - + My Application + +``` + +### Multiple rows + +The Material Design specifications describe that toolbars can also have multiple rows. Creating +toolbars with multiple rows in Angular Material can be done by placing `` elements +inside of a ``. + +```html + - Second Row + First Row - Third Row + Second Row ``` +**Note**: Placing content outside of a `` when multiple rows are specified, is not +supported. + ### Positioning toolbar content The toolbar does not perform any positioning of its content. This gives the user full power to position the content as it suits their application. diff --git a/src/lib/toolbar/toolbar.scss b/src/lib/toolbar/toolbar.scss index 87becbfa16e3..e6b4cb21650b 100644 --- a/src/lib/toolbar/toolbar.scss +++ b/src/lib/toolbar/toolbar.scss @@ -4,39 +4,39 @@ $mat-toolbar-height-desktop: 64px !default; $mat-toolbar-height-mobile-portrait: 56px !default; $mat-toolbar-height-mobile-landscape: 48px !default; -$mat-toolbar-padding: 16px !default; +$mat-toolbar-row-padding: 16px !default; @mixin mat-toolbar-height($height) { - .mat-toolbar { + .mat-toolbar-multiple-rows { min-height: $height; } - .mat-toolbar-row { + .mat-toolbar-row, .mat-toolbar-single-row { height: $height; } } -.mat-toolbar { +.mat-toolbar-row, .mat-toolbar-single-row { display: flex; box-sizing: border-box; - width: 100%; - padding: 0 $mat-toolbar-padding; - flex-direction: column; - .mat-toolbar-row { - display: flex; - box-sizing: border-box; + padding: 0 $mat-toolbar-row-padding; + width: 100%; - width: 100%; + // Flexbox Vertical Alignment + flex-direction: row; + align-items: center; - // Flexbox Vertical Alignment - flex-direction: row; - align-items: center; + // Per Material specs a toolbar cannot have multiple lines inside of a single row. + // Disable text wrapping inside of the toolbar. Developers are still able to overwrite it. + white-space: nowrap; +} - // Per Material specs a toolbar cannot have multiple lines inside of a single row. - // Disable text wrapping inside of the toolbar. Developers are still able to overwrite it. - white-space: nowrap; - } +.mat-toolbar-multiple-rows { + display: flex; + box-sizing: border-box; + flex-direction: column; + width: 100%; } // Set the default height for the toolbar. diff --git a/src/lib/toolbar/toolbar.spec.ts b/src/lib/toolbar/toolbar.spec.ts index 6e0c6d4da7cd..e9d624cabea4 100644 --- a/src/lib/toolbar/toolbar.spec.ts +++ b/src/lib/toolbar/toolbar.spec.ts @@ -3,55 +3,105 @@ import {TestBed, async, ComponentFixture} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MdToolbarModule} from './index'; - describe('MdToolbar', () => { - let fixture: ComponentFixture; - let testComponent: TestApp; - let toolbarElement: HTMLElement; - beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MdToolbarModule], - declarations: [TestApp], + declarations: [ToolbarSingleRow, ToolbarMultipleRows, ToolbarMixedRowModes], }); TestBed.compileComponents(); })); - beforeEach(() => { - fixture = TestBed.createComponent(TestApp); - testComponent = fixture.debugElement.componentInstance; - toolbarElement = fixture.debugElement.query(By.css('md-toolbar')).nativeElement; - }); + describe('with single row', () => { + let fixture: ComponentFixture; + let testComponent: ToolbarSingleRow; + let toolbarElement: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(ToolbarSingleRow); + testComponent = fixture.debugElement.componentInstance; + toolbarElement = fixture.debugElement.query(By.css('.mat-toolbar')).nativeElement; + }); + + it('should apply class based on color attribute', () => { + testComponent.toolbarColor = 'primary'; + fixture.detectChanges(); - it('should apply class based on color attribute', () => { - testComponent.toolbarColor = 'primary'; - fixture.detectChanges(); + expect(toolbarElement.classList.contains('mat-primary')).toBe(true); - expect(toolbarElement.classList.contains('mat-primary')).toBe(true); + testComponent.toolbarColor = 'accent'; + fixture.detectChanges(); - testComponent.toolbarColor = 'accent'; - fixture.detectChanges(); + expect(toolbarElement.classList.contains('mat-primary')).toBe(false); + expect(toolbarElement.classList.contains('mat-accent')).toBe(true); - expect(toolbarElement.classList.contains('mat-primary')).toBe(false); - expect(toolbarElement.classList.contains('mat-accent')).toBe(true); + testComponent.toolbarColor = 'warn'; + fixture.detectChanges(); + + expect(toolbarElement.classList.contains('mat-accent')).toBe(false); + expect(toolbarElement.classList.contains('mat-warn')).toBe(true); + }); - testComponent.toolbarColor = 'warn'; - fixture.detectChanges(); + it('should set the toolbar role on the host', () => { + expect(toolbarElement.getAttribute('role')).toBe('toolbar'); + }); - expect(toolbarElement.classList.contains('mat-accent')).toBe(false); - expect(toolbarElement.classList.contains('mat-warn')).toBe(true); + it('should not wrap the first row contents inside of a generated element', () => { + expect(toolbarElement.firstElementChild!.tagName).toBe('SPAN', + 'Expected the element of the first row to be a direct child of the toolbar'); + }); }); - it('should set the toolbar role on the host', () => { - expect(toolbarElement.getAttribute('role')).toBe('toolbar'); + describe('with multiple rows', () => { + + it('should project each toolbar-row element inside of the toolbar', () => { + const fixture = TestBed.createComponent(ToolbarMultipleRows); + fixture.detectChanges(); + + expect(fixture.debugElement.queryAll(By.css('.mat-toolbar > .mat-toolbar-row')).length) + .toBe(2, 'Expected one toolbar row to be present while no content is projected.'); + }); + + it('should throw an error if different toolbar modes are mixed', () => { + expect(() => { + const fixture = TestBed.createComponent(ToolbarMixedRowModes); + fixture.detectChanges(); + }).toThrowError(/attempting to combine different/i); + }); }); }); -@Component({template: `Test Toolbar`}) -class TestApp { +@Component({ + template: ` + + First Row + + ` +}) +class ToolbarSingleRow { toolbarColor: string; } + +@Component({ + template: ` + + First Row + Second Row + + ` +}) +class ToolbarMultipleRows {} + +@Component({ + template: ` + + First Row + Second Row + + ` +}) +class ToolbarMixedRowModes {} diff --git a/src/lib/toolbar/toolbar.ts b/src/lib/toolbar/toolbar.ts index 772869c8fadb..c9e982397dd6 100644 --- a/src/lib/toolbar/toolbar.ts +++ b/src/lib/toolbar/toolbar.ts @@ -13,15 +13,13 @@ import { Directive, ElementRef, Renderer2, + ContentChildren, + QueryList, + AfterViewInit, + isDevMode, } from '@angular/core'; import {CanColor, mixinColor} from '../core/common-behaviors/color'; - - -@Directive({ - selector: 'md-toolbar-row, mat-toolbar-row', - host: {'class': 'mat-toolbar-row'}, -}) -export class MdToolbarRow {} +import {Platform} from '@angular/cdk/platform'; // Boilerplate for applying mixins to MdToolbar. /** @docs-private */ @@ -30,6 +28,11 @@ export class MdToolbarBase { } export const _MdToolbarMixinBase = mixinColor(MdToolbarBase); +@Directive({ + selector: 'md-toolbar-row, mat-toolbar-row', + host: {'class': 'mat-toolbar-row'}, +}) +export class MdToolbarRow {} @Component({ moduleId: module.id, @@ -39,15 +42,43 @@ export const _MdToolbarMixinBase = mixinColor(MdToolbarBase); inputs: ['color'], host: { 'class': 'mat-toolbar', - 'role': 'toolbar' + 'role': 'toolbar', + '[class.mat-toolbar-multiple-rows]': 'this._toolbarRows.length', + '[class.mat-toolbar-single-row]': '!this._toolbarRows.length' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }) -export class MdToolbar extends _MdToolbarMixinBase implements CanColor { +export class MdToolbar extends _MdToolbarMixinBase implements CanColor, AfterViewInit { + + /** Reference to all toolbar row elements that have been projected. */ + @ContentChildren(MdToolbarRow) _toolbarRows: QueryList; - constructor(renderer: Renderer2, elementRef: ElementRef) { + constructor(renderer: Renderer2, elementRef: ElementRef, private _platform: Platform) { super(renderer, elementRef); } + ngAfterViewInit() { + if (!isDevMode() || !this._platform.isBrowser || !this._toolbarRows.length) { + return; + } + + const isCombinedUsage = [].slice.call(this._elementRef.nativeElement.childNodes) + .filter(node => !(node.classList && node.classList.contains('mat-toolbar-row'))) + .some(node => node.textContent.trim()); + + if (isCombinedUsage) { + throwToolbarMixedModesError(); + } + } +} + +/** + * Throws an exception when attempting to combine the different toolbar row modes. + * @docs-private + */ +export function throwToolbarMixedModesError() { + throw Error('MdToolbar: Attempting to combine different toolbar modes. ' + + 'Either specify multiple `` elements explicitly or just place content ' + + 'inside of a `` for a single row.'); } diff --git a/src/material-examples/toolbar-multirow/toolbar-multirow-example.html b/src/material-examples/toolbar-multirow/toolbar-multirow-example.html index 9b178a21249f..49f9a7a96f23 100644 --- a/src/material-examples/toolbar-multirow/toolbar-multirow-example.html +++ b/src/material-examples/toolbar-multirow/toolbar-multirow-example.html @@ -1,5 +1,7 @@ - Custom Toolbar + + Custom Toolbar + Second Line