[ADF-3512] SidenavLayoutComponent option to show the sidebar on the right (#3768)

* add sidebar end start property

* add demo and test

* fix test

* fix failing test
This commit is contained in:
Eugenio Romano
2018-09-12 10:02:24 +01:00
committed by GitHub
parent 3d5da1e622
commit cc396e2a11
16 changed files with 186 additions and 79 deletions

View File

@@ -1,8 +1,19 @@
<adf-sidenav-layout [sidenavMin]="70" [sidenavMax]="220" [stepOver]="780" [hideSidenav]="hideSidenav" [expandedSidenav]= "expandedSidenav" (expanded)="setState($event)"> <adf-sidenav-layout [sidenavMin]="70" [sidenavMax]="220" [stepOver]="780" [hideSidenav]="hideSidenav"
[expandedSidenav]="expandedSidenav" (expanded)="setState($event)"
[position]="position">
<adf-sidenav-layout-header> <adf-sidenav-layout-header>
<ng-template let-toggleMenu=" toggleMenu"> <ng-template let-toggleMenu=" toggleMenu">
<adf-layout-header id="adf-header" [title]="title | translate" [redirectUrl]="redirectUrl" [logo]="logo" [tooltip]="tooltip | translate" [showSidenavToggle]="showMenu" [color]="color" (clicked)=toggleMenu($event) >
<adf-layout-header id="adf-header"
[title]="title | translate"
[redirectUrl]="redirectUrl"
[logo]="logo"
[tooltip]="tooltip | translate"
[showSidenavToggle]="showMenu"
[color]="color"
[position]="position"
(clicked)=toggleMenu($event)>
<div class="adf-app-layout-menu-spacer"></div> <div class="adf-app-layout-menu-spacer"></div>
@@ -31,15 +42,19 @@
<adf-sidenav-layout-navigation> <adf-sidenav-layout-navigation>
<ng-template let-isMenuMinimized="isMenuMinimized"> <ng-template let-isMenuMinimized="isMenuMinimized">
<mat-nav-list class="adf-sidenav-linklist"> <mat-nav-list class="adf-sidenav-linklist">
<a mat-list-item *ngFor="let link of links" [attr.data-automation-id]="link.title | translate" [routerLink]="link.href" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }" class="adf-sidenav-link"> <a mat-list-item *ngFor="let link of links" [attr.data-automation-id]="link.title | translate"
[routerLink]="link.href" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }"
class="adf-sidenav-link">
<mat-icon matListIcon class="sidenav-menu-icon">{{link.icon}}</mat-icon> <mat-icon matListIcon class="sidenav-menu-icon">{{link.icon}}</mat-icon>
<div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">{{link.title | translate }}</div> <div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">{{link.title | translate }}</div>
</a> </a>
<a mat-list-item adf-logout [enabelRedirect]="enabelRedirect" redirectUri="/logout" class="adf-sidenav-link"> <a mat-list-item adf-logout [enabelRedirect]="enabelRedirect" redirectUri="/logout"
class="adf-sidenav-link">
<mat-icon matListIcon class="sidenav-menu-icon">exit_to_app</mat-icon> <mat-icon matListIcon class="sidenav-menu-icon">exit_to_app</mat-icon>
<div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">Logout</div> <div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">Logout</div>
</a> </a>
</mat-nav-list> </mat-nav-list>
</ng-template> </ng-template>
</adf-sidenav-layout-navigation> </adf-sidenav-layout-navigation>

View File

