Add side menu for header.

This commit is contained in:
Florian THIERRY
2024-04-22 15:57:22 +02:00
parent fae709a254
commit 7f5d52dce5
11 changed files with 218 additions and 92 deletions

View File

@@ -1,5 +1,5 @@
<div> <div>
<button type="button"> <button type="button" (click)="sideMenu.open()">
<mat-icon>menu</mat-icon> <mat-icon>menu</mat-icon>
</button> </button>
<a [routerLink]="['/home']"> <a [routerLink]="['/home']">
@@ -20,4 +20,5 @@
<ng-template #anonymousRightMenu> <ng-template #anonymousRightMenu>
<a [routerLink]="['/login']">Login</a> <a [routerLink]="['/login']">Login</a>
</ng-template> </ng-template>
</div> </div>
<app-side-menu #sideMenu></app-side-menu>

View File

@@ -90,4 +90,8 @@ $headerHeight: 3.5em;
} }
} }
} }
}
app-side-menu {
height: 100%;
} }

View File

@@ -4,11 +4,12 @@ import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { AuthenticationService } from '../../core/service/authentication.service'; import { AuthenticationService } from '../../core/service/authentication.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SideMenuComponent } from '../side-menu/side-menu.component';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
standalone: true, standalone: true,
imports: [CommonModule, MatButtonModule, MatIconModule, RouterModule], imports: [CommonModule, MatButtonModule, MatIconModule, RouterModule, SideMenuComponent],
templateUrl: './header.component.html', templateUrl: './header.component.html',
styleUrl: './header.component.scss', styleUrl: './header.component.scss',
}) })

View File

@@ -0,0 +1,11 @@
<div class="category {{category.isOpenned ? 'openned' : ''}}" *ngFor="let category of categories$ | async">
<div id="category-{{category.id}}" class="category-header" (click)="setOpenned(category)">
{{category.name}}
<mat-icon>chevron_right</mat-icon>
</div>
<div class="sub-category-container {{category.isOpenned ? 'displayed' : ''}}">
<a [routerLink]="['/']" class="sub-category" *ngFor="let subCategory of category.subCategories">
{{subCategory.name}}
</a>
</div>
</div>

View File

