Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.

Commit 2fef562

Browse files
committed
✨ creation du composant header et de la directive clickoutside
1 parent 9d7be8c commit 2fef562

File tree

7 files changed

+166
-28
lines changed

7 files changed

+166
-28
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
2+
3+
@Directive({
4+
selector: '[clickOutside]'
5+
})
6+
export class ClickOutsideDirective {
7+
8+
constructor(private elementRef: ElementRef) { }
9+
10+
@Output() clickOutside = new EventEmitter<MouseEvent>();
11+
12+
@HostListener('document:click', ['$event', '$event.target'])
13+
public onClick(event: MouseEvent, targetElement: HTMLElement): void {
14+
if (!targetElement) {
15+
return;
16+
}
17+
18+
const clickedInside = this.elementRef.nativeElement.contains(targetElement);
19+
20+
if (!clickedInside) {
21+
this.clickOutside.emit(event);
22+
}
23+
}
24+
25+
}

src/app/shared/shared.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { OverlapingLabelComponent } from './components/inputs/overlaping-label/o
88
import { PrimaryComponent as ButtonPrimary } from './components/buttons/primary/primary.component';
99
import { ErrorComponent } from './components/alert/error/error.component';
1010
import { SuccessComponent } from './components/alert/success/success.component';
11+
import { ClickOutsideDirective } from './directives/click-outside.directive';
1112

1213

1314
const MODULES = [CommonModule, ThemeModule];
@@ -16,6 +17,7 @@ const DECLARATIONS = [
1617
OverlapingLabelComponent,
1718
ErrorComponent,
1819
SuccessComponent,
20+
ClickOutsideDirective,
1921
];
2022

