diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 81a4a46a9494b6ca71b13b2c9cd92172a3e1972b..b7b28df102d7b83095690b3ebb3e67e041125fc5 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -3,6 +3,8 @@ import { NgModule } from '@angular/core'; import { AgGridModule } from 'ag-grid-angular'; import { SharedModule } from '../shared/shared.module'; import { AdminRoutingModule } from './admin-routing.module'; +import { AdminHeaderComponent } from './components/admin-header/admin-header.component'; +import { ButtonCellRendererComponent } from './components/button-cell-renderer/button-cell-renderer.component'; import { ClaimStructureComponent } from './components/claim-structure/claim-structure.component'; import { DeletedStructuresComponent } from './components/deleted-structures/deleted-structures.component'; import { EspaceCoopCNFSComponent } from './components/espace-coop-cnfs/espace-coop-cnfs.component'; @@ -21,9 +23,7 @@ import { DeleteUserComponent } from './components/manage-users/delete-user/delet import { EmployerRendererComponent } from './components/manage-users/employer-renderer/employer-renderer.component'; import { JobRendererComponent } from './components/manage-users/job-renderer/job-renderer.component'; import { ManageUsersComponent } from './components/manage-users/manage-users.component'; -import { NavBarComponent } from './components/nav-bar/nav-bar.component'; import { AdminStructuresListComponent } from './components/structures-list/admin-structures-list.component'; -import { ButtonCellRendererComponent } from './components/button-cell-renderer/button-cell-renderer.component'; @NgModule({ declarations: [ @@ -45,7 +45,7 @@ import { ButtonCellRendererComponent } from './components/button-cell-renderer/b ManageJobsComponent, ManageEmployersComponent, DeletedStructuresComponent, - NavBarComponent, + AdminHeaderComponent, EspaceCoopCNFSComponent, ButtonCellRendererComponent, ], diff --git a/src/app/admin/admin.scss b/src/app/admin/admin.scss index 7e52d1c767865fed4c62a0fd4a48bafcad9af6f7..017d2f5ddc82bda5def5c11b21b8971ee2a98c81 100644 --- a/src/app/admin/admin.scss +++ b/src/app/admin/admin.scss @@ -1,20 +1,21 @@ @import 'color'; .adminLayout { - display: flex; - flex-direction: column; - align-items: start; - gap: 1rem; + display: block; margin: auto; - padding-bottom: 1rem; + padding: 2rem 0; width: 80%; } +h3, +h2 { + margin: 1rem 0; +} + h3.inline { display: flex; flex-direction: row; align-items: center; - gap: 1rem; } .incomplete { diff --git a/src/app/admin/components/admin-header/admin-header.component.html b/src/app/admin/components/admin-header/admin-header.component.html new file mode 100644 index 0000000000000000000000000000000000000000..462fa25f6c9da424093ebdc092744f1e78edd047 --- /dev/null +++ b/src/app/admin/components/admin-header/admin-header.component.html @@ -0,0 +1,12 @@ +<div class="header"> + <div class="title-and-ghost"> + <h1>Administration</h1> + <app-button [variant]="'secondary'" [label]="'Ghost'" [size]="'small'" (action)="openGhost()" /> + </div> + <app-nav-bar + [tabs]="getTabsNames()" + [currentTab]="currentTab" + [ariaLabel]="'Sélectionner le panneau admin'" + (clickedTab)="navigateTo($event)" + /> +</div> diff --git a/src/app/admin/components/admin-header/admin-header.component.scss b/src/app/admin/components/admin-header/admin-header.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..802b74babb06e0f176bb1994efe530a275682035 --- /dev/null +++ b/src/app/admin/components/admin-header/admin-header.component.scss @@ -0,0 +1,22 @@ +@import 'typography'; + +.header { + display: flex; + flex-direction: column; + width: 100%; + + .title-and-ghost { + display: flex; + justify-content: space-between; + width: 100%; + + h1 { + justify-content: start; + @include font-regular-24; + } + } + + app-nav-bar { + margin: 1.5rem 0; + } +} diff --git a/src/app/admin/components/nav-bar/nav-bar.component.ts b/src/app/admin/components/admin-header/admin-header.component.ts similarity index 60% rename from src/app/admin/components/nav-bar/nav-bar.component.ts rename to src/app/admin/components/admin-header/admin-header.component.ts index 42e33463f6f0fbacc3c640b149d774b477fc650c..8bdaca400c0dfda9f2ad651129d31d9b3afd2ec2 100644 --- a/src/app/admin/components/nav-bar/nav-bar.component.ts +++ b/src/app/admin/components/admin-header/admin-header.component.ts @@ -1,20 +1,19 @@ import { Component, OnInit } from '@angular/core'; -import { Router, NavigationEnd } from '@angular/router'; +import { Router } from '@angular/router'; import { ConfigService } from '../../../services/config.service'; import { AdminRoutes } from '../../admin-routing.module'; -import { filter } from 'rxjs/operators'; @Component({ - selector: 'app-admin-nav-bar', - templateUrl: './nav-bar.component.html', - styleUrls: ['./nav-bar.component.scss'], + selector: 'app-admin-header', + templateUrl: './admin-header.component.html', + styleUrls: ['./admin-header.component.scss'], }) -export class NavBarComponent implements OnInit { - public routes = AdminRoutes; +export class AdminHeaderComponent implements OnInit { private ghostLink: string; - public currentRoute: string; + public currentTab = 0; + public routes = AdminRoutes; - public buttons = [ + public tabs = [ { label: 'Revendication structure', route: this.routes.pendingStructures.link }, { label: 'Liste structures', route: this.routes.structuresList.link }, { label: 'Structures supprimées', route: this.routes.deletedStructures.link }, @@ -35,22 +34,19 @@ export class NavBarComponent implements OnInit { this.ghostLink = config.ghostAdminUrl; }); - this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => { - this.currentRoute = event.urlAfterRedirects || event.url; - }); - - this.currentRoute = this.router.url.substring(1); + // We navigateTo distinct routes, so we need to recover the currentTab after rerouting + this.currentTab = this.tabs.findIndex((tab) => tab.route === this.router.url.substring(1)); } - public openGhost(): void { - window.open(this.ghostLink); + public getTabsNames(): string[] { + return this.tabs.map((tab) => tab.label); } - public navigateTo(route: string): void { - this.router.navigateByUrl(route); + public navigateTo(tabIndex: number): void { + this.router.navigateByUrl(this.tabs[tabIndex].route); } - public isActive(route: string): string { - return this.currentRoute === route ? 'active' : ''; + public openGhost(): void { + window.open(this.ghostLink); } } diff --git a/src/app/admin/components/claim-structure/claim-structure.component.html b/src/app/admin/components/claim-structure/claim-structure.component.html index 0e63cd7fefb8a296ee154c196c6db0b6872d56c2..ca40b8adc7a6c9ab86e3391b80cb8451de4af150 100644 --- a/src/app/admin/components/claim-structure/claim-structure.component.html +++ b/src/app/admin/components/claim-structure/claim-structure.component.html @@ -1,5 +1,6 @@ -<app-admin-nav-bar /> <div class="adminLayout"> + <app-admin-header /> + <h2>Revendication structure</h2> <ag-grid-angular *ngIf="demandsAttachment?.length" diff --git a/src/app/admin/components/deleted-structures/deleted-structures.component.html b/src/app/admin/components/deleted-structures/deleted-structures.component.html index 875fd4dba1b3d5abfadf257a91498378db0384c5..c746ded7db4d1ad50a24c80ec3f86c2341262cd6 100644 --- a/src/app/admin/components/deleted-structures/deleted-structures.component.html +++ b/src/app/admin/components/deleted-structures/deleted-structures.component.html @@ -1,8 +1,8 @@ -<app-admin-nav-bar /> <div *ngIf="isLoading" class="loader" aria-busy="true"> <img class="loader-gif" src="/assets/gif/loader_circle.gif" alt /> </div> <div *ngIf="!isLoading" class="adminLayout"> + <app-admin-header /> <h2>Liste des structures supprimées</h2> <ag-grid-angular *ngIf="deletedStructures" diff --git a/src/app/admin/components/espace-coop-cnfs/espace-coop-cnfs.component.html b/src/app/admin/components/espace-coop-cnfs/espace-coop-cnfs.component.html index 7d6870a6942ae7505f8635d13a3504f895ff367e..0a41926ada594bf6e2aa4f8ee134e3970b648391 100644 --- a/src/app/admin/components/espace-coop-cnfs/espace-coop-cnfs.component.html +++ b/src/app/admin/components/espace-coop-cnfs/espace-coop-cnfs.component.html @@ -1,5 +1,5 @@ -<app-admin-nav-bar /> <div class="adminLayout"> + <app-admin-header /> <h2>CNFS présents dans Espace Coop sans compte Rés'in ({{ unregisteredCNFS.length }})</h2> <ag-grid-angular *ngIf="unregisteredCNFS.length" diff --git a/src/app/admin/components/manage-employers/manage-employers.component.html b/src/app/admin/components/manage-employers/manage-employers.component.html index f098ac42f84203722067b66e89a2be26e173ddfe..7bb145742b57644ad94b64bdfae95fac201877fb 100644 --- a/src/app/admin/components/manage-employers/manage-employers.component.html +++ b/src/app/admin/components/manage-employers/manage-employers.component.html @@ -1,14 +1,12 @@ -<app-admin-nav-bar /> <div class="adminLayout"> + <app-admin-header /> <h2> Gestion des employeurs <span *ngIf="unvalidatedEmployers && validatedEmployers"> ({{ unvalidatedEmployers.length + validatedEmployers.length }}) </span> </h2> -</div> -<div class="adminLayout"> <h3 *ngIf="unvalidatedEmployers" class="title">Employeurs non validés ({{ unvalidatedEmployers.length }})</h3> <ag-grid-angular *ngIf="unvalidatedEmployers" diff --git a/src/app/admin/components/manage-jobs/manage-jobs.component.html b/src/app/admin/components/manage-jobs/manage-jobs.component.html index 57256cc84f244bea5755afc6b2dca32bfd564df4..0e4702bf328ac1361357e8ffdb6c0e12602a1083 100644 --- a/src/app/admin/components/manage-jobs/manage-jobs.component.html +++ b/src/app/admin/components/manage-jobs/manage-jobs.component.html @@ -1,12 +1,10 @@ -<app-admin-nav-bar /> <div class="adminLayout"> + <app-admin-header /> <h2> Gestion des fonctions <span *ngIf="unvalidatedJobs && validatedJobs"> ({{ unvalidatedJobs.length + validatedJobs.length }}) </span> </h2> -</div> -<div class="adminLayout"> <h3 *ngIf="unvalidatedJobs" class="title">Fonctions non validées ({{ unvalidatedJobs.length }})</h3> <ag-grid-angular *ngIf="unvalidatedJobs" diff --git a/src/app/admin/components/manage-users/manage-users.component.html b/src/app/admin/components/manage-users/manage-users.component.html index 2dd43a6928cc8e3f6c05094a7cda542dde86f32b..297bd295e09b444c352c2e106e37325b58d06625 100644 --- a/src/app/admin/components/manage-users/manage-users.component.html +++ b/src/app/admin/components/manage-users/manage-users.component.html @@ -1,14 +1,12 @@ -<app-admin-nav-bar /> <div class="adminLayout"> + <app-admin-header /> <h2> Gestion des utilisateurs <span *ngIf="unVerifiedUsers && unAttachedUsers && attachedUsers"> ({{ unVerifiedUsers.length + unAttachedUsers.length + attachedUsers.length }}) </span> </h2> -</div> -<div class="adminLayout"> <h3 *ngIf="unVerifiedUsers" class="title">Utilisateurs non vérifiés ({{ unVerifiedUsers.length }})</h3> <ag-grid-angular *ngIf="validatedJobs && validatedEmployers" diff --git a/src/app/admin/components/nav-bar/nav-bar.component.html b/src/app/admin/components/nav-bar/nav-bar.component.html deleted file mode 100644 index 569fc8cbeded90785057150fa77ff9c00a86a444..0000000000000000000000000000000000000000 --- a/src/app/admin/components/nav-bar/nav-bar.component.html +++ /dev/null @@ -1,23 +0,0 @@ -<div class="header"> - <div class="title-and-ghost"> - <h1>Administration</h1> - <app-button - [variant]="'secondary'" - [label]="'Ghost'" - [type]="'button'" - (action)="openGhost()" - /> - </div> - <nav> - <ng-container *ngFor="let button of buttons"> - <div [class]="isActive(button.route)"> - <app-button - [variant]="'tertiary'" - [label]="button.label" - [type]="'button'" - (action)="navigateTo(button.route)" - /> - </div> - </ng-container> - </nav> -</div> diff --git a/src/app/admin/components/nav-bar/nav-bar.component.scss b/src/app/admin/components/nav-bar/nav-bar.component.scss deleted file mode 100644 index 57d5fe7d51930f883730890b23a961ab735fef01..0000000000000000000000000000000000000000 --- a/src/app/admin/components/nav-bar/nav-bar.component.scss +++ /dev/null @@ -1,40 +0,0 @@ -.header { - display: flex; - flex-direction: column; - align-items: center; - - .title-and-ghost { - margin-top: 2rem; - display: flex; - flex-direction: row; - justify-content: space-between; - width: 80%; - height: 2rem; - margin-bottom: 1.5rem; - } - - h1 { - justify-content: start; - font-weight: 400; - } - - nav { - margin-top: 0.5rem; - display: flex; - justify-content: space-between; - border-bottom: 1px solid #ccc; - margin-bottom: 1.5rem; - width: 80%; - overflow-x: auto; - } - - app-button { - height: 3.75rem; - position: relative; - } - - .active { - border-bottom: 3px solid black; - padding-bottom: 0.5rem; - } -} diff --git a/src/app/admin/components/structures-list/admin-structures-list.component.html b/src/app/admin/components/structures-list/admin-structures-list.component.html index 73d9a9e43a81c24902bfd38fad84cec94b3814f2..218b7051cf65eb68e5222ba182c6dcc7b925672c 100644 --- a/src/app/admin/components/structures-list/admin-structures-list.component.html +++ b/src/app/admin/components/structures-list/admin-structures-list.component.html @@ -1,37 +1,39 @@ -<app-admin-nav-bar /> +<div class="adminLayout"> + <app-admin-header /> -<div class="filters-containers"> - <div *ngFor="let filterOption of filterOptions"> - <div class="filter"> - <app-radio-option - class="filter-option" - [id]="filterOption.value" - [label]="filterOption.label" - [value]="filterOption.value" - [selected]="filter === filterOption.value" - (click)="setFilter(filterOption.value)" - /> + <div class="filters-containers"> + <div *ngFor="let filterOption of filterOptions"> + <div class="filter"> + <app-radio-option + class="filter-option" + [id]="filterOption.value" + [label]="filterOption.label" + [value]="filterOption.value" + [selected]="filter === filterOption.value" + (click)="setFilter(filterOption.value)" + /> + </div> </div> </div> -</div> -<div *ngIf="isLoading" class="loader" aria-busy="true"> - <img class="loader-gif" src="/assets/gif/loader_circle.gif" alt="" /> -</div> -<div *ngIf="!isLoading" class="adminLayout"> - <div *ngFor="let section of filteredSections" class="section"> - <h3>{{ section.title }} ({{ section.data?.length || 0 }})</h3> - <ag-grid-angular - *ngIf="section.data?.length" - class="ag-theme-alpine" - domLayout="autoHeight" - style="width: 100%" - [gridOptions]="getGridContext(section.filter)" - [rowData]="section.data" - [columnDefs]="columnDefs" - [rowHeight]="rowHeight" - [ngClass]="section.cssClass" - /> - <div *ngIf="!section.data?.length" class="no-structures">Aucun renseignement</div> + <div *ngIf="isLoading" class="loader" aria-busy="true"> + <img class="loader-gif" src="/assets/gif/loader_circle.gif" alt="" /> + </div> + <div *ngIf="!isLoading"> + <div *ngFor="let section of filteredSections" class="section"> + <h3>{{ section.title }} ({{ section.data?.length || 0 }})</h3> + <ag-grid-angular + *ngIf="section.data?.length" + class="ag-theme-alpine" + domLayout="autoHeight" + style="width: 100%" + [gridOptions]="getGridContext(section.filter)" + [rowData]="section.data" + [columnDefs]="columnDefs" + [rowHeight]="rowHeight" + [ngClass]="section.cssClass" + /> + <div *ngIf="!section.data?.length" class="no-structures">Aucun renseignement</div> + </div> </div> </div> diff --git a/src/app/admin/components/structures-list/admin-structures-list.component.scss b/src/app/admin/components/structures-list/admin-structures-list.component.scss index a2fb1edba9065b0c9f255472d5e08b4aa5eeef8b..f442b750fc0adc1ec7faa2e16e5189ca5ec75a9f 100644 --- a/src/app/admin/components/structures-list/admin-structures-list.component.scss +++ b/src/app/admin/components/structures-list/admin-structures-list.component.scss @@ -19,12 +19,8 @@ } .filters-containers { - width: 80%; display: flex; - margin-left: 10%; - flex-direction: row; - align-items: center; - margin-bottom: 1rem; + margin-bottom: 2rem; gap: 0.5rem; } diff --git a/src/app/post/components/post-header/post-header.component.html b/src/app/post/components/post-header/post-header.component.html index a24a9232a22fa9810c652269d61b2203cec91c10..d489ed9cc62a27e5e908e47a15ada04d1a25e860 100644 --- a/src/app/post/components/post-header/post-header.component.html +++ b/src/app/post/components/post-header/post-header.component.html @@ -8,23 +8,10 @@ </div> </div> - <nav> - <div role="tablist" class="navigation" aria-label="Filtrer les actualités"> - <div - *ngFor="let tag of tags.others" - class="tag" - role="tab" - tabindex="0" - aria-controls="posts" - [attr.aria-selected]="tag.slug === mainActiveTag.slug ? true : false" - [ngClass]="{ active: tag.slug === mainActiveTag.slug || tag.name === mainActiveTag.slug }" - (click)="activateTag(tag)" - (keydown.enter)="activateTag(tag)" - > - <span> - {{ tag.name }} - </span> - </div> - </div> - </nav> + <app-nav-bar + ariaLabel="Sélectionner une catégorie d'actualités" + [tabs]="getTabsNames()" + [currentTab]="currentTab" + (clickedTab)="activateTag($event)" + /> </div> diff --git a/src/app/post/components/post-header/post-header.component.scss b/src/app/post/components/post-header/post-header.component.scss index c934ed5f4512157d47a0699e66694f1f7237a6ad..a516d2d25803acea2f3a5070259597597aed213c 100644 --- a/src/app/post/components/post-header/post-header.component.scss +++ b/src/app/post/components/post-header/post-header.component.scss @@ -13,41 +13,3 @@ color: $grey-3; } } - -nav { - display: flex; - justify-content: space-between; - align-items: center; - - border-bottom: 1px solid $grey-4; - - .navigation { - display: flex; - overflow-x: auto; - white-space: nowrap; - - .tag { - cursor: pointer; - user-select: none; - @include font-regular-16; - box-sizing: border-box; - padding: 0 16px; - height: 60px; - display: flex; - align-items: center; - border-bottom: 3px solid transparent; - transition: all 0.3s ease-in-out; - outline-offset: -2px; // Fixes the focus display issue in Firefox where it was not visible due to "overflow-x" on .navigation - &.active { - @include font-bold-16; - border-color: $grey-1; - } - &:hover { - border-color: $grey-4; - } - &:focus { - outline-color: $red; - } - } - } -} diff --git a/src/app/post/components/post-header/post-header.component.ts b/src/app/post/components/post-header/post-header.component.ts index fd5c0a482da7101a0c0ce0143253ca8e09e33487..1e4e68ada19882abea6b01578f370eab46cf23a0 100644 --- a/src/app/post/components/post-header/post-header.component.ts +++ b/src/app/post/components/post-header/post-header.component.ts @@ -11,8 +11,8 @@ import { parseSlugToTag } from '../utils/NewsUtils'; }) export class PostHeaderComponent implements OnInit { public tags: TagWithMeta; - public mainActiveTag: Tag = new Tag({ slug: TagEnum.aLaUne }); public tagEnum = TagEnum; + public currentTab = 0; public checkedPublicTags: Tag[] = []; public checkedLocationTags: Tag[] = []; @@ -33,8 +33,9 @@ export class PostHeaderComponent implements OnInit { }); this.route.queryParams.subscribe((queryParams) => { + // We navigateTo distinct routes, so we need to recover the currentTab after rerouting if (queryParams.mainTag) { - this.mainActiveTag = new Tag({ slug: queryParams.mainTag }); + this.currentTab = this.tags.others.findIndex((tag) => tag.slug === queryParams.mainTag); } if (queryParams.publicTags) { this.checkedPublicTags = parseSlugToTag(queryParams.publicTags); @@ -45,6 +46,10 @@ export class PostHeaderComponent implements OnInit { }); } + public getTabsNames(): string[] { + return this.tags.others.map((tab) => tab.name); + } + private removeTagBySlug(slugToRemove: string): void { this.tags.others = this.tags.others.filter((tag) => tag.slug !== slugToRemove); } @@ -67,16 +72,11 @@ export class PostHeaderComponent implements OnInit { return nameTagA.localeCompare(nameTagB); } - public activateTag(tag: Tag): void { - this.mainActiveTag = tag; - this.setQueryParam(); - } - - private setQueryParam(): void { + public activateTag(tabIndex: number): void { this.router.navigate(['/actualites'], { relativeTo: this.route, queryParams: { - mainTag: this.getMainTag(), + mainTag: this.getMainTag(this.tags.others[tabIndex]), publicTags: this.checkedPublicTags.map((tag) => tag.slug), locationTags: this.checkedLocationTags.map((tag) => tag.slug), }, @@ -84,10 +84,10 @@ export class PostHeaderComponent implements OnInit { }); } - public getMainTag(): string { - if (this.mainActiveTag.slug === TagEnum.aLaUne) { + public getMainTag(tag: Tag): string { + if (tag.slug === TagEnum.aLaUne) { return ''; } - return this.mainActiveTag.slug === this.tagEnum.etudes ? this.mainActiveTag.name : this.mainActiveTag.slug; + return tag.slug === this.tagEnum.etudes ? tag.name : tag.slug; } } diff --git a/src/app/profile/edit/edit.component.html b/src/app/profile/edit/edit.component.html index 79195f232b3d124e049455abad27ae2eceba0439..bfffb8af5cde2ac848bb9766e545020b133405f6 100644 --- a/src/app/profile/edit/edit.component.html +++ b/src/app/profile/edit/edit.component.html @@ -24,51 +24,18 @@ /> </div> <!-- Navigation --> - <div class="navigation" role="menu"> - <div - class="tab" - tabindex="0" - role="menuitem" - [ngClass]="{ selected: currentTab === tabsEnum.details }" - (click)="navigateTo(tabsEnum.details)" - (keyup.enter)="navigateTo(tabsEnum.details)" - > - Coordonnées - </div> - <div - class="tab" - tabindex="0" - role="menuitem" - [ngClass]="{ selected: currentTab === tabsEnum.credentials }" - (click)="navigateTo(tabsEnum.credentials)" - (keyup.enter)="navigateTo(tabsEnum.credentials)" - > - Email et mot de passe - </div> - <div - class="tab" - tabindex="0" - role="menuitem" - [ngClass]="{ selected: currentTab === tabsEnum.employer }" - (click)="navigateTo(tabsEnum.employer)" - (keyup.enter)="navigateTo(tabsEnum.employer)" - > - Employeur et fonction - </div> - <div - class="tab" - tabindex="0" - role="menuitem" - [ngClass]="{ selected: currentTab === tabsEnum.description }" - (click)="navigateTo(tabsEnum.description)" - (keyup.enter)="navigateTo(tabsEnum.description)" - > - Description - </div> - </div> + <app-nav-bar + [tabs]="getTabsNames()" + [currentTab]="currentTab" + [shouldExpand]="true" + [ariaLabel]="'Sélectionner quelle section de votre profil éditer'" + [ariaControls]="getTabsIds()" + (clickedTab)="changeTab($event)" + /> + <!-- Content of tabs --> <div class="content"> - <div *ngIf="currentTab === tabsEnum.details" class="detailsTab"> + <div *ngIf="currentTab === tabsEnum.details" id="detailsTab" class="detailsTab"> <app-input [id]="'name'" [label]="'Prénom'" @@ -92,7 +59,7 @@ /> </div> - <div *ngIf="currentTab === tabsEnum.credentials" class="credentialsTab"> + <div *ngIf="currentTab === tabsEnum.credentials" id="credentialsTab" class="credentialsTab"> <div class="credentials"> <div class="inline"> <app-svg-icon [folder]="'tags'" [icon]="'mail'" [iconClass]="'icon-20'" /> @@ -123,7 +90,7 @@ </div> </div> - <div *ngIf="currentTab === tabsEnum.employer" class="employerJob"> + <div *ngIf="currentTab === tabsEnum.employer" id="employerJob" class="employerJob"> <app-select-or-create [autocompleteFunction]="profileService.getEmployers.bind(profileService)" [name]="'Employeur'" @@ -149,7 +116,7 @@ /> </div> - <div *ngIf="currentTab === tabsEnum.description" class="descriptionTab"> + <div *ngIf="currentTab === tabsEnum.description" id="descriptionTab" class="descriptionTab"> <app-textarea [id]="'description'" [label]="'Description'" diff --git a/src/app/profile/edit/edit.component.scss b/src/app/profile/edit/edit.component.scss index 32672d9d1fd0e83a728ed0cbd8efc775caf7fe64..62c4eac23c8cc77665957893987e269eb9ae62e0 100644 --- a/src/app/profile/edit/edit.component.scss +++ b/src/app/profile/edit/edit.component.scss @@ -53,37 +53,6 @@ } } - .navigation { - justify-content: flex-start; - overflow-x: auto; - white-space: nowrap; - border-bottom: 1px solid $grey-5; - - .tab { - cursor: pointer; - user-select: none; - @include font-regular-16; - box-sizing: border-box; - padding: 0 16px; - height: 60px; - display: flex; - justify-content: center; - align-items: center; - flex: 1; - border-bottom: 3px solid transparent; - transition: all 0.3s ease-in-out; - - &:hover { - border-color: $grey-4; - } - - &.selected { - font-weight: bold; - border-color: $grey-1; - } - } - } - .content { flex: 1; max-width: 600px; diff --git a/src/app/profile/edit/edit.component.ts b/src/app/profile/edit/edit.component.ts index 2fe8d98a24185e190e069b1803fef25231e70d3b..1c28692ecadb5fe6dba4372d38795c2e84dab639 100644 --- a/src/app/profile/edit/edit.component.ts +++ b/src/app/profile/edit/edit.component.ts @@ -16,7 +16,6 @@ enum tabsEnum { credentials, employer, description, - avatar, } enum showPasswordEnum { @@ -34,7 +33,7 @@ type passwordStatuses = 'error' | 'info' | 'success'; }) export class EditComponent implements OnInit { public tabsEnum = tabsEnum; - public currentTab: tabsEnum = tabsEnum.details; + public currentTab = tabsEnum.details; @Input() userProfile: User; public initialUserProfile: User; @@ -99,6 +98,18 @@ export class EditComponent implements OnInit { history.back(); } + public getTabsNames(): string[] { + return ['Coordonnées', 'Email et mot de passe', 'Employeur et fonction', 'Description']; + } + + public getTabsIds(): string[] { + return ['detailsTab', 'credentials', 'employerJob', 'descriptionTab']; + } + + public changeTab(tab: number): void { + this.currentTab = tab; + } + public phoneValid(): boolean { return Boolean(this.userProfile.phone.match(CustomRegExp.PHONE)); } @@ -180,10 +191,6 @@ export class EditComponent implements OnInit { return success ? 'success' : 'error'; } - public navigateTo(tab: tabsEnum): void { - this.currentTab = tab; - } - public cancel(): void { this.userProfile = { ...this.initialUserProfile }; } diff --git a/src/app/profile/personal-offer-edition/personal-offer-edition.component.html b/src/app/profile/personal-offer-edition/personal-offer-edition.component.html index 895ea56f9053b3207c76fbc3233a70c7872bfccb..0b65f9a8ca114b6ba5650568561ca0c149853586 100644 --- a/src/app/profile/personal-offer-edition/personal-offer-edition.component.html +++ b/src/app/profile/personal-offer-edition/personal-offer-edition.component.html @@ -26,29 +26,17 @@ /> </div> <!-- Navigation --> - <div class="navigation"> - <span - tabindex="0" - role="button" - [ngClass]="{ tab: true, selected: currentTab === tabsEnum.onlineProcedures }" - (click)="navigateTo(tabsEnum.onlineProcedures)" - (keyup.enter)="navigateTo(tabsEnum.onlineProcedures)" - > - Démarches en ligne - </span> - <span - tabindex="0" - role="button" - [ngClass]="{ tab: true, selected: currentTab === tabsEnum.digitalSkills }" - (click)="navigateTo(tabsEnum.digitalSkills)" - (keyup.enter)="navigateTo(tabsEnum.digitalSkills)" - > - Compétences numériques - </span> - </div> + <app-nav-bar + ariaLabel="Sélectionner le type d'offre personnelle" + [tabs]="getTabsNames()" + [currentTab]="currentTab" + [shouldExpand]="true" + [ariaControls]="getTabsIds()" + (clickedTab)="changeTab($event)" + /> <!-- Content of tabs --> <div class="content"> - <div *ngIf="currentTab === tabsEnum.onlineProcedures"> + <div *ngIf="currentTab === tabsEnum.onlineProcedures" id="onlineProcedures"> <app-accompaniment-picker *ngIf="personalOfferForm" [personalOfferForm]="personalOfferForm" @@ -56,7 +44,7 @@ /> </div> - <div *ngIf="currentTab === tabsEnum.digitalSkills"> + <div *ngIf="currentTab === tabsEnum.digitalSkills" id="digitalSkills"> <app-training-type-picker *ngIf="personalOfferForm" [baseSkills]="personalOfferForm.get('categories').get('baseSkills').value" diff --git a/src/app/profile/personal-offer-edition/personal-offer-edition.component.scss b/src/app/profile/personal-offer-edition/personal-offer-edition.component.scss index 581ea7142e803d9d5e9e2abe620e48bc0a5f6a4f..cde2cb5a386d93ea6598ddd8c110d0e85bde78c7 100644 --- a/src/app/profile/personal-offer-edition/personal-offer-edition.component.scss +++ b/src/app/profile/personal-offer-edition/personal-offer-edition.component.scss @@ -11,7 +11,6 @@ .edit-personal-offer { display: flex; flex-direction: column; - flex: 1; max-width: 980px; width: 100%; margin: 0 auto; @@ -59,35 +58,6 @@ } } - .navigation { - justify-content: flex-start; - overflow-x: auto; - white-space: nowrap; - border-bottom: 1px solid $grey-5; - - .tab { - cursor: pointer; - user-select: none; - @include font-regular-14; - box-sizing: border-box; - padding: 0 16px; - height: 60px; - display: flex; - justify-content: center; - align-items: center; - flex: 1; - border-bottom: 3px solid transparent; - transition: all 0.3s ease-in-out; - &:hover { - border-color: $grey-4; - } - &.selected { - font-weight: bold; - border-color: $grey-1; - } - } - } - .content { flex: 1; max-width: 600px; diff --git a/src/app/profile/personal-offer-edition/personal-offer-edition.component.ts b/src/app/profile/personal-offer-edition/personal-offer-edition.component.ts index 28461141e83a2bc40d2dd58ae3b6591815b6605c..815eafa41606833bb839e133f6ecf2a73db9a836 100644 --- a/src/app/profile/personal-offer-edition/personal-offer-edition.component.ts +++ b/src/app/profile/personal-offer-edition/personal-offer-edition.component.ts @@ -51,6 +51,18 @@ export class PersonalOfferEditionComponent implements OnInit { this.structureName = history.state.structureName; } + public getTabsNames(): string[] { + return ['Démarches en ligne', 'Compétences numériques']; + } + + public getTabsIds(): string[] { + return ['onlineProcedures', 'digitalSkills']; + } + + public changeTab(tab: number): void { + this.currentTab = tab; + } + private createPersonalOfferForm(personalOfferState): UntypedFormGroup { return new UntypedFormGroup({ categories: new UntypedFormGroup({ @@ -69,7 +81,9 @@ export class PersonalOfferEditionComponent implements OnInit { // We sort modules alphabetically, except for the 'autres' module which is always at the end this.onlineProcedures = categ; const othersModule = this.onlineProcedures.modules.find((module) => module.id === 'autres'); - const sortedModules = this.onlineProcedures.modules.filter((module) => module.id !== 'autres').sort((a, b) => a.name.localeCompare(b.name)); + const sortedModules = this.onlineProcedures.modules + .filter((module) => module.id !== 'autres') + .sort((a, b) => a.name.localeCompare(b.name)); if (othersModule) sortedModules.push(othersModule); this.onlineProcedures.modules = sortedModules; break; @@ -85,10 +99,6 @@ export class PersonalOfferEditionComponent implements OnInit { }); } - public navigateTo(tab: tabsEnum): void { - this.currentTab = tab; - } - public cancel(): void { this.personalOfferForm = this.createPersonalOfferForm(this.initialPersonalOffer); } diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index c30311533b8d74e8874e01ed52a77a556a983f0c..ce2234a5bb5d6259c206ad847f1f43850eb4b2e2 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -20,6 +20,7 @@ import { InputComponent } from './input/input.component'; import { LogoCardComponent } from './logo-card/logo-card.component'; import { MemberCardComponent } from './member-card/member-card.component'; import { ModalComponent } from './modal/modal.component'; +import { NavBarComponent } from './nav-bar/nav-bar.component'; import { PrintHeaderComponent } from './print-header/print-header.component'; import { PrintStructuresGridComponent } from './print-structures-grid/print-structures-grid.component'; import { RadioOptionComponent } from './radio-option/radio-option.component'; @@ -51,6 +52,7 @@ export { LogoCardComponent, MemberCardComponent, ModalComponent, + NavBarComponent, PrintHeaderComponent, PrintStructuresGridComponent, ProgressBarComponent, @@ -87,6 +89,7 @@ export const SharedComponents = [ MemberCardComponent, LogoCardComponent, ModalComponent, + NavBarComponent, ProgressBarComponent, PrintHeaderComponent, PrintStructuresGridComponent, diff --git a/src/app/shared/components/nav-bar/nav-bar.component.html b/src/app/shared/components/nav-bar/nav-bar.component.html new file mode 100644 index 0000000000000000000000000000000000000000..0e708821dccc86f69d1700ad77945a92df83ff33 --- /dev/null +++ b/src/app/shared/components/nav-bar/nav-bar.component.html @@ -0,0 +1,17 @@ +<nav> + <div role="tablist" class="navigation" [attr.aria-label]="ariaLabel" [ngClass]="{ expand: shouldExpand }"> + <div + *ngFor="let tab of tabs; let i = index" + class="tab" + role="tab" + tabindex="0" + [attr.aria-controls]="getControl(i)" + [attr.aria-selected]="isActive(i)" + [ngClass]="{ active: isActive(i) }" + (click)="onClickTab(i)" + (keydown.enter)="onClickTab(i)" + > + {{ tab }} + </div> + </div> +</nav> diff --git a/src/app/shared/components/nav-bar/nav-bar.component.scss b/src/app/shared/components/nav-bar/nav-bar.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..1893a0b09b6732cf53d3cefd324279ed6177f2ac --- /dev/null +++ b/src/app/shared/components/nav-bar/nav-bar.component.scss @@ -0,0 +1,42 @@ +@import 'color'; +@import 'typography'; + +nav { + display: flex; + border-bottom: 1px solid $grey-4; + + .navigation { + display: flex; + overflow-x: auto; + white-space: nowrap; + &.expand { + width: 100%; + } + + .tab { + cursor: pointer; + user-select: none; + @include font-regular-16; + box-sizing: border-box; + padding: 0 16px; + height: 60px; + display: flex; + align-items: center; + justify-content: center; + flex: 1; + border-bottom: 3px solid transparent; + transition: all 0.3s ease-in-out; + outline-offset: -2px; // Fixes the focus display issue in Firefox where it was not visible due to "overflow-x" on .navigation + &.active { + @include font-bold-16; + border-color: $grey-1; + } + &:hover { + border-color: $grey-4; + } + &:focus { + outline-color: $red; + } + } + } +} diff --git a/src/app/shared/components/nav-bar/nav-bar.component.ts b/src/app/shared/components/nav-bar/nav-bar.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..33f943ee56f4d4fc3ef399981b6e7c38b4916973 --- /dev/null +++ b/src/app/shared/components/nav-bar/nav-bar.component.ts @@ -0,0 +1,28 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-nav-bar', + templateUrl: './nav-bar.component.html', + styleUrls: ['./nav-bar.component.scss'], +}) +export class NavBarComponent { + @Input({ required: true }) tabs: string[]; + @Input({ required: true }) ariaLabel: string; + @Input() shouldExpand = false; + @Input() currentTab = 0; + @Input() ariaControls = []; + @Output() clickedTab = new EventEmitter<number>(); + + public onClickTab(tabIndex: number): void { + this.currentTab = tabIndex; + this.clickedTab.emit(tabIndex); + } + + public isActive(idx: number): boolean { + return this.currentTab === idx; + } + + public getControl(index: number): string | null { + return this.ariaControls[index] ?? null; + } +} diff --git a/src/app/shared/components/nav-bar/nav-bar.stories.ts b/src/app/shared/components/nav-bar/nav-bar.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..42768e54461a25d93a630cd7860807ff8f20823e --- /dev/null +++ b/src/app/shared/components/nav-bar/nav-bar.stories.ts @@ -0,0 +1,34 @@ +import { CommonModule } from '@angular/common'; +import type { Meta, StoryObj } from '@storybook/angular'; +import { moduleMetadata } from '@storybook/angular'; +import { NavBarComponent } from './nav-bar.component'; + +// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction +const meta: Meta<NavBarComponent> = { + title: 'Components/NavBar', + component: NavBarComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + declarations: [], + imports: [CommonModule], + }), + ], + argTypes: {}, +}; + +export default meta; +type Story = StoryObj<NavBarComponent>; + +export const NavBar: Story = { + args: { + tabs: ['tab 1', 'tab 2'], + }, +}; + +export const ExpandedNavBar: Story = { + args: { + tabs: ['tab 1', 'tab 2'], + shouldExpand: true, + }, +};