@@ -0,0 +1,57 @@
:host {
display: flex;
flex-direction: column;
.category {
transition: background-color .2s ease-in-out;
&:hover {
cursor: pointer;
background-color: #5c6bc0;
}
.category-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: .5em 1em;
mat-icon {
transition: transform .2s ease-in-out;
}
}
&.openned {
.category-header {
mat-icon {
transform: rotate(90deg);
}
}
.sub-category-container {
max-height: none;
}
}
.sub-category-container {
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 0;
transition: max-height .2s ease-in-out;
background-color: #303f9f;
.sub-category {
padding: .5em 1em .5em 2em;
text-decoration: none;
color: inherit;
transition: background-color .2s ease-in-out;
&:hover {
background-color: #5c6bc0;
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
import { CommonModule } from "@angular/common";
import { Component, inject } from "@angular/core";
import { MatIconModule } from "@angular/material/icon";
import { DisplayableCategory, SideMenuService } from "../side-menu.service";
import { Observable } from "rxjs";
import { RouterModule } from "@angular/router";
@Component({
selector: 'app-categories-menu',
standalone: true,
imports: [CommonModule, RouterModule, MatIconModule],
templateUrl: './categories-menu.component.html',
styleUrl: './categories-menu.component.scss'
})
export class CategoriesMenuComponent {
private sideMenuService = inject(SideMenuService);
get categories$(): Observable<DisplayableCategory[]> {
return this.sideMenuService.categories$;
}
setOpenned(category: DisplayableCategory): void {
if (category.isOpenned) {
const categoryDiv = document.getElementById(`category-${category.id}`);
if (categoryDiv) {
this.closeAccordion(categoryDiv);
}
} else {
const categoriesDivs = document.getElementsByClassName('category-header');
Array.from(categoriesDivs)
.map(category => category as HTMLElement)
.forEach(categoryDiv => this.closeAccordion(categoryDiv));
const categoryDiv = document.getElementById(`category-${category.id}`);
if (categoryDiv) {
this.openAccordion(categoryDiv);
}
}
this.sideMenuService.setOpenned(category);
}
private closeAccordion(categoryDiv: HTMLElement): void {
const divContent = categoryDiv?.nextElementSibling as HTMLElement;
divContent.style.maxHeight = '0';
}
private openAccordion(categoryDiv: HTMLElement): void {
const divContent = categoryDiv?.nextElementSibling as HTMLElement;
divContent.style.maxHeight = `${divContent.scrollHeight}px`;
}
}

View File

@@ -1,15 +1,14 @@
<h1>Codiki</h1> <div class="menu {{ isOpenned ? 'displayed' : '' }}">
<h2>Catégories</h2> <h1>
<div class="categories-container"> <span>
<div class="category {{category.isOpenned ? 'openned' : ''}}" *ngFor="let category of categories$ | async"> <img src="assets/images/codiki.png" alt="logo"/>
<div id="category-{{category.id}}" class="category-header" (click)="setOpenned(category)"> Codiki
{{category.name}} </span>
<mat-icon>chevron_right</mat-icon> <button type="button" (click)="close()" matTooltip="Close the menu">
</div> <mat-icon>close</mat-icon>
<div class="sub-category-container {{category.isOpenned ? 'displayed' : ''}}"> </button>
<div class="sub-category" *ngFor="let subCategory of category.subCategories"> </h1>
{{subCategory.name}} <h2>Catégories</h2>
</div> <app-categories-menu></app-categories-menu>
</div>
</div>
</div> </div>
<div class="overlay {{ isOpenned ? 'displayed' : ''}}" (click)="close()"></div>

View File

@@ -1,52 +1,83 @@
:host { :host {
display: flex; .menu {
flex-direction: column;
.categories-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: #3f51b5;
color: white;
.category { $categoriesMenuWidth: 20em;
border: 1px solid blue; position: fixed;
top: 0;
left: -$categoriesMenuWidth - 1em - 1;
bottom: 0;
transition: left .2s ease-in-out;
width: $categoriesMenuWidth;
z-index: 2;
padding: 1em 0;
&:hover { &.displayed {
cursor: pointer; left: 0;
} }
.category-header { h1 {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 1em;
span {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: start;
align-items: center; align-items: center;
padding: .5em 1em; gap: .5em;
mat-icon { img {
transition: transform .2s ease-in-out; $imageSize: 1.2em;
width: $imageSize;
height: $imageSize;
} }
} }
&.openned { button {
.category-header { border-radius: 10em;
mat-icon { border: none;
transform: rotate(90deg); display: flex;
} justify-content: center;
} align-items: center;
width: 2.5em;
height: 2.5em;
color: white;
background-color: #3f51b5;
transition: background-color .2s ease-in-out;
.sub-category-container { &:hover {
max-height: none; cursor: pointer;
} background-color: #5c6bc0;
}
.sub-category-container {
border: 1px solid red;
overflow: hidden;
max-height: 0;
transition: max-height .2s ease-in-out;
.sub-category {
padding: .5em 1em .5em 2em;
} }
} }
} }
h2 {
padding: 0 1em;
}
} }
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: #000;
opacity: .2;
&.displayed {
display: block;
}
}
}

View File

@@ -1,51 +1,23 @@
import { CommonModule } from '@angular/common'; import {Component} from '@angular/core';
import { Component, inject } from '@angular/core'; import {MatIconModule} from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon'; import {CategoriesMenuComponent} from './categories-menu/categories-menu.component';
import { DisplayableCategory, SideMenuService } from './side-menu.service'; import {MatTooltipModule} from '@angular/material/tooltip';
import { Observable } from 'rxjs';
@Component({ @Component({
selector: 'app-side-menu', selector: 'app-side-menu',
standalone: true, standalone: true,
imports: [CommonModule, MatIconModule], imports: [CategoriesMenuComponent, MatIconModule, MatTooltipModule],
templateUrl: './side-menu.component.html', templateUrl: './side-menu.component.html',
styleUrl: './side-menu.component.scss' styleUrl: './side-menu.component.scss'
}) })
export class SideMenuComponent { export class SideMenuComponent {
private sideMenuService = inject(SideMenuService); isOpenned: boolean = false;
get categories$(): Observable<DisplayableCategory[]> { open(): void {
return this.sideMenuService.categories$; this.isOpenned = true;
} }
setOpenned(category: DisplayableCategory): void { close(): void {
if (category.isOpenned) { this.isOpenned = false;
const categoryDiv = document.getElementById(`category-${category.id}`);
if (categoryDiv) {
this.closeAccordion(categoryDiv);
}
} else {
const categoriesDivs = document.getElementsByClassName('category-header');
Array.from(categoriesDivs)
.map(category => category as HTMLElement)
.forEach(categoryDiv => this.closeAccordion(categoryDiv));
const categoryDiv = document.getElementById(`category-${category.id}`);
if (categoryDiv) {
this.openAccordion(categoryDiv);
}
}
this.sideMenuService.setOpenned(category);
}
private closeAccordion(categoryDiv: HTMLElement): void {
const divContent = categoryDiv?.nextElementSibling as HTMLElement;
divContent.style.maxHeight = '0';
}
private openAccordion(categoryDiv: HTMLElement): void {
const divContent = categoryDiv?.nextElementSibling as HTMLElement;
divContent.style.maxHeight = `${divContent.scrollHeight}px`;
} }
} }

View File

@@ -1,2 +1 @@
<h1>Welcome to Codiki application!</h1> <h1>Welcome to Codiki application!</h1>
<app-side-menu></app-side-menu>

View File

@@ -1,10 +1,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { SideMenuComponent } from '../../components/side-menu/side-menu.component';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
standalone: true, standalone: true,
imports: [SideMenuComponent], imports: [],
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrl: './home.component.scss' styleUrl: './home.component.scss'
}) })