2123
@NgModule({
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<header class="p-6 border-b border-slate-100 sm:px-8">
2+
<div class="flex items-center justify-between">
3+
<div class="max-w-lg">
4+
<!-- SearchBar Component -->
5+
</div>
6+
<div class="flex items-center divide-x divide-slate-200">
7+
<a href="#" class="flex items-center pr-4 text-sm leading-5 text-slate-500 hover:text-slate-700">
8+
<svg class="w-6 h-6 mr-2 text-slate-400" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
9+
<path d="M2.5 12h3.382c.685 0 1.312.387 1.618 1 .306.613.933 1 1.618 1h5.764c.685 0 1.312-.387 1.618-1 .306-.613.933-1 1.618-1H21.5M8.967 4h6.066c1.077 0 1.616 0 2.091.164a3 3 0 0 1 1.121.693c.36.352.6.833 1.082 1.796l2.166 4.333c.19.378.284.567.35.765.06.177.102.357.128.541.029.207.029.418.029.841V15.2c0 1.68 0 2.52-.327 3.162a3 3 0 0 1-1.311 1.311C19.72 20 18.88 20 17.2 20H6.8c-1.68 0-2.52 0-3.162-.327a3 3 0 0 1-1.311-1.311C2 17.72 2 16.88 2 15.2v-2.067c0-.422 0-.634.029-.84.026-.184.068-.365.128-.541.066-.199.16-.388.35-.766l2.166-4.333c.482-.963.723-1.444 1.082-1.796a3 3 0 0 1 1.12-.693C7.352 4 7.89 4 8.968 4Z"/>
10+
</svg>
11+
Vous rencontrez un problème ?
12+
</a>
13+
<div class="flex items-center pl-4 space-x-3">
14+
<button type="button" class="inline-flex items-center p-1 text-sm leading-5 rounded-full hover:bg-slate-50 text-slate-500 hover:text-slate-900 focus:outline-none">
15+
<svg class="w-6 h-6" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
16+
<path d="M9.354 21c.705.622 1.632 1 2.646 1s1.94-.378 2.646-1M18 8A6 6 0 1 0 6 8c0 3.09-.78 5.206-1.65 6.605-.735 1.18-1.102 1.771-1.089 1.936.015.182.054.252.2.36.133.099.732.099 1.928.099H18.61c1.196 0 1.795 0 1.927-.098.147-.11.186-.179.2-.361.014-.165-.353-.755-1.088-1.936C18.78 13.206 18 11.09 18 8Z"/>
17+
</svg>
18+
</button>
19+
<div class="relative" #menuDropdown>
20+
<button
21+
type="button"
22+
class="flex items-center text-sm rounded-full focus:outline-none"
23+
id="user-menu-button"
24+
aria-expanded="false"
25+
aria-haspopup="true"
26+
(click)="toggleMobileMenu()"
27+
>
28+
<span class="sr-only">Open user menu</span>
29+
<img class="w-8 h-8 rounded-full" *ngIf="user$ | async; let user" [src]="user.profilePhotoUrl" [alt]="user.name">
30+
</button>
31+
<div
32+
[@openClose]="openCloseTrigger"
33+
class="absolute right-0 z-10 w-56 mt-2 origin-top-right bg-white divide-y rounded-md shadow-lg divide-slate-100 ring-1 ring-black ring-opacity-5 focus:outline-none"
34+
role="menu"
35+
aria-orientation="vertical"
36+
aria-labelledby="menu-button"
37+
tabindex="-1"
38+
>
39+
<div class="px-4 py-3" role="none">
40+
<p class="text-sm" role="none">Connecté avec</p>
41+
<p class="text-sm font-medium truncate text-slate-900" role="none" *ngIf="user$ | async; let user">{{ user.email }}</p>
42+
</div>
43+
<div class="py-1" role="none">
44+
<a href="#" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900" role="menuitem" tabindex="-1" id="menu-item-0">Mon compte</a>
45+
<a href="#" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900" role="menuitem" tabindex="-1" id="menu-item-1">Support</a>
46+
<a href="#" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900" role="menuitem" tabindex="-1" id="menu-item-2">Guide d'utilisation</a>
47+
</div>
48+
<div class="flex items-center justify-between py-1" role="none">
49+
<button type="button" (click)="logout()" class="block w-full px-4 py-2 text-sm text-left text-slate-700" role="menuitem" tabindex="-1" id="menu-item-3">Se déconnecter</button>
50+
<span *ngIf="(loading$ | async)" class="flex items-center px-3">
51+
<svg class="w-5 h-5 mr-3 -ml-1 text-green-500 animate-spin" fill="none" viewBox="0 0 24 24">
52+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
53+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
54+
</svg>
55+
</span>
56+
</div>
57+
</div>
58+
</div>
59+
</div>
60+
</div>
61+
</div>
62+
</header>

src/app/shared/themes/components/header/header.component.scss

Whitespace-only changes.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Store } from '@ngrx/store';
2+
import { Observable } from 'rxjs';
3+
import { Component, ElementRef, HostListener, ViewChild } from '@angular/core';
4+
import {
5+
animate,
6+
state,
7+
style,
8+
transition,
9+
trigger
10+
} from '@angular/animations';
11+
12+
import { User } from '@app/modules/user/interfaces/user.interface';
13+
import { selectCurrentUser, selectLoading } from '@app/modules/authentication/store/auth.selectors';
14+
import { logoutAction } from '@app/modules/authentication/store/auth.actions';
15+
16+
@Component({
17+
selector: 'cosna-header',
18+
templateUrl: './header.component.html',
19+
styleUrls: ['./header.component.scss'],
20+
animations: [
21+
trigger('openClose', [
22+
state(
23+
'open',
24+
style({
25+
opacity: 1,
26+
transform: 'scale(1, 1)'
27+
})
28+
),
29+
state(
30+
'closed',
31+
style({
32+
opacity: 0,
33+
transform: 'scale(0.95, 0.95)'
34+
})
35+
),
36+
transition('open => closed', [animate('100ms ease-in')]),
37+
transition('closed => open', [animate('200ms ease-out')])
38+
])
39+
]
40+
})
41+
export class HeaderComponent {
42+
43+
mobileMenuOpen!: boolean;
44+
45+
@ViewChild('menuDropdown') menuDropdown!: ElementRef;
46+
47+
public user$: Observable<User | null> = this.store.select(selectCurrentUser);
48+
49+
public loading$: Observable<boolean> = this.store.select(selectLoading);
50+
51+
public logout() {
52+
this.store.dispatch(logoutAction());
53+
}
54+
55+
get openCloseTrigger() {
56+
return this.mobileMenuOpen ? 'open' : 'closed';
57+
}
58+
59+
toggleMobileMenu(): void {
60+
this.mobileMenuOpen = !this.mobileMenuOpen;
61+
}
62+
63+
@HostListener('document:click', ['$event.target'])
64+
public onPageClick(targetElement: HTMLElement): void {
65+
const clickedInside = this.menuDropdown.nativeElement.contains(targetElement);
66+
67+
if (!clickedInside) {
68+
this.mobileMenuOpen = false;
69+
}
70+
}
71+
72+
constructor(private store: Store) { }
73+
74+
}

src/app/shared/themes/layouts/cpanel/cpanel.component.html

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,7 @@
22
<cosna-sidebar [menus]="menus"></cosna-sidebar>
33
<div class="flex flex-col flex-1 w-0 bg-white" style="box-shadow: -2px 1px 5px 0 rgba(0, 0, 0, 0.9);">
44
<div class="relative z-0 flex flex-col h-full overflow-hidden overflow-y-auto focus:ouline-none">
5-
<header class="p-6 border-b border-slate-100 sm:px-8">
6-
<div class="flex items-center justify-between">
7-
<div class="max-w-lg">
8-
<!-- SearchBar Component -->
9-
</div>
10-
<div class="flex items-center divide-x divide-slate-200">
11-
<a href="#" class="flex items-center pr-4 text-sm leading-5 text-slate-500 hover:text-slate-700">
12-
<svg class="w-6 h-6 mr-2 text-slate-400" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
13-
<path d="M2.5 12h3.382c.685 0 1.312.387 1.618 1 .306.613.933 1 1.618 1h5.764c.685 0 1.312-.387 1.618-1 .306-.613.933-1 1.618-1H21.5M8.967 4h6.066c1.077 0 1.616 0 2.091.164a3 3 0 0 1 1.121.693c.36.352.6.833 1.082 1.796l2.166 4.333c.19.378.284.567.35.765.06.177.102.357.128.541.029.207.029.418.029.841V15.2c0 1.68 0 2.52-.327 3.162a3 3 0 0 1-1.311 1.311C19.72 20 18.88 20 17.2 20H6.8c-1.68 0-2.52 0-3.162-.327a3 3 0 0 1-1.311-1.311C2 17.72 2 16.88 2 15.2v-2.067c0-.422 0-.634.029-.84.026-.184.068-.365.128-.541.066-.199.16-.388.35-.766l2.166-4.333c.482-.963.723-1.444 1.082-1.796a3 3 0 0 1 1.12-.693C7.352 4 7.89 4 8.968 4Z"/>
14-
</svg>
15-
Vous rencontrez un problème ?
16-
</a>
17-
<div class="flex items-center pl-4 space-x-2">
18-
<button type="button" class="inline-flex items-center p-1 text-sm leading-5 rounded-full hover:bg-slate-50 text-slate-500 hover:text-slate-900 focus:outline-none">
19-
<svg class="w-6 h-6" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
20-
<path d="M9.354 21c.705.622 1.632 1 2.646 1s1.94-.378 2.646-1M18 8A6 6 0 1 0 6 8c0 3.09-.78 5.206-1.65 6.605-.735 1.18-1.102 1.771-1.089 1.936.015.182.054.252.2.36.133.099.732.099 1.928.099H18.61c1.196 0 1.795 0 1.927-.098.147-.11.186-.179.2-.361.014-.165-.353-.755-1.088-1.936C18.78 13.206 18 11.09 18 8Z"/>
21-
</svg>
22-
</button>
23-
<div class="relative">
24-
<button type="button" class="flex items-center text-sm rounded-full focus:outline-none" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
25-
<span class="sr-only">Open user menu</span>
26-
<img class="w-8 h-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" alt="">
27-
</button>
28-
</div>
29-
</div>
30-
</div>
31-
</div>
32-
</header>
5+
<cosna-header></cosna-header>
336
<div class="flex flex-1 h-full px-6 lg:px-8">
347
<router-outlet></router-outlet>
358
</div>

src/app/shared/themes/theme.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AuthComponent } from './layouts/auth/auth.component';
66
import { CpanelComponent } from './layouts/cpanel/cpanel.component';
77
import { SidebarComponent } from './components/sidebar/sidebar.component';
88
import { LogoComponent } from './components/logo/logo.component';
9+
import { HeaderComponent } from './components/header/header.component';
910

1011
const MODULES = [CommonModule, RouterModule];
1112

@@ -15,6 +16,7 @@ const MODULES = [CommonModule, RouterModule];
1516
CpanelComponent,
1617
SidebarComponent,
1718
LogoComponent,
19+
HeaderComponent,
1820
],
1921
imports: MODULES,
2022
exports: [

0 commit comments

Comments
 (0)