mirror of
https://github.com/google/nomulus
synced 2026-08-03 13:56:12 +00:00
Restyle registrar console based on the new design proposal (#2336)
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<mat-tree
|
||||
[dataSource]="dataSource"
|
||||
[treeControl]="treeControl"
|
||||
class="console-app__nav-tree"
|
||||
>
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node"
|
||||
matTreeNodeToggle
|
||||
(click)="onClick(node)"
|
||||
[class.active]="router.url.endsWith(node.path)"
|
||||
>
|
||||
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
|
||||
{{ node.iconName }}
|
||||
</mat-icon>
|
||||
{{ node.title }}
|
||||
</mat-tree-node>
|
||||
<mat-nested-tree-node
|
||||
*matTreeNodeDef="let node; when: hasChild"
|
||||
(click)="onClick(node)"
|
||||
>
|
||||
<div class="mat-tree-node" [class.active]="router.url.endsWith(node.path)">
|
||||
<button
|
||||
class="console-app__nav-icon_expand"
|
||||
mat-icon-button
|
||||
matTreeNodeToggle
|
||||
[attr.aria-label]="'Toggle ' + node.title"
|
||||
>
|
||||
<mat-icon>
|
||||
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
|
||||
{{ node.iconName }}
|
||||
</mat-icon>
|
||||
{{ node.title }}
|
||||
</div>
|
||||
<div
|
||||
[class.console-app__nav-tree_invisible]="!treeControl.isExpanded(node)"
|
||||
role="group"
|
||||
>
|
||||
<ng-container matTreeNodeOutlet></ng-container>
|
||||
</div>
|
||||
</mat-nested-tree-node>
|
||||
</mat-tree>
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
$expand-icon-size: 26px;
|
||||
|
||||
.console-app {
|
||||
&__sidebar {
|
||||
min-width: 300px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&__nav-icon {
|
||||
width: $expand-icon-size;
|
||||
height: $expand-icon-size;
|
||||
color: var(--secondary) !important;
|
||||
margin-right: $expand-icon-size;
|
||||
|
||||
&_expand {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
padding: 0px;
|
||||
width: $expand-icon-size;
|
||||
height: $expand-icon-size;
|
||||
}
|
||||
}
|
||||
|
||||
&__nav-tree {
|
||||
.mat-tree-node {
|
||||
cursor: pointer;
|
||||
padding-left: $expand-icon-size;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--light-highlight);
|
||||
border-radius: 0 25px 25px 0;
|
||||
}
|
||||
&.active {
|
||||
border-radius: 0 25px 25px 0;
|
||||
background-color: var(--lightest);
|
||||
}
|
||||
}
|
||||
|
||||
div[role="group"] > .mat-tree-node {
|
||||
// expand icon + regular icon + spacing = 3 * $expand-icon-size
|
||||
padding-left: calc($expand-icon-size * 3);
|
||||
}
|
||||
|
||||
ul,
|
||||
li {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
&_invisible {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 { NestedTreeControl } from '@angular/cdk/tree';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatTreeNestedDataSource } from '@angular/material/tree';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { RouteWithIcon, routes } from '../app-routing.module';
|
||||
|
||||
interface NavMenuNode extends RouteWithIcon {
|
||||
parentRoute?: RouteWithIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* This component is responsible for rendering navigation menu based on the allowed routes
|
||||
* and keeping UI in sync when route changes (eg highlights selected route in the menu).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-navigation',
|
||||
templateUrl: './navigation.component.html',
|
||||
styleUrls: ['./navigation.component.scss'],
|
||||
})
|
||||
export class NavigationComponent {
|
||||
renderRouter: boolean = true;
|
||||
treeControl = new NestedTreeControl<RouteWithIcon>((node) => node.children);
|
||||
dataSource = new MatTreeNestedDataSource<RouteWithIcon>();
|
||||
private subscription!: Subscription;
|
||||
hasChild = (_: number, node: RouteWithIcon) =>
|
||||
!!node.children && node.children.length > 0;
|
||||
|
||||
constructor(protected router: Router) {
|
||||
this.dataSource.data = this.ngRoutesToNavMenuNodes(routes);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscription = this.router.events.subscribe((navigationParams) => {
|
||||
if (navigationParams instanceof NavigationEnd) {
|
||||
this.syncExpandedNavigationWithRoute(navigationParams.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
syncExpandedNavigationWithRoute(url: string) {
|
||||
const maybeComponentWithChildren = this.dataSource.data.find((menuNode) => {
|
||||
return (
|
||||
// @ts-ignore - optional function added to components with children,
|
||||
// there's no availble tools to get current active router component
|
||||
typeof menuNode.component?.matchesUrl === 'function' &&
|
||||
// @ts-ignore
|
||||
menuNode.component?.matchesUrl(url)
|
||||
);
|
||||
});
|
||||
if (maybeComponentWithChildren) {
|
||||
this.treeControl.expand(maybeComponentWithChildren);
|
||||
}
|
||||
}
|
||||
|
||||
onClick(node: NavMenuNode) {
|
||||
if (node.parentRoute) {
|
||||
this.router.navigate([node.parentRoute.path + '/' + node.path], {
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
} else {
|
||||
this.router.navigate([node.path], { queryParamsHandling: 'merge' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only want to use routes with titles and we want to provide easy reference to parent node
|
||||
*/
|
||||
ngRoutesToNavMenuNodes(routes: RouteWithIcon[]): NavMenuNode[] {
|
||||
return routes
|
||||
.filter((r) => r.title)
|
||||
.map((r) => {
|
||||
if (r.children) {
|
||||
return {
|
||||
...r,
|
||||
children: r.children
|
||||
.filter((r) => r.title)
|
||||
.map((childRoute) => {
|
||||
return {
|
||||
...childRoute,
|
||||
parentRoute: r,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
return r;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user