@@ -61,6 +61,8 @@ export class AppLayoutComponent implements OnInit {
expandedSidenav = false; expandedSidenav = false;
position = 'start';
hideSidenav = false; hideSidenav = false;
showMenu = true; showMenu = true;
@@ -87,6 +89,7 @@ export class AppLayoutComponent implements OnInit {
this.headerService.logo.subscribe(path => this.logo = path); this.headerService.logo.subscribe(path => this.logo = path);
this.headerService.redirectUrl.subscribe(redirectUrl => this.redirectUrl = redirectUrl); this.headerService.redirectUrl.subscribe(redirectUrl => this.redirectUrl = redirectUrl);
this.headerService.tooltip.subscribe(tooltip => this.tooltip = tooltip); this.headerService.tooltip.subscribe(tooltip => this.tooltip = tooltip);
this.headerService.position.subscribe(position => this.position = position);
} }
constructor( constructor(

View File

@@ -13,14 +13,16 @@
<option value="warn">Warn</option> <option value="warn">Warn</option>
</select> </select>
OR OR
<input type="text" name="color" (keyup.enter)="changeColor($event.target.value)" placeholder="hex color code"> <input type="text" name="color" (keyup.enter)="changeColor($event.target.value)"
placeholder="hex color code">
<p>*Choose only one value at a time: either hex code or theme color.</p> <p>*Choose only one value at a time: either hex code or theme color.</p>
<p>*press enter for submitting new hex color</p> <p>*press enter for submitting new hex color</p>
</div> </div>
<div> <div>
<label>Change title</label> <label>Change title</label>
<input type="text" name="title" (keyup.enter)="submitTitle($event.target.value)" placeholder ="{{ 'APP_LAYOUT.APP_NAME' | translate}}"> <input type="text" name="title" (keyup.enter)="submitTitle($event.target.value)"
placeholder="{{ 'APP_LAYOUT.APP_NAME' | translate}}">
<p>*press enter for submitting new title</p> <p>*press enter for submitting new title</p>
</div> </div>
@@ -42,5 +44,15 @@
<input type="text" placeholder="Tooltip text" (keyup.enter)="submitTooltip($event.target.value)"> <input type="text" placeholder="Tooltip text" (keyup.enter)="submitTooltip($event.target.value)">
<p>*press enter for submitting new tooltip</p> <p>*press enter for submitting new tooltip</p>
</div> </div>
<div>
<label>Sidebar Position </label>
<mat-radio-group [(ngModel)]="position" (change)="changePosition()">
<mat-radio-button value="start">Start</mat-radio-button>
<mat-radio-button value="end">End</mat-radio-button>
</mat-radio-group>
</div>
</mat-card> </mat-card>
</div> </div>

View File

@@ -24,8 +24,10 @@ import { HeaderDataService } from './header-data.service';
}) })
export class HeaderDataComponent { export class HeaderDataComponent {
checkbox = true; checkbox = true;
position = 'start';
constructor(private headerService: HeaderDataService) {} constructor(private headerService: HeaderDataService) {
}
hideButton() { hideButton() {
this.headerService.hideMenuButton(); this.headerService.hideMenuButton();
@@ -59,4 +61,8 @@ export class HeaderDataComponent {
this.headerService.changeTooltip(tooltip); this.headerService.changeTooltip(tooltip);
} }
} }
changePosition() {
this.headerService.changePosition(this.position);
}
} }

View File

