Add side menu for header.
This commit is contained in:
@@ -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>
|
||||||
@@ -90,4 +90,8 @@ $headerHeight: 3.5em;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app-side-menu {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -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',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
<h1>Welcome to Codiki application!</h1>
|
<h1>Welcome to Codiki application!</h1>
|
||||||
<app-side-menu></app-side-menu>
|
|
||||||
@@ -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'
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user