[ACA-20] Sidenav - submenu support (#779)

* transform children data

* update navigation schema for children

* sidenav submenu

* update style

* disable extansion panel animation

* child routerLinkActive template reference

* getApplicationNavigation test

* minimised submenu indicator

* sort navigaton children
This commit is contained in:
Cilibiu Bogdan 2018-11-06 14:08:57 +02:00 committed by Suzana Dirla
parent 264597439b
commit 88678852e7
6 changed files with 240 additions and 39 deletions

View File

@ -173,7 +173,7 @@
},
"navBarLinkRef": {
"type": "object",
"required": ["id", "icon", "title", "route"],
"required": ["id", "icon", "title"],
"properties": {
"id": {
"description": "Unique identifier",
@ -199,6 +199,12 @@
"description": "Element order",
"type": "number"
},
"children": {
"description": "Navigation children items",
"type": "array",
"items": { "$ref": "#/definitions/navBarLinkRef" },
"minItems": 1
},
"rules": {
"description": "Element rules",
"type": "object",
@ -209,7 +215,21 @@
}
}
}
}
},
"oneOf": [
{
"required": ["route"],
"not": {
"required": ["children"]
}
},
{
"required": ["children"],
"not": {
"required": ["route"]
}
}
]
},
"navBarGroupRef": {
"type": "object",

View File

@ -1,40 +1,148 @@
<div class="sidenav">
<div class="sidenav__section sidenav__section sidenav_action-menu">
<app-create-menu [expanded]="showLabel"></app-create-menu>
<app-create-menu [expanded]="showLabel"></app-create-menu>
</div>
<div *ngFor="let group of groups; trackBy: trackById"
class="sidenav__section sidenav__section--menu" >
<ul class="sidenav-menu">
<li *ngFor="let item of group.items; trackBy: trackById"
class="sidenav-menu__item"
routerLinkActive
#rla="routerLinkActive"
[attr.title]="item.description | translate">
class="sidenav__section sidenav__section--menu">
<button
[id]="item.id"
mat-icon-button
mat-ripple
[routerLink]="item.url"
[color]="rla.isActive ? 'accent': 'primary'"
[attr.aria-label]="item.title | translate"
matRippleColor="primary"
[matRippleTrigger]="rippleTrigger"
[matRippleCentered]="true">
<div class="sidenav-menu">
<div *ngFor="let item of group.items; trackBy: trackById"
routerLinkActive
#routerLink="routerLinkActive">
<mat-icon>{{ item.icon }}</mat-icon>
</button>
<ng-container *ngIf="showLabel">
<ng-container *ngIf="!item.children">
<div class="sidenav-menu__item">
<button [id]="item.id"
mat-icon-button mat-ripple
[routerLink]="item.url"
[color]="routerLink.isActive ? 'accent': 'primary'"
[attr.aria-label]="item.title | translate"
matRippleColor="primary"
[matRippleTrigger]="rippleTrigger"
[matRippleCentered]="true"
[matRippleRadius]="20">
<span #rippleTrigger
[routerLink]="item.url"
class="menu__item--label"
[hidden]="!showLabel"
[ngClass]="{
'menu__item--active': rla.isActive,
'menu__item--default': !rla.isActive
}">{{ item.title | translate }}</span>
</li>
</ul>
<mat-icon>{{ item.icon }}</mat-icon>
</button>
<span #rippleTrigger
class="menu__item--label"
[routerLink]="item.url"
[attr.aria-label]="item.title | translate"
[ngClass]="{
'menu__item--active': routerLink.isActive,
'menu__item--default': !routerLink.isActive
}">
{{ item.title | translate }}</span>
</div>
</ng-container>
<ng-container *ngIf="item.children && item.children.length">
<mat-expansion-panel [expanded]="routerLink.isActive" [@.disabled]="true">
<mat-expansion-panel-header expandedHeight="48px" collapsedHeight="48px" [id]="item.id">
<mat-panel-title>
<mat-icon [color]="routerLink.isActive? 'accent': 'primary'">{{ item.icon }}</mat-icon>
<span class="menu__item--label"
[ngClass]="{
'menu__item--active': routerLink.isActive,
'menu__item--default': !routerLink.isActive
}">
{{ item.title | translate }}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<div *ngFor="let child of item.children; trackBy: trackById"
routerLinkActive #childRouteActive="routerLinkActive"
[attr.title]="child.description | translate">
<button [id]="child.id"
mat-icon-button mat-ripple
[routerLink]="child.url"
[color]="childRouteActive.isActive ? 'accent': 'primary'"
[attr.aria-label]="child.title | translate"
matRippleColor="primary"
[matRippleTrigger]="rippleTrigger"
[matRippleCentered]="true"
[matRippleRadius]="20">
<mat-icon>{{ child.icon }}</mat-icon>
</button>
<span #rippleTrigger
[routerLink]="child.url"
class="menu__item--label"
[ngClass]="{
'menu__item--active': childRouteActive.isActive,
'menu__item--default': !childRouteActive.isActive
}">
{{ child.title | translate }}</span>
</div>
</mat-expansion-panel>
</ng-container>
</ng-container>
<ng-container *ngIf="!showLabel">
<ng-container *ngIf="!item.children">
<div class="sidenav-menu__item">
<button [id]="item.id"
mat-icon-button [routerLink]="item.url"
[color]="routerLink.isActive ? 'accent': 'primary'"
[attr.aria-label]="item.title | translate">
<mat-icon>{{ item.icon }}</mat-icon>
</button>
</div>
</ng-container>
<ng-container *ngIf="item.children && item.children.length">
<div class="sidenav-menu__item">
<button [id]="item.id"
color="accent"
mat-mini-fab
mat-icon-button
disableRipple="true"
matRipple
[matRippleCentered]="true"
matRippleColor="accent"
[matRippleRadius]="24"
#childMenu="matMenuTrigger"
[matMenuTriggerFor]="menu">
<mat-icon
[color]="routerLink.isActive|| childMenu.menuOpen? 'accent': 'primary'">
{{ item.icon }}
</mat-icon>
<mat-icon
[color]="routerLink.isActive|| childMenu.menuOpen? 'accent': 'primary'">
more_vert
</mat-icon>
</button>
</div>
<mat-menu #menu="matMenu"[overlapTrigger]="false">
<button mat-menu-item
*ngFor="let child of item.children; trackBy: trackById"
routerLinkActive
#menuRouterLink="routerLinkActive"
[routerLink]="child.url"
[id]="child.id">
<mat-icon [color]="menuRouterLink.isActive ? 'primary': null"
[attr.color]="menuRouterLink.isActive ? 'primary': null">
{{ child.icon }}
</mat-icon>
<span class="mat-button" [ngClass]="{ 'mat-primary': menuRouterLink.isActive }">
{{ child.title | translate }}
</span>
</button>
</mat-menu>
</ng-container>
</ng-container>
</div>
</div>
</div>
</div>
</div>

