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>
<button type="button">
<button type="button" (click)="sideMenu.open()">
<mat-icon>menu</mat-icon>
</button>
<a [routerLink]="['/home']">
@@ -20,4 +20,5 @@
<ng-template #anonymousRightMenu>
<a [routerLink]="['/login']">Login</a>
</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 { AuthenticationService } from '../../core/service/authentication.service';
import { CommonModule } from '@angular/common';
import { SideMenuComponent } from '../side-menu/side-menu.component';
@Component({
selector: 'app-header',
standalone: true,
imports: [CommonModule, MatButtonModule, MatIconModule, RouterModule],
imports: [CommonModule, MatButtonModule, MatIconModule, RouterModule, SideMenuComponent],
templateUrl: './header.component.html',
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>
<h2>Catégories</h2>
<div class="categories-container">
<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' : ''}}">
<div class="sub-category" *ngFor="let subCategory of category.subCategories">
{{subCategory.name}}
</div>
</div>
</div>
<div class="menu {{ isOpenned ? 'displayed' : '' }}">
<h1>
<span>
<img src="assets/images/codiki.png" alt="logo"/>
Codiki
</span>
<button type="button" (click)="close()" matTooltip="Close the menu">
<mat-icon>close</mat-icon>
</button>
</h1>
<h2>Catégories</h2>
<app-categories-menu></app-categories-menu>
</div>
<div class="overlay {{ isOpenned ? 'displayed' : ''}}" (click)="close()"></div>

View File

@@ -1,52 +1,83 @@
:host {
display: flex;
flex-direction: column;
.categories-container {
.menu {
display: flex;
flex-direction: column;
background-color: #3f51b5;
color: white;
.category {
border: 1px solid blue;
$categoriesMenuWidth: 20em;
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 {
cursor: pointer;
}
&.displayed {
left: 0;
}
.category-header {
h1 {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 1em;
span {
display: flex;
flex-direction: row;
justify-content: space-between;
justify-content: start;
align-items: center;
padding: .5em 1em;
gap: .5em;
mat-icon {
transition: transform .2s ease-in-out;
img {
$imageSize: 1.2em;
width: $imageSize;
height: $imageSize;
}
}
&.openned {
.category-header {
mat-icon {
transform: rotate(90deg);
}
}
button {
border-radius: 10em;
border: none;
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 {
max-height: none;
}
}
.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;
&:hover {
cursor: pointer;
background-color: #5c6bc0;
}
}
}
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, inject } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { DisplayableCategory, SideMenuService } from './side-menu.service';
import { Observable } from 'rxjs';
import {Component} from '@angular/core';
import {MatIconModule} from '@angular/material/icon';
import {CategoriesMenuComponent} from './categories-menu/categories-menu.component';
import {MatTooltipModule} from '@angular/material/tooltip';
@Component({
selector: 'app-side-menu',
standalone: true,
imports: [CommonModule, MatIconModule],
imports: [CategoriesMenuComponent, MatIconModule, MatTooltipModule],
templateUrl: './side-menu.component.html',
styleUrl: './side-menu.component.scss'
})
export class SideMenuComponent {
private sideMenuService = inject(SideMenuService);
isOpenned: boolean = false;
get categories$(): Observable<DisplayableCategory[]> {
return this.sideMenuService.categories$;
open(): void {
this.isOpenned = true;
}
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`;
close(): void {
this.isOpenned = false;
}
}

View File

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

View File

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