ADF
diff --git a/demo-shell/src/app/components/home/home.component.scss b/demo-shell/src/app/components/home/home.component.scss
index 9aee2a5db0..51e04c0ebe 100644
--- a/demo-shell/src/app/components/home/home.component.scss
+++ b/demo-shell/src/app/components/home/home.component.scss
@@ -1,3 +1,9 @@
+:host {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
.adf-home-header-background {
overflow: hidden;
}
@@ -8,6 +14,7 @@
}
.adf-home-headline {
+
h1 {
font-size: 56px;
font-weight: 300;
diff --git a/docs/core/sidenav-layout.component.md b/docs/core/sidenav-layout.component.md
new file mode 100644
index 0000000000..960eebf516
--- /dev/null
+++ b/docs/core/sidenav-layout.component.md
@@ -0,0 +1,78 @@
+---
+Added: v2.3.0
+Status: Active
+---
+# Sidenav layout component
+
+A generalised component to help displayig the "ADF style" application frame. The layout consists of 3 regions:
+
+- header
+- navigation
+- content
+
+The navigation (depending on the screensize) either uses the Angular Material Sidenav (on small breakpoint), or the ADF style Sidenav (on bigger breakpoint).
+
+- For Angular Material Sidenav see examples on the Angular Material project's site.
+- The ADF style Sidenav has 2 states: **expanded** and **compact**. Regardless of the state, it is always displayed on the screen, either in small width (compact) or in bigger width (expanded).
+
+The contents of the 3 regions can be injected through Angular's template transclusion. For more details see the usage example of the components.
+
+On desktop (above stepOver):
+

+
+On mobile (below stepOver):
+

+
+## Basic Usage
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Properties
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| sidenavMin | number | - | (**required**) the compact width in pixels |
+| sidenavMax | number | - | (**required**) the expanded width in pixels |
+| stepOver | number | - | (**required**) The breakpoint in pixels, where to step over to mobile/desktop |
+| hideSidenav | boolean | false | Hide the navigation or not |
+| expandedSidenav | boolean | true | The initial (expanded/compact) state of the navigation |
+
+## Template context
+
+Each template is given the context containing the following methods:
+
+### toggleMenu(): void
+
+Trigger menu toggling
+### isMenuMinimized(): boolean
+The expanded/compact (minimized) state of the navigation. This one only makes sense in case of desktop size, when the screen size is above the value of stepOver.
\ No newline at end of file
diff --git a/docs/docassets/images/sidenav-layout-mobile.png b/docs/docassets/images/sidenav-layout-mobile.png
new file mode 100644
index 0000000000..a114499f02
Binary files /dev/null and b/docs/docassets/images/sidenav-layout-mobile.png differ
diff --git a/docs/docassets/images/sidenav-layout.png b/docs/docassets/images/sidenav-layout.png
new file mode 100644
index 0000000000..074b745081
Binary files /dev/null and b/docs/docassets/images/sidenav-layout.png differ
diff --git a/lib/core/core.module.ts b/lib/core/core.module.ts
index e73ba79be5..9873f3d9bf 100644
--- a/lib/core/core.module.ts
+++ b/lib/core/core.module.ts
@@ -37,6 +37,7 @@ import { ToolbarModule } from './toolbar/toolbar.module';
import { UserInfoModule } from './userinfo/userinfo.module';
import { ViewerModule } from './viewer/viewer.module';
import { FormModule } from './form/form.module';
+import { SidenavLayoutModule } from './sidenav-layout/sidenav-layout.module';
import { SideBarActionModule } from './sidebar/sidebar-action.module';
import { DirectiveModule } from './directives/directive.module';
@@ -122,6 +123,7 @@ export function providers() {
@NgModule({
imports: [
ViewerModule,
+ SidenavLayoutModule,
SideBarActionModule,
PipeModule,
CommonModule,
@@ -155,6 +157,7 @@ export function providers() {
exports: [
ViewerModule,
SideBarActionModule,
+ SidenavLayoutModule,
PipeModule,
CommonModule,
DirectiveModule,
@@ -186,6 +189,7 @@ export class CoreModuleLazy {
imports: [
ViewerModule,
SideBarActionModule,
+ SidenavLayoutModule,
PipeModule,
CommonModule,
DirectiveModule,
@@ -218,6 +222,7 @@ export class CoreModuleLazy {
exports: [
ViewerModule,
SideBarActionModule,
+ SidenavLayoutModule,
PipeModule,
CommonModule,
DirectiveModule,
diff --git a/lib/core/sidenav-layout/components/layout-container/layout-container.component.html b/lib/core/sidenav-layout/components/layout-container/layout-container.component.html
new file mode 100644
index 0000000000..83856bbd36
--- /dev/null
+++ b/lib/core/sidenav-layout/components/layout-container/layout-container.component.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/lib/core/sidenav-layout/components/layout-container/layout-container.component.scss b/lib/core/sidenav-layout/components/layout-container/layout-container.component.scss
new file mode 100644
index 0000000000..eca3fb0e31
--- /dev/null
+++ b/lib/core/sidenav-layout/components/layout-container/layout-container.component.scss
@@ -0,0 +1,39 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+ng-content {
+ display: block;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+.sidenav--hidden {
+ visibility: hidden !important;
+ width: 0 !important;
+ transform: unset !important;
+ opacity: 0 !important;
+}
+
+.mat-sidenav-container {
+ display: block;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+.mat-sidenav {
+ overflow: hidden;
+}
+
+.mat-sidenav-content,
+.mat-drawer-transition .mat-drawer-content {
+ transform: unset !important;
+ transition-property: unset !important;
+ transition-duration: unset !important;
+ transition-timing-function: unset !important;
+}
diff --git a/lib/core/sidenav-layout/components/layout-container/layout-container.component.ts b/lib/core/sidenav-layout/components/layout-container/layout-container.component.ts
new file mode 100644
index 0000000000..2e10ef8aac
--- /dev/null
+++ b/lib/core/sidenav-layout/components/layout-container/layout-container.component.ts
@@ -0,0 +1,109 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, Input, ViewChild, OnInit, OnDestroy } from '@angular/core';
+import { MatSidenav } from '@angular/material';
+import { sidenavAnimation, contentAnimation } from '../../helpers/animations';
+
+@Component({
+ selector: 'adf-layout-container',
+ templateUrl: './layout-container.component.html',
+ styleUrls: ['./layout-container.component.scss'],
+ animations: [ sidenavAnimation, contentAnimation ]
+})
+export class LayoutContainerComponent implements OnInit, OnDestroy {
+ @Input() sidenavMin: number;
+ @Input() sidenavMax: number;
+ @Input() mediaQueryList: MediaQueryList;
+ @Input() hideSidenav = false;
+ @Input() expandedSidenav = true;
+
+ @ViewChild(MatSidenav) sidenav: MatSidenav;
+
+ sidenavAnimationState: any;
+ contentAnimationState: any;
+
+ SIDENAV_STATES = { MOBILE: {}, EXPANDED: {}, COMPACT: {} };
+ CONTENT_STATES = { MOBILE: {}, EXPANDED: {}, COMPACT: {} };
+
+ constructor() {
+ this.onMediaQueryChange = this.onMediaQueryChange.bind(this);
+ }
+
+ ngOnInit() {
+ this.SIDENAV_STATES.MOBILE = { value: 'expanded', params: { width: this.sidenavMax } };
+ this.SIDENAV_STATES.EXPANDED = { value: 'expanded', params: { width: this.sidenavMax } };
+ this.SIDENAV_STATES.COMPACT = { value: 'compact', params: {width: this.sidenavMin } };
+
+ this.CONTENT_STATES.MOBILE = { value: 'expanded', params: { marginLeft: 0 } };
+ this.CONTENT_STATES.EXPANDED = { value: 'expanded', params: { marginLeft: this.sidenavMin } };
+ this.CONTENT_STATES.COMPACT = { value: 'compact', params: { marginLeft: this.sidenavMax } };
+
+ this.mediaQueryList.addListener(this.onMediaQueryChange);
+
+ if (this.isMobileScreenSize) {
+ this.sidenavAnimationState = this.SIDENAV_STATES.MOBILE;
+ this.contentAnimationState = this.CONTENT_STATES.MOBILE;
+ } else if (this.expandedSidenav) {
+ this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED;
+ this.contentAnimationState = this.CONTENT_STATES.COMPACT;
+ } else {
+ this.sidenavAnimationState = this.SIDENAV_STATES.COMPACT;
+ this.contentAnimationState = this.CONTENT_STATES.EXPANDED;
+ }
+ }
+
+ ngOnDestroy(): void {
+ this.mediaQueryList.removeListener(this.onMediaQueryChange);
+ }
+
+ toggleMenu(): void {
+ if (this.isMobileScreenSize) {
+ this.sidenav.toggle();
+ } else {
+ this.sidenavAnimationState = this.toggledSidenavAnimation;
+ this.contentAnimationState = this.toggledContentAnimation;
+ }
+ }
+
+ private get toggledSidenavAnimation() {
+ return this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED
+ ? this.SIDENAV_STATES.COMPACT
+ : this.SIDENAV_STATES.EXPANDED;
+ }
+
+ private get toggledContentAnimation() {
+ if (this.isMobileScreenSize) {
+ return this.CONTENT_STATES.MOBILE;
+ }
+
+ if (this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED) {
+ return this.CONTENT_STATES.COMPACT;
+ } else {
+ return this.CONTENT_STATES.EXPANDED;
+ }
+ }
+
+ private get isMobileScreenSize(): boolean {
+ return this.mediaQueryList.matches;
+ }
+
+ private onMediaQueryChange() {
+ this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED;
+ this.contentAnimationState = this.toggledContentAnimation;
+ }
+}
diff --git a/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.html b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.html
new file mode 100644
index 0000000000..55ff07fc51
--- /dev/null
+++ b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.scss b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.scss
new file mode 100644
index 0000000000..da152a9c2d
--- /dev/null
+++ b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.scss
@@ -0,0 +1,18 @@
+:host {
+ display: flex;
+ flex: 1;
+
+ .sidenav-layout {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .layout__content {
+ flex: 1 1 auto;
+ }
+ }
+
+ router-outlet {
+ flex: 0 0;
+ }
+}
diff --git a/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.spec.ts b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.spec.ts
new file mode 100644
index 0000000000..b0c924bc5d
--- /dev/null
+++ b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.spec.ts
@@ -0,0 +1,280 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*tslint:disable: ban*/
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { SidenavLayoutComponent } from './sidenav-layout.component';
+import { Component, Input } from '@angular/core';
+import { LayoutModule, MediaMatcher } from '@angular/cdk/layout';
+import { PlatformModule } from '@angular/cdk/platform';
+import { MaterialModule } from '../../../material.module';
+import { SidenavLayoutContentDirective } from '../../directives/sidenav-layout-content.directive';
+import { SidenavLayoutHeaderDirective } from '../../directives/sidenav-layout-header.directive';
+import { SidenavLayoutNavigationDirective } from '../../directives/sidenav-layout-navigation.directive';
+import { CommonModule } from '@angular/common';
+
+@Component({
+ selector: 'adf-layout-container',
+ template: `
+
+
`
+})
+export class DummyLayoutContainerComponent {
+ @Input() sidenavMin: number;
+ @Input() sidenavMax: number;
+ @Input() mediaQueryList: MediaQueryList;
+ @Input() hideSidenav: boolean;
+ @Input() expandedSidenav: boolean;
+ toggleMenu () {}
+}
+
+describe('SidenavLayoutComponent', () => {
+
+ let fixture: ComponentFixture
,
+ mediaMatcher: MediaMatcher,
+ mediaQueryList: any;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ CommonModule,
+ PlatformModule,
+ LayoutModule,
+ MaterialModule
+ ],
+ declarations: [
+ DummyLayoutContainerComponent,
+ SidenavLayoutComponent,
+ SidenavLayoutContentDirective,
+ SidenavLayoutHeaderDirective,
+ SidenavLayoutNavigationDirective
+ ],
+ providers: [
+ MediaMatcher
+ ]
+ });
+ }));
+
+ beforeEach(() => {
+ mediaQueryList = {
+ matches: false,
+ addListener: () => {},
+ removeListener: () => {}
+ };
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ TestBed.resetTestingModule();
+ });
+
+ describe('Template transclusion', () => {
+
+ @Component({
+ selector: 'adf-test-component-for-sidenav',
+ template: `
+
+
+
+
+
+
+
+
+
+
+ {{ isMenuMinimized !== undefined ? 'variable-is-injected' : 'variable-is-not-injected' }}
+
+
+
+
+
+
+
+
+ `
+ })
+ class SidenavLayoutTesterComponent {}
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({ declarations: [ SidenavLayoutTesterComponent ] }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ mediaMatcher = TestBed.get(MediaMatcher);
+ spyOn(mediaMatcher, 'matchMedia').and.returnValue(mediaQueryList);
+
+ fixture = TestBed.createComponent(SidenavLayoutTesterComponent);
+ fixture.detectChanges();
+ });
+
+ describe('adf-sidenav-layout-navigation', () => {
+
+ const injectedElementSelector = By.css('[data-automation-id="adf-layout-container"] #nav-test');
+
+ it('should contain the transcluded side navigation template', () => {
+ const injectedElement = fixture.debugElement.query(injectedElementSelector);
+
+ expect(injectedElement === null).toBe(false);
+ });
+
+ it('should let the isMenuMinimized property of component to be accessed by the transcluded template', () => {
+ const injectedElement = fixture.debugElement.query(injectedElementSelector);
+
+ expect(injectedElement.nativeElement.innerText.trim()).toBe('variable-is-injected');
+ });
+ });
+
+ describe('adf-sidenav-layout-header', () => {
+
+ const outerHeaderSelector = By.css('.sidenav-layout > #header-test'),
+ innerHeaderSelector = By.css('.sidenav-layout [data-automation-id="adf-layout-container"] #header-test');
+
+ it('should contain the transcluded header template outside of the layout-container', () => {
+ mediaQueryList.matches = false;
+ fixture.detectChanges();
+ const outerHeaderElement = fixture.debugElement.query(outerHeaderSelector);
+ const innerHeaderElement = fixture.debugElement.query(innerHeaderSelector);
+
+ expect(outerHeaderElement === null).toBe(false, 'Outer header should be shown');
+ expect(innerHeaderElement === null).toBe(true, 'Inner header should not be shown');
+ });
+
+ it('should contain the transcluded header template inside of the layout-container', () => {
+ mediaQueryList.matches = true;
+ fixture.detectChanges();
+ const outerHeaderElement = fixture.debugElement.query(outerHeaderSelector);
+ const innerHeaderElement = fixture.debugElement.query(innerHeaderSelector);
+
+ expect(outerHeaderElement === null).toBe(true, 'Outer header should not be shown');
+ expect(innerHeaderElement === null).toBe(false, 'Inner header should be shown');
+ });
+
+ it('should call through the layout container\'s toggleMenu method', () => {
+ mediaQueryList.matches = false;
+ fixture.detectChanges();
+ const layoutContainerComponent = fixture.debugElement.query(By.directive(DummyLayoutContainerComponent)).componentInstance;
+ spyOn(layoutContainerComponent, 'toggleMenu');
+
+ const outerHeaderElement = fixture.debugElement.query(outerHeaderSelector);
+ outerHeaderElement.triggerEventHandler('click', {});
+
+ expect(layoutContainerComponent.toggleMenu).toHaveBeenCalled();
+ });
+ });
+
+ describe('adf-sidenav-layout-content', () => {
+
+ const injectedElementSelector = By.css('[data-automation-id="adf-layout-container"] #content-test');
+
+ it('should contain the transcluded content template', () => {
+ const injectedElement = fixture.debugElement.query(injectedElementSelector);
+
+ expect(injectedElement === null).toBe(false);
+ });
+ });
+ });
+
+ describe('General behaviour', () => {
+
+ let component: SidenavLayoutComponent;
+
+ beforeEach(async(() => {
+ TestBed.compileComponents();
+ }));
+
+ beforeEach(() => {
+ mediaMatcher = TestBed.get(MediaMatcher);
+ spyOn(mediaMatcher, 'matchMedia').and.callFake((mediaQuery) => {
+ mediaQueryList.originalMediaQueryPassed = mediaQuery;
+ spyOn(mediaQueryList, 'addListener').and.stub();
+ spyOn(mediaQueryList, 'removeListener').and.stub();
+ return mediaQueryList;
+ });
+
+ fixture = TestBed.createComponent(SidenavLayoutComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should pass through input paramters', () => {
+ component.sidenavMin = 1;
+ component.sidenavMax = 2;
+ component.hideSidenav = true;
+ component.expandedSidenav = false;
+ fixture.detectChanges();
+
+ const layoutContainerComponent = fixture.debugElement.query(By.directive(DummyLayoutContainerComponent)).componentInstance;
+
+ expect(layoutContainerComponent.sidenavMin).toBe(component.sidenavMin);
+ expect(layoutContainerComponent.sidenavMax).toBe(component.sidenavMax);
+ expect(layoutContainerComponent.hideSidenav).toBe(component.hideSidenav);
+ expect(layoutContainerComponent.expandedSidenav).toBe(component.expandedSidenav);
+ expect(layoutContainerComponent.mediaQueryList.originalMediaQueryPassed).toBe(`(max-width: 600px)`);
+ });
+
+ it('addListener of mediaQueryList should have been called', () => {
+ expect(mediaQueryList.addListener).toHaveBeenCalledTimes(1);
+ expect(mediaQueryList.addListener).toHaveBeenCalledWith(component.onMediaQueryChange);
+ });
+
+ it('addListener of mediaQueryList should have been called', () => {
+ fixture.destroy();
+
+ expect(mediaQueryList.removeListener).toHaveBeenCalledTimes(1);
+ expect(mediaQueryList.removeListener).toHaveBeenCalledWith(component.onMediaQueryChange);
+ });
+ });
+
+ describe('toggleMenu', () => {
+
+ let component;
+
+ beforeEach(async(() => {
+ TestBed.compileComponents();
+ }));
+
+ beforeEach(() => {
+ mediaMatcher = TestBed.get(MediaMatcher);
+ spyOn(mediaMatcher, 'matchMedia').and.returnValue(mediaQueryList);
+
+ fixture = TestBed.createComponent(SidenavLayoutComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should toggle the isMenuMinimized if the mediaQueryList.matches is false (we are on desktop)', () => {
+ mediaQueryList.matches = false;
+ component.isMenuMinimized = false;
+
+ component.toggleMenu();
+
+ expect(component.isMenuMinimized).toBe(true);
+ });
+
+ it('should set the isMenuMinimized to false if the mediaQueryList.matches is true (we are on mobile)', () => {
+ mediaQueryList.matches = true;
+ component.isMenuMinimized = true;
+
+ component.toggleMenu();
+
+ expect(component.isMenuMinimized).toBe(false);
+ });
+ });
+});
diff --git a/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.ts b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.ts
new file mode 100644
index 0000000000..734ef08dae
--- /dev/null
+++ b/lib/core/sidenav-layout/components/sidenav-layout/sidenav-layout.component.ts
@@ -0,0 +1,101 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, ContentChild, Input, OnInit, AfterViewInit, ViewChild, OnDestroy, TemplateRef } from '@angular/core';
+import { MediaMatcher } from '@angular/cdk/layout';
+import { SidenavLayoutContentDirective } from '../../directives/sidenav-layout-content.directive';
+import { SidenavLayoutHeaderDirective } from '../../directives/sidenav-layout-header.directive';
+import { SidenavLayoutNavigationDirective } from '../../directives/sidenav-layout-navigation.directive';
+
+@Component({
+ selector: 'adf-sidenav-layout',
+ templateUrl: './sidenav-layout.component.html',
+ styleUrls: ['./sidenav-layout.component.scss']
+})
+export class SidenavLayoutComponent implements OnInit, AfterViewInit, OnDestroy {
+
+ static STEP_OVER = 600;
+
+ @Input() sidenavMin: number;
+ @Input() sidenavMax: number;
+ @Input() stepOver: number;
+ @Input() hideSidenav = false;
+ @Input() expandedSidenav = true;
+
+ @ContentChild(SidenavLayoutHeaderDirective) headerDirective: SidenavLayoutHeaderDirective;
+ @ContentChild(SidenavLayoutNavigationDirective) navigationDirective: SidenavLayoutNavigationDirective;
+ @ContentChild(SidenavLayoutContentDirective) contentDirective: SidenavLayoutContentDirective;
+
+ @ViewChild('container') container: any;
+ @ViewChild('emptyTemplate') emptyTemplate: any;
+
+ mediaQueryList: MediaQueryList;
+ isMenuMinimized;
+ templateContext = {
+ toggleMenu: () => {},
+ isMenuMinimized: () => this.isMenuMinimized
+ };
+
+ constructor(private mediaMatcher: MediaMatcher) {
+ this.onMediaQueryChange = this.onMediaQueryChange.bind(this);
+ }
+
+ ngOnInit() {
+ const stepOver = this.stepOver || SidenavLayoutComponent.STEP_OVER;
+ this.isMenuMinimized = !this.expandedSidenav;
+ this.mediaQueryList = this.mediaMatcher.matchMedia(`(max-width: ${stepOver}px)`);
+ this.mediaQueryList.addListener(this.onMediaQueryChange);
+ }
+
+ ngAfterViewInit() {
+ this.templateContext.toggleMenu = this.toggleMenu.bind(this);
+ }
+
+ ngOnDestroy(): void {
+ this.mediaQueryList.removeListener(this.onMediaQueryChange);
+ }
+
+ toggleMenu() {
+ if (!this.mediaQueryList.matches) {
+ this.isMenuMinimized = !this.isMenuMinimized;
+ } else {
+ this.isMenuMinimized = false;
+ }
+
+ this.container.toggleMenu();
+ }
+
+ get isHeaderInside() {
+ return this.mediaQueryList.matches;
+ }
+
+ get headerTemplate(): TemplateRef {
+ return this.headerDirective && this.headerDirective.template || this.emptyTemplate;
+ }
+
+ get navigationTemplate(): TemplateRef {
+ return this.navigationDirective && this.navigationDirective.template || this.emptyTemplate;
+ }
+
+ get contentTemplate(): TemplateRef {
+ return this.contentDirective && this.contentDirective.template || this.emptyTemplate;
+ }
+
+ onMediaQueryChange() {
+ this.isMenuMinimized = false;
+ }
+}
diff --git a/lib/core/sidenav-layout/directives/sidenav-layout-content.directive.ts b/lib/core/sidenav-layout/directives/sidenav-layout-content.directive.ts
new file mode 100644
index 0000000000..7f9e0fd747
--- /dev/null
+++ b/lib/core/sidenav-layout/directives/sidenav-layout-content.directive.ts
@@ -0,0 +1,26 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ContentChild, Directive, TemplateRef } from '@angular/core';
+
+@Directive({
+ selector: 'adf-sidenav-layout-content'
+})
+export class SidenavLayoutContentDirective {
+ @ContentChild(TemplateRef)
+ public template: TemplateRef;
+}
diff --git a/lib/core/sidenav-layout/directives/sidenav-layout-header.directive.ts b/lib/core/sidenav-layout/directives/sidenav-layout-header.directive.ts
new file mode 100644
index 0000000000..0a70602657
--- /dev/null
+++ b/lib/core/sidenav-layout/directives/sidenav-layout-header.directive.ts
@@ -0,0 +1,26 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ContentChild, Directive, TemplateRef } from '@angular/core';
+
+@Directive({
+ selector: 'adf-sidenav-layout-header'
+})
+export class SidenavLayoutHeaderDirective {
+ @ContentChild(TemplateRef)
+ public template: TemplateRef;
+}
diff --git a/lib/core/sidenav-layout/directives/sidenav-layout-navigation.directive.ts b/lib/core/sidenav-layout/directives/sidenav-layout-navigation.directive.ts
new file mode 100644
index 0000000000..baa9ed45d3
--- /dev/null
+++ b/lib/core/sidenav-layout/directives/sidenav-layout-navigation.directive.ts
@@ -0,0 +1,26 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ContentChild, Directive, TemplateRef } from '@angular/core';
+
+@Directive({
+ selector: 'adf-sidenav-layout-navigation'
+})
+export class SidenavLayoutNavigationDirective {
+ @ContentChild(TemplateRef)
+ public template: TemplateRef;
+}
diff --git a/lib/core/sidenav-layout/helpers/animations.ts b/lib/core/sidenav-layout/helpers/animations.ts
new file mode 100644
index 0000000000..a529222be8
--- /dev/null
+++ b/lib/core/sidenav-layout/helpers/animations.ts
@@ -0,0 +1,30 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { trigger, transition, animate, style, state, AnimationTriggerMetadata } from '@angular/animations';
+
+export const sidenavAnimation: AnimationTriggerMetadata = trigger('sidenavAnimation', [
+ state('expanded', style({ width: '{{ width }}px' }), { params : { width: 0 } }),
+ state('compact', style({ width: '{{ width }}px' }), { params : { width: 0 } }),
+ transition('compact <=> expanded', animate('0.4s cubic-bezier(0.25, 0.8, 0.25, 1)'))
+]);
+
+export const contentAnimation: AnimationTriggerMetadata = trigger('contentAnimation', [
+ state('expanded', style({ 'margin-left': '{{ marginLeft }}px' }), { params : { marginLeft: 0 } }),
+ state('compact', style({'margin-left': '{{ marginLeft }}px' }), { params : { marginLeft: 0 } }),
+ transition('expanded <=> compact', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
+]);
diff --git a/lib/core/sidenav-layout/index.ts b/lib/core/sidenav-layout/index.ts
new file mode 100644
index 0000000000..4c6ac1d58f
--- /dev/null
+++ b/lib/core/sidenav-layout/index.ts
@@ -0,0 +1,18 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './public-api';
diff --git a/lib/core/sidenav-layout/public-api.ts b/lib/core/sidenav-layout/public-api.ts
new file mode 100644
index 0000000000..f4ddfbd0b1
--- /dev/null
+++ b/lib/core/sidenav-layout/public-api.ts
@@ -0,0 +1,18 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './components/sidenav-layout/sidenav-layout.component';
diff --git a/lib/core/sidenav-layout/sidenav-layout.module.ts b/lib/core/sidenav-layout/sidenav-layout.module.ts
new file mode 100644
index 0000000000..b3f7b98f41
--- /dev/null
+++ b/lib/core/sidenav-layout/sidenav-layout.module.ts
@@ -0,0 +1,47 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { MaterialModule } from '../material.module';
+import { SidenavLayoutContentDirective } from './directives/sidenav-layout-content.directive';
+import { SidenavLayoutHeaderDirective } from './directives/sidenav-layout-header.directive';
+import { SidenavLayoutNavigationDirective } from './directives/sidenav-layout-navigation.directive';
+import { SidenavLayoutComponent } from '.';
+import { LayoutContainerComponent } from './components/layout-container/layout-container.component';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ MaterialModule
+ ],
+ exports: [
+ SidenavLayoutHeaderDirective,
+ SidenavLayoutContentDirective,
+ SidenavLayoutNavigationDirective,
+ SidenavLayoutComponent,
+ LayoutContainerComponent
+ ],
+ declarations: [
+ SidenavLayoutHeaderDirective,
+ SidenavLayoutContentDirective,
+ SidenavLayoutNavigationDirective,
+ SidenavLayoutComponent,
+ LayoutContainerComponent
+ ]
+})
+export class SidenavLayoutModule {}