@@ -28,6 +28,7 @@ export class HeaderDataService {
@Output() logo: EventEmitter<string> = new EventEmitter(); @Output() logo: EventEmitter<string> = new EventEmitter();
@Output() redirectUrl: EventEmitter<string | any[]> = new EventEmitter(); @Output() redirectUrl: EventEmitter<string | any[]> = new EventEmitter();
@Output() tooltip: EventEmitter<string> = new EventEmitter(); @Output() tooltip: EventEmitter<string> = new EventEmitter();
@Output() position: EventEmitter<string> = new EventEmitter();
hideMenuButton() { hideMenuButton() {
this.show = !this.show; this.show = !this.show;
@@ -54,4 +55,8 @@ export class HeaderDataService {
changeTooltip(tooltip: string) { changeTooltip(tooltip: string) {
this.tooltip.emit(tooltip); this.tooltip.emit(tooltip);
} }
changePosition(position) {
this.position.emit(position);
}
} }

View File

@@ -44,6 +44,7 @@ body of the element:
| tooltip | `string` | The tooltip text for the application logo. | tooltip | `string` | The tooltip text for the application logo.
| color | `string` | Background color for the header. It can be any hex color code or the Material theme colors: 'primary', 'accent' or 'warn'. | color | `string` | Background color for the header. It can be any hex color code or the Material theme colors: 'primary', 'accent' or 'warn'.
| showSidenavToggle | `boolean` | Signals if the sidenav button will be displayed in the header or not. By default is true. | showSidenavToggle | `boolean` | Signals if the sidenav button will be displayed in the header or not. By default is true.
| position | `string` | 'start' | The side that the drawer is attached to 'start' or 'end' page |
### Events ### Events

View File

@@ -73,6 +73,7 @@ sub-components (note the use of `<ng-template>` in the sub-components' body sect
| sidenavMax | `number` | | Maximum size of the navigation region | | sidenavMax | `number` | | Maximum size of the navigation region |
| sidenavMin | `number` | | Minimum size of the navigation region | | sidenavMin | `number` | | Minimum size of the navigation region |
| stepOver | `number` | | Screen size at which display switches from small screen to large screen configuration | | stepOver | `number` | | Screen size at which display switches from small screen to large screen configuration |
| position | `string` | 'start' | The side that the drawer is attached to 'start' or 'end' page |
### Events ### Events

View File

@@ -1,5 +1,6 @@
<mat-toolbar color="{{color}}" [style.background-color]="color"> <mat-toolbar color="{{color}}" [style.background-color]="color">
<button *ngIf="showSidenavToggle" data-automation-id="adf-menu-icon" class="mat-icon-button adf-menu-icon" mat-icon-button (click)="toggleMenu()"> <button *ngIf="showSidenavToggle && position === 'start'" id="adf-sidebar-toggle-start" data-automation-id="adf-menu-icon"
class="mat-icon-button adf-menu-icon" mat-icon-button (click)="toggleMenu()">
<mat-icon class="mat-icon material-icon" role="img" aria-hidden="true">menu</mat-icon> <mat-icon class="mat-icon material-icon" role="img" aria-hidden="true">menu</mat-icon>
</button> </button>
@@ -9,5 +10,11 @@
<span fxFlex="1 1 auto" fxShow fxHide.lt-sm="true" class="adf-app-title">{{title}}</span> <span fxFlex="1 1 auto" fxShow fxHide.lt-sm="true" class="adf-app-title">{{title}}</span>
<ng-content></ng-content> <ng-content></ng-content>
<button *ngIf="showSidenavToggle && position === 'end'" id="adf-sidebar-toggle-end" data-automation-id="adf-menu-icon"
class="mat-icon-button adf-menu-icon" mat-icon-button (click)="toggleMenu()">
<mat-icon class="mat-icon material-icon" role="img" aria-hidden="true">menu</mat-icon>
</button>
</mat-toolbar> </mat-toolbar>

View File

@@ -121,6 +121,26 @@ describe('HeaderLayoutComponent', () => {
const button = fixture.nativeElement.querySelector('.adf-menu-icon'); const button = fixture.nativeElement.querySelector('.adf-menu-icon');
expect(button === null).toBeTruthy(); expect(button === null).toBeTruthy();
}); });
it('if position is end the button menu should be at the end', () => {
component.position = 'end';
fixture.detectChanges();
const buttonStart = fixture.nativeElement.querySelector('#adf-sidebar-toggle-start');
const buttonEnd = fixture.nativeElement.querySelector('#adf-sidebar-toggle-end');
expect(buttonStart === null).toBeTruthy();
expect(buttonEnd === null).toBeFalsy();
});
it('if position is start the button menu should be at the start', () => {
component.position = 'start';
fixture.detectChanges();
const buttonStart = fixture.nativeElement.querySelector('#adf-sidebar-toggle-start');
const buttonEnd = fixture.nativeElement.querySelector('#adf-sidebar-toggle-end');
expect(buttonStart === null).toBeFalsy();
expect(buttonEnd === null).toBeTruthy();
});
}); });
describe('Template tranclusion', () => { describe('Template tranclusion', () => {

View File

@@ -32,6 +32,9 @@ export class HeaderLayoutComponent implements OnInit {
@Input() showSidenavToggle: boolean = true; @Input() showSidenavToggle: boolean = true;
@Output() clicked = new EventEmitter<any>(); @Output() clicked = new EventEmitter<any>();
/** The side that the drawer is attached to 'start' | 'end' page */
@Input() position = 'start';
toggleMenu() { toggleMenu() {
this.clicked.emit(true); this.clicked.emit(true);
} }

View File

@@ -1,5 +1,6 @@
<mat-sidenav-container> <mat-sidenav-container>
<mat-sidenav <mat-sidenav
[position]="position"
[disableClose]="!isMobileScreenSize" [disableClose]="!isMobileScreenSize"
[ngClass]="{ 'sidenav--hidden': hideSidenav }" [ngClass]="{ 'sidenav--hidden': hideSidenav }"
[@sidenavAnimation]="sidenavAnimationState" [@sidenavAnimation]="sidenavAnimationState"
@@ -9,7 +10,7 @@
</mat-sidenav> </mat-sidenav>
<div> <div>
<div [@contentAnimation]="contentAnimationState"> <div [@contentAnimationLeft]="getContentAnimationStateLeft()" [@contentAnimationRight]="getContentAnimationStateRight()">
<ng-content select="[app-layout-content]"></ng-content> <ng-content select="[app-layout-content]"></ng-content>
</div> </div>
</div> </div>

View File

@@ -17,14 +17,14 @@
import { Component, Input, ViewChild, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'; import { Component, Input, ViewChild, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
import { MatSidenav } from '@angular/material'; import { MatSidenav } from '@angular/material';
import { sidenavAnimation, contentAnimation } from '../../helpers/animations'; import { sidenavAnimation, contentAnimationLeft, contentAnimationRight } from '../../helpers/animations';
@Component({ @Component({
selector: 'adf-layout-container', selector: 'adf-layout-container',
templateUrl: './layout-container.component.html', templateUrl: './layout-container.component.html',
styleUrls: ['./layout-container.component.scss'], styleUrls: ['./layout-container.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
animations: [ sidenavAnimation, contentAnimation ] animations: [sidenavAnimation, contentAnimationLeft, contentAnimationRight]
}) })
export class LayoutContainerComponent implements OnInit, OnDestroy { export class LayoutContainerComponent implements OnInit, OnDestroy {
@Input() sidenavMin: number; @Input() sidenavMin: number;
@@ -36,6 +36,9 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
@Input() hideSidenav = false; @Input() hideSidenav = false;
@Input() expandedSidenav = true; @Input() expandedSidenav = true;
/** The side that the drawer is attached to 'start' | 'end' page */
@Input() position = 'start';
@ViewChild(MatSidenav) sidenav: MatSidenav; @ViewChild(MatSidenav) sidenav: MatSidenav;
sidenavAnimationState: any; sidenavAnimationState: any;
@@ -53,9 +56,9 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
this.SIDENAV_STATES.EXPANDED = { 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.SIDENAV_STATES.COMPACT = { value: 'compact', params: { width: this.sidenavMin } };
this.CONTENT_STATES.MOBILE = { value: 'expanded', params: { marginLeft: 0 } }; this.CONTENT_STATES.MOBILE = { value: 'expanded', params: { margin: 0 } };
this.CONTENT_STATES.EXPANDED = { value: 'expanded', params: { marginLeft: this.sidenavMin } }; this.CONTENT_STATES.EXPANDED = { value: 'expanded', params: { margin: this.sidenavMin } };
this.CONTENT_STATES.COMPACT = { value: 'compact', params: { marginLeft: this.sidenavMax } }; this.CONTENT_STATES.COMPACT = { value: 'compact', params: { margin: this.sidenavMax } };
this.mediaQueryList.addListener(this.onMediaQueryChange); this.mediaQueryList.addListener(this.onMediaQueryChange);
@@ -110,4 +113,21 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED; this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED;
this.contentAnimationState = this.toggledContentAnimation; this.contentAnimationState = this.toggledContentAnimation;
} }
getContentAnimationStateLeft() {
if (this.position === 'start') {
return this.contentAnimationState;
} else {
return { value: 'compact', params: { width: this.sidenavMin } };
}
}
getContentAnimationStateRight() {
if (this.position === 'end') {
return this.contentAnimationState;
} else {
return { value: 'compact', params: { width: this.sidenavMin } };
}
}
} }

View File

@@ -1,9 +1,11 @@
<div class="sidenav-layout"> <div class="sidenav-layout">
<ng-container *ngIf="!isHeaderInside"> <ng-container *ngIf="!isHeaderInside">
<ng-container class="adf-sidenav-layout-outer-header" *ngTemplateOutlet="headerTemplate; context:templateContext"></ng-container> <ng-container class="adf-sidenav-layout-outer-header"
*ngTemplateOutlet="headerTemplate; context:templateContext"></ng-container>
</ng-container> </ng-container>
<adf-layout-container #container <adf-layout-container #container
[position]="position"
[sidenavMin]="sidenavMin" [sidenavMin]="sidenavMin"
[sidenavMax]="sidenavMax" [sidenavMax]="sidenavMax"
[mediaQueryList]="mediaQueryList" [mediaQueryList]="mediaQueryList"
@@ -12,7 +14,8 @@
data-automation-id="adf-layout-container" data-automation-id="adf-layout-container"
class="layout__content"> class="layout__content">
<ng-container app-layout-navigation *ngTemplateOutlet="navigationTemplate; context:templateContext"></ng-container> <ng-container app-layout-navigation
*ngTemplateOutlet="navigationTemplate; context:templateContext"></ng-container>
<ng-container app-layout-content> <ng-container app-layout-content>
<ng-container *ngIf="isHeaderInside"> <ng-container *ngIf="isHeaderInside">

View File

@@ -38,6 +38,7 @@ import { CommonModule } from '@angular/common';
export class DummyLayoutContainerComponent { export class DummyLayoutContainerComponent {
@Input() sidenavMin: number; @Input() sidenavMin: number;
@Input() sidenavMax: number; @Input() sidenavMax: number;
@Input() position: string;
@Input() mediaQueryList: MediaQueryList; @Input() mediaQueryList: MediaQueryList;
@Input() hideSidenav: boolean; @Input() hideSidenav: boolean;
@Input() expandedSidenav: boolean; @Input() expandedSidenav: boolean;

View File

@@ -31,6 +31,9 @@ export class SidenavLayoutComponent implements OnInit, AfterViewInit, OnDestroy
static STEP_OVER = 600; static STEP_OVER = 600;
/** The side that the drawer is attached to 'start' | 'end' page */
@Input() position = 'start';
@Input() sidenavMin: number; @Input() sidenavMin: number;
@Input() sidenavMax: number; @Input() sidenavMax: number;
@Input() stepOver: number; @Input() stepOver: number;

View File

@@ -23,8 +23,14 @@ export const sidenavAnimation: AnimationTriggerMetadata = trigger('sidenavAnimat
transition('compact <=> expanded', animate('0.4s cubic-bezier(0.25, 0.8, 0.25, 1)')) transition('compact <=> expanded', animate('0.4s cubic-bezier(0.25, 0.8, 0.25, 1)'))
]); ]);
export const contentAnimation: AnimationTriggerMetadata = trigger('contentAnimation', [ export const contentAnimationLeft: AnimationTriggerMetadata = trigger('contentAnimationLeft', [
state('expanded', style({ 'margin-left': '{{ marginLeft }}px' }), { params : { marginLeft: 0 } }), state('expanded', style({ 'margin-left': '{{ margin }}px' }), { params : { margin: 0 } }),
state('compact', style({'margin-left': '{{ marginLeft }}px' }), { params : { marginLeft: 0 } }), state('compact', style({'margin-left': '{{ margin }}px' }), { params : { margin: 0 } }),
transition('expanded <=> compact', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
]);
export const contentAnimationRight: AnimationTriggerMetadata = trigger('contentAnimationRight', [
state('expanded', style({ 'margin-right': '{{ margin }}px' }), { params : { margin: 0 } }),
state('compact', style({'margin-right': '{{ margin }}px' }), { params : { margin: 0 } }),
transition('expanded <=> compact', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)')) transition('expanded <=> compact', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
]); ]);