View File

@ -18,15 +18,13 @@
&__section {
padding: 8px 14px;
position: relative;
}
&-menu {
display: inline-flex;
display: flex;
flex-direction: column;
padding: 0;
margin: 0;
list-style-type: none;
}
&-menu__item {
@ -48,4 +46,18 @@
.menu__item--label:focus {
outline: none;
}
.mat-expansion-panel-header {
padding: 0 8px !important;
}
.mat-expansion-panel-header-title span {
margin-left: 18px;
}
.mat-expansion-panel-header-title {
display: flex;
flex-direction: row;
align-items: center;
}
}

View File

@ -11,6 +11,25 @@
background-color: mat-color($background, background);
.mat-expansion-panel {
background-color: unset;
color: mat-color($primary, 0.87) !important;
}
.mat-expansion-panel {
box-shadow: none !important;
}
.mat-expansion-panel:not(.mat-expanded)
.mat-expansion-panel-header:not([aria-disabled='true']):hover {
background: none !important;
}
.mat-mini-fab {
box-shadow: unset !important;
background-color: unset !important;
}
.adf-sidebar-action-menu-button {
background-color: mat-color($accent);
}

View File

@ -618,4 +618,25 @@ describe('AppExtensionService', () => {
expect(result[0].id).toBe('1');
expect(result[1].id).toBe('3');
});
describe('getApplicationNavigation', () => {
it('should create navigation data', () => {
const navigation = service.getApplicationNavigation([
{ items: [{ route: 'route1' }, { route: 'route2' }] },
{ items: [{ children: [{ route: 'route3' }] }] }
]);
expect(navigation).toEqual([
{
items: [
{ route: 'route1', url: '/route1' },
{ route: 'route2', url: '/route2' }
]
},
{
items: [{ children: [{ route: 'route3', url: '/route3' }] }]
}
]);
});
});
});

View File

@ -185,11 +185,31 @@ export class AppExtensionService implements RuleContext {
return {
...group,
items: (group.items || [])
.filter(item => {
return this.filterByRules(item);
})
.filter(item => this.filterByRules(item))
.sort(sortByOrder)
.map(item => {
if (item.children && item.children.length > 0) {
item.children = item.children
.filter(child => this.filterByRules(child))
.sort(sortByOrder)
.map(child => {
const childRouteRef = this.extensions.getRouteById(
child.route
);
const childUrl = `/${
childRouteRef ? childRouteRef.path : child.route
}`;
return {
...child,
url: childUrl
};
});
return {
...item
};
}
const routeRef = this.extensions.getRouteById(item.route);
const url = `/${routeRef ? routeRef.path : item.route}`;
return {
@ -197,6 +217,7 @@ export class AppExtensionService implements RuleContext {
url
};
})
.reduce(reduceEmptyMenus, [])
};
});
}