Browse Source

UI: add desktop sidenav collapse/expand with width animation

Sidenav width is now driven by signals — 250px expanded, 50px collapsed — with a matching margin-left animation on mat-sidenav-content. The toolbar button toggles between sidebar-close and sidebar-open icons, and the logo is hidden when the rail is collapsed. Desktop-only; mobile overlay sidenav keeps its existing behaviour.
pull/15888/head
Igor Kulikov 4 months ago
parent
commit
df83c3acae
  1. 22
      ui-ngx/src/app/modules/home/home.component.html
  2. 12
      ui-ngx/src/app/modules/home/home.component.scss
  3. 29
      ui-ngx/src/app/modules/home/home.component.ts

22
ui-ngx/src/app/modules/home/home.component.html

@ -17,16 +17,26 @@
--> -->
<mat-sidenav-container> <mat-sidenav-container>
<mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2" <mat-sidenav #sidenav class="tb-site-sidenav mat-elevation-z2"
[class.tb-desktop]="sidenavDesktop()"
[disableClose]="sidenavOpened" [disableClose]="sidenavOpened"
(click)="sidenavClicked()" (click)="sidenavClicked()"
[mode]="sidenavMode" [mode]="sidenavMode"
[opened]="sidenavOpened && !forceFullscreen"> [opened]="sidenavOpened && !forceFullscreen"
[style.width]="sidenavWidth()">
<header class="tb-nav-header"> <header class="tb-nav-header">
<mat-toolbar class="tb-nav-header-toolbar"> <mat-toolbar class="tb-nav-header-toolbar">
<div class="flex flex-1 flex-row justify-between"> <div class="flex flex-1 flex-row justify-between">
<tb-logo [src]="logo" class="tb-logo-title"></tb-logo> @if (showLogo()) {
@if (sidenavDesktop) { <tb-logo [src]="logo" class="tb-logo-title"></tb-logo>
<button mat-icon-button class="tb-mat-32"><tb-icon class="tb-mat-20">sidebar-close</tb-icon></button> }
@if (sidenavDesktop()) {
<button mat-icon-button class="tb-mat-32" (click)="toggleSidenav()">
@if (sidenavCollapsed()) {
<tb-icon class="tb-mat-20">sidebar-open</tb-icon>
} @else {
<tb-icon class="tb-mat-20">sidebar-close</tb-icon>
}
</button>
} }
</div> </div>
</mat-toolbar> </mat-toolbar>
@ -38,7 +48,9 @@
<tb-user-menu></tb-user-menu> <tb-user-menu></tb-user-menu>
</mat-toolbar> </mat-toolbar>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content> <mat-sidenav-content class="tb-site-content"
[class.tb-desktop]="sidenavDesktop()"
[style.margin-left]="sidenavWidth()">
<div class="flex flex-col" role="main" style="height: 100%;"> <div class="flex flex-col" role="main" style="height: 100%;">
<mat-toolbar class="tb-primary-toolbar flex flex-row"> <mat-toolbar class="tb-primary-toolbar flex flex-row">
<button mat-icon-button id="main" <button mat-icon-button id="main"

12
ui-ngx/src/app/modules/home/home.component.scss

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
$sidenav-anim-timing: 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
:host { :host {
display: flex; display: flex;
width: 100%; width: 100%;
@ -26,6 +29,9 @@
@media (max-width:456px) { @media (max-width:456px) {
width: calc(100% - 56px); width: calc(100% - 56px);
} }
&.tb-desktop {
transition: width $sidenav-anim-timing;
}
.tb-nav-header { .tb-nav-header {
z-index: 2; z-index: 2;
flex-shrink: 0; flex-shrink: 0;
@ -60,6 +66,12 @@
} }
} }
} }
mat-sidenav-content.tb-site-content {
&.tb-desktop {
margin-left: 250px;
transition: margin-left $sidenav-anim-timing;
}
}
.tb-primary-toolbar { .tb-primary-toolbar {
z-index: 2; z-index: 2;
height: 56px; height: 56px;

29
ui-ngx/src/app/modules/home/home.component.ts

@ -14,7 +14,17 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { AfterViewInit, Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core'; import {
AfterViewInit,
Component,
computed,
ElementRef,
Inject,
OnDestroy,
OnInit,
signal,
ViewChild
} from '@angular/core';
import { skip, startWith, Subject } from 'rxjs'; import { skip, startWith, Subject } from 'rxjs';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
@ -52,10 +62,15 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
sidenavMode: 'over' | 'push' | 'side' = 'side'; sidenavMode: 'over' | 'push' | 'side' = 'side';
sidenavOpened = true; sidenavOpened = true;
sidenavDesktop = true;
sidenavDesktop = signal(true);
sidenavCollapsed = signal(false);
sidenavWidth = computed(() => this.sidenavDesktop() ? (this.sidenavCollapsed() ? '50px' : '250px') : '');
logo = 'assets/logo_title_black.svg'; logo = 'assets/logo_title_black.svg';
showLogo= computed(() => !this.sidenavDesktop() || !this.sidenavCollapsed());
@ViewChild('sidenav') @ViewChild('sidenav')
sidenav: MatSidenav; sidenav: MatSidenav;
@ -84,7 +99,7 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']);
this.sidenavMode = isGtSm ? 'side' : 'over'; this.sidenavMode = isGtSm ? 'side' : 'over';
this.sidenavOpened = isGtSm; this.sidenavOpened = isGtSm;
this.sidenavDesktop = isGtSm; this.sidenavDesktop.set(isGtSm);
this.breakpointObserver this.breakpointObserver
.observe(MediaBreakpoints['gt-sm']) .observe(MediaBreakpoints['gt-sm'])
@ -93,11 +108,11 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
if (state.matches) { if (state.matches) {
this.sidenavMode = 'side'; this.sidenavMode = 'side';
this.sidenavOpened = true; this.sidenavOpened = true;
this.sidenavDesktop = true; this.sidenavDesktop.set(true);
} else { } else {
this.sidenavMode = 'over'; this.sidenavMode = 'over';
this.sidenavOpened = false; this.sidenavOpened = false;
this.sidenavDesktop = false; this.sidenavDesktop.set(false);
} }
} }
); );
@ -124,6 +139,10 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
} }
} }
toggleSidenav() {
this.sidenavCollapsed.update(state => !state);
}
toggleFullscreen() { toggleFullscreen() {
if (screenfull.isEnabled) { if (screenfull.isEnabled) {
screenfull.toggle(); screenfull.toggle();

Loading…
Cancel
Save