diff --git a/package-lock.json b/package-lock.json index 4a35703c8ab5a6b9d9fa74ddc35c87f259f9d9dd..9215c5142d2069418ac3a722962e3ab603d0db3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@ag-grid-community/csv-export": "^29.0.0", "@angular/animations": "^17.3.8", + "@angular/cdk": "^17.3.6", "@angular/common": "^17.3.8", "@angular/compiler": "^17.3.8", "@angular/core": "^17.3.8", @@ -921,6 +922,22 @@ "@angular/core": "17.3.8" } }, + "node_modules/@angular/cdk": { + "version": "17.3.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.6.tgz", + "integrity": "sha512-7eKrC61/6pmMAxllU/vYKadZRF7x7GxUYpA5G70fNaQsIUUiZvxx/SJN9AuZEoPGAtF6atKlJD8QVmFoDzv/Lw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "17.3.7", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.7.tgz", @@ -12390,7 +12407,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12" }, @@ -19861,7 +19878,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, + "devOptional": true, "dependencies": { "entities": "^4.4.0" }, diff --git a/package.json b/package.json index 5a1005e13f35bd0c98a18a58b8fe89b334a505c6..2a412e9dbd24775a41c404def77ab26c320d2789 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@ag-grid-community/csv-export": "^29.0.0", "@angular/animations": "^17.3.8", + "@angular/cdk": "^17.3.6", "@angular/common": "^17.3.8", "@angular/compiler": "^17.3.8", "@angular/core": "^17.3.8", @@ -81,4 +82,4 @@ "storybook": "^8.0.4", "typescript": "~5.2.0" } -} \ No newline at end of file +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5f8226d9ea3e212d8488f3a2d0388728a9742533..f4b53ac98e1eb53559f51bc804b696d11508baa0 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,3 +1,4 @@ +import { A11yModule } from '@angular/cdk/a11y'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { LOCALE_ID, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @@ -70,6 +71,7 @@ import { StructureJoinComponent } from './structure/structure-join/structure-joi imports: [ BrowserModule, HttpClientModule, + A11yModule, AppRoutingModule, SharedModule, MapModule, diff --git a/src/app/carto/carto.module.ts b/src/app/carto/carto.module.ts index 964b0511439c86bfe50623c1cf3627cf42d03a43..50bf8b2939c0b6c450511207dbed174e3de6110d 100644 --- a/src/app/carto/carto.module.ts +++ b/src/app/carto/carto.module.ts @@ -1,3 +1,4 @@ +import { A11yModule } from '@angular/cdk/a11y'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { MapModule } from '../map/map.module'; @@ -8,7 +9,7 @@ import { StructureListSearchComponent } from '../structure-list/components/struc import { StructureListComponent } from '../structure-list/structure-list.component'; import { CartoComponent } from './carto.component'; @NgModule({ - imports: [CommonModule, SharedModule, MapModule], + imports: [CommonModule, SharedModule, MapModule, A11yModule], declarations: [ CartoComponent, CardComponent, diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 5d07e0752eeaeab960a19ef7961b14d94cd3a041..7d16fca349adef9959d5e1ba49b7f9279123ae91 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -1,15 +1,12 @@ <header class="hide-on-print"> <div class="containerIconMenu"> <app-svg-icon - class="clickable" - tabindex="0" - role="button" aria-label="Ouvrir le menu" + [asButton]="true" [folder]="'ico'" [icon]="'menu'" [iconClass]="'icon-32'" - (click)="openMenu()" - (keyup.enter)="openMenu()" + (action)="openMenu()" /> </div> <div @@ -31,15 +28,12 @@ </div> <div class="containerIconMenu profile"> <app-svg-icon - class="clickable" - tabindex="0" - role="button" + [asButton]="true" [attr.aria-label]="isLoggedIn ? 'Ouvrir le menu Mon compte' : 'Se connecter'" [folder]="'ico'" [icon]="'profile'" [iconClass]="'icon-32'" - (click)="isLoggedIn ? openProfileMenu() : goToLoginPage()" - (keyup.enter)="isLoggedIn ? openProfileMenu() : goToLoginPage()" + (action)="isLoggedIn ? openProfileMenu() : goToLoginPage()" /> </div> <div class="rightHeader"> @@ -210,7 +204,7 @@ ></div> </div> -<div *ngIf="showProfileMenu && isLoggedIn" class="profileModal" role="menu" [@toggle]> +<div *ngIf="showProfileMenu && isLoggedIn" class="profileModal" role="menu" cdkTrapFocus [@toggle]> <div class="overlay" tabindex="0" @@ -228,7 +222,13 @@ <span class="name">{{ displayFullName }}</span> </div> <div class="profileMenuButtons"> - <app-button [variant]="'primaryBlack'" [label]="'Voir mon compte'" [size]="'small'" (action)="goToProfile()" /> + <app-button + cdkFocusInitial + [variant]="'primaryBlack'" + [label]="'Voir mon compte'" + [size]="'small'" + (action)="goToProfile()" + /> <app-button [variant]="'secondary'" [label]="'Se déconnecter'" [size]="'small'" (action)="logout()" /> </div> </div> diff --git a/src/app/shared/components/collapse/collapse-header/collapse-header.component.scss b/src/app/shared/components/collapse/collapse-header/collapse-header.component.scss index e67cf260852847bbfc98537dec163244c8955f9b..c3ead4b41b8965f42cf3b85de37b182e1d594a37 100644 --- a/src/app/shared/components/collapse/collapse-header/collapse-header.component.scss +++ b/src/app/shared/components/collapse/collapse-header/collapse-header.component.scss @@ -1,9 +1,14 @@ @import 'color'; .collapse-header { + cursor: pointer; + width: 100%; + border: 0px solid transparent; display: flex; justify-content: space-between; - cursor: pointer; + background-color: $white; + padding: 0; + text-align: left; // SIZES &.small { diff --git a/src/app/shared/components/collapse/collapse-header/collapse-header.component.ts b/src/app/shared/components/collapse/collapse-header/collapse-header.component.ts index 9683c91c00c384c7b30fc43dc74015538d4cb4e5..591b5f17ee9a6d3b9e9c9deeb8fca64650e6661d 100644 --- a/src/app/shared/components/collapse/collapse-header/collapse-header.component.ts +++ b/src/app/shared/components/collapse/collapse-header/collapse-header.component.ts @@ -2,18 +2,16 @@ import { Component, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-collapse-header', - template: `<div + template: `<button + type="button" class="collapse-header" - role="button" aria-haspopup="true" - tabindex="0" [ngClass]="size" (click)="toggle.emit()" - (keyup.enter)="toggle.emit()" > <ng-content /> <app-svg-icon [ngClass]="expanded && 'expanded'" [folder]="'ico'" [icon]="'chevronDown'" [iconClass]="'icon-32'" /> - </div>`, + </button>`, styleUrls: ['collapse-header.component.scss'], }) export class CollapseHeaderComponent { diff --git a/src/app/shared/components/modal/modal.component.html b/src/app/shared/components/modal/modal.component.html index 6c3d913fe4934a43b885d84f6e6ca1982232ca48..4dc00f956b0f72627852bfef613878cb83edf596 100644 --- a/src/app/shared/components/modal/modal.component.html +++ b/src/app/shared/components/modal/modal.component.html @@ -1,16 +1,14 @@ -<div *ngIf="opened" class="modalBackground"> +<div *ngIf="opened" class="modalBackground" cdkTrapFocus [cdkTrapFocusAutoCapture]="true"> <div class="modal"> <div class="headerModal"> <h3>{{ title }}</h3> <app-svg-icon - tabindex="0" - role="button" aria-label="Fermer la fenêtre" + [asButton]="true" [folder]="'ico'" [icon]="'cross'" - [iconClass]="'icon-24 hover'" - (keyup.enter)="closeModal(false)" - (click)="closeModal(false)" + [iconClass]="'icon-24'" + (action)="closeModal(false)" /> </div> <p><ng-content /></p> diff --git a/src/app/shared/components/svg-icon/svg-icon.component.html b/src/app/shared/components/svg-icon/svg-icon.component.html index 11bdbbd4c736dfe6708823da386bfed0afb471b8..367937390205a97e7da2f35bca7eac89a7a8ec37 100644 --- a/src/app/shared/components/svg-icon/svg-icon.component.html +++ b/src/app/shared/components/svg-icon/svg-icon.component.html @@ -1,10 +1,16 @@ -<svg - aria-hidden="true" - class="icon" - role="presentation" - [ngClass]="iconClass" - [attr.fill]="iconColor" - [attr.stroke]="iconColor" -> - <use [attr.xlink:href]="'assets/' + folder + '/sprite.svg#' + icon" /> -</svg> +<button *ngIf="asButton; else svgIcon" type="button" class="asButton" (click)="action.emit()"> + <ng-container *ngTemplateOutlet="svgIcon" /> +</button> + +<ng-template #svgIcon> + <svg + aria-hidden="true" + class="icon" + role="presentation" + [ngClass]="iconClass" + [attr.fill]="iconColor" + [attr.stroke]="iconColor" + > + <use [attr.xlink:href]="'assets/' + folder + '/sprite.svg#' + icon" /> + </svg> +</ng-template> diff --git a/src/app/shared/components/svg-icon/svg-icon.component.scss b/src/app/shared/components/svg-icon/svg-icon.component.scss index fa06b7b1583c7447ffd626f3ce964d9cd7491945..59a9c64d125a36a2fabe0181bdb7ca0e4c766a56 100644 --- a/src/app/shared/components/svg-icon/svg-icon.component.scss +++ b/src/app/shared/components/svg-icon/svg-icon.component.scss @@ -55,15 +55,23 @@ $sizes: ( height: 26px; width: 20px; } - &.hover { - cursor: pointer; - &:hover { - opacity: 0.8; - } - } } .acces-icon { width: 3rem; height: 1rem; } + +.asButton { + cursor: pointer; + border: 0px solid transparent; + display: flex; + align-items: center; + justify-content: center; + background-color: $white; + padding: 0; + + &:hover { + opacity: 0.8; + } +} diff --git a/src/app/shared/components/svg-icon/svg-icon.component.ts b/src/app/shared/components/svg-icon/svg-icon.component.ts index 59bce347689128db6a2fcafa018ff36bffe43f19..ea27199f52fb448bf6492c329bfa452f859fbd7d 100644 --- a/src/app/shared/components/svg-icon/svg-icon.component.ts +++ b/src/app/shared/components/svg-icon/svg-icon.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { SpriteFolderType } from './SpriteFolder.type'; @Component({ @@ -15,4 +15,8 @@ export class SvgIconComponent { @Input() iconClass: string; @Input() iconColor = 'none'; + + /** If should be interactive */ + @Input() asButton = false; + @Output() action = new EventEmitter<void>(); } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 7dd808d672279f148e7198c1658af276ee484de3..792bd3e1ed9dbb5171e8bff36615a1d92b468298 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,3 +1,4 @@ +import { A11yModule } from '@angular/cdk/a11y'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -13,8 +14,9 @@ import { TextareaComponent } from './components/textarea/textarea.component'; import { YesNoComponent } from './components/yes-no/yes-no.component'; import { SharedDirectives } from './directives'; import { SharedPipes } from './pipes'; + @NgModule({ - imports: [CommonModule, FormsModule, RouterModule, ReactiveFormsModule], + imports: [CommonModule, FormsModule, RouterModule, ReactiveFormsModule, A11yModule], declarations: [ ...SharedPipes, ...SharedComponents, diff --git a/src/app/structure-list/components/more-filters/more-filters.component.html b/src/app/structure-list/components/more-filters/more-filters.component.html index e62edd8c8ed231cbb2a5860cd3144c226719bf17..1b55f7e458e4deacd4a1e1fa9033281d2ff3a546 100644 --- a/src/app/structure-list/components/more-filters/more-filters.component.html +++ b/src/app/structure-list/components/more-filters/more-filters.component.html @@ -1,18 +1,16 @@ -<div *ngIf="modalType" [ngClass]="['filterModal', getModalType()]"> +<div *ngIf="modalType" cdkTrapFocus [cdkTrapFocusAutoCapture]="true" [ngClass]="['filterModal', getModalType()]"> <div class="filterModalContainer" (appClickOutside)="getModalType() === 'moreFilters' ? closeModal() : ''"> <!-- Header for "Other filters" modal --> <div *ngIf="getModalType() === 'moreFilters'" class="moreFiltersHeader"> <h3>Plus de filtres</h3> <app-svg-icon - tabindex="0" - role="button" aria-label="Fermer la fenêtre" - [iconClass]="'icon-24 hover'" + [asButton]="true" + [iconClass]="'icon-24'" [iconColor]="'grey-1'" [icon]="'cross'" [folder]="'ico'" - (click)="closeModal()" - (keyup.enter)="closeModal()" + (action)="closeModal()" /> </div> <!-- Filter with single category --> diff --git a/src/app/structure-list/components/structure-details/structure-details.component.html b/src/app/structure-list/components/structure-details/structure-details.component.html index 918053f95d84f331284faace7e6bb1041c41e50e..e32a493c3dadfc7f5dd770809e22ae2a5b5b4cfc 100644 --- a/src/app/structure-list/components/structure-details/structure-details.component.html +++ b/src/app/structure-list/components/structure-details/structure-details.component.html @@ -1,4 +1,10 @@ -<div *ngIf="structure" class="structure-details-container" [ngClass]="{ fullScreen: fullScreen }" [@slideInOut]> +<div + *ngIf="structure" + class="structure-details-container" + cdkTrapFocus + [ngClass]="{ fullScreen: fullScreen }" + [@slideInOut] +> <div *ngIf="isLoading || structure['categories'] === undefined || structure['categoriesDisplay'] === undefined" class="loader structure-details-content" @@ -22,81 +28,53 @@ /> </div> <app-svg-icon - role="button" - tabindex="0" + #closeButton + cdkFocusInitial aria-label="Fermer" + [asButton]="true" [folder]="'ico'" [icon]="'cross'" [iconColor]="'grey-1'" - (click)="close()" - (keyup.enter)="close()" + [iconClass]="'icon-32'" + (action)="close()" /> </section> <section *ngIf="showButtons" class="actions hide-on-print"> - <div - *ngIf="structure.hasUserWithAppointmentDN" - class="clickableDiv" - role="button" - tabindex="0" - (click)="goToOrientation()" - (keyup.enter)="goToOrientation()" - > + <button *ngIf="structure.hasUserWithAppointmentDN" type="button" class="clickableDiv" (click)="goToOrientation()"> <app-svg-icon class="icon" [folder]="'ico'" [icon]="'rdvDetail'" [iconClass]="'icon-32'" /> <div class="iconTitle">Demander un RDV</div> - </div> + </button> <!-- Voir le site --> - <div - *ngIf="structure.website" - class="clickableDiv" - role="button" - tabindex="0" - (click)="goToWebsite()" - (keyup.enter)="goToWebsite()" - > + <button *ngIf="structure.website" type="button" class="clickableDiv" (click)="goToWebsite()"> <app-svg-icon class="icon" [folder]="'ico'" [icon]="'web'" [iconClass]="'icon-32'" /> <div class="iconTitle">Site web</div> - </div> + </button> <!-- Imprimer --> - <div role="button" class="printButton clickableDiv" tabindex="0" (click)="print()" (keyup.enter)="print()"> + <button type="button" class="printButton clickableDiv" (click)="print()"> <app-svg-icon class="icon" [folder]="'ico'" [icon]="'printStructure'" [iconClass]="'icon-32'" /> <div class="iconTitle">Imprimer</div> - </div> + </button> <!-- Signaler une erreur --> - <div - role="button" - class="clickableDiv" - tabindex="0" - (click)="displayModalError()" - (keyup.enter)="displayModalError()" - > + <button type="button" class="clickableDiv" (click)="displayModalError()"> <app-svg-icon class="icon" [folder]="'ico'" [icon]="'watch'" [iconClass]="'icon-32'" /> <div class="iconTitle">Signaler une erreur</div> - </div> + </button> <!-- Je travaille ici --> - <div - *ngIf="!isInStructure" - class="clickableDiv" - role="button" - tabindex="0" - (click)="handleJoin()" - (keyup.enter)="handleJoin()" - > + <button *ngIf="!isInStructure" type="button" class="clickableDiv" (click)="handleJoin()"> <app-svg-icon class="icon" [folder]="'ico'" [icon]="'workHere'" [iconClass]="'icon-32'" /> <div class="iconTitle">Je travaille ici</div> - </div> + </button> <!-- Modifier la structure --> - <div + <button *ngIf="isInStructure || profileService.isAdmin()" + type="button" class="clickableDiv" - role="button" - tabindex="0" (click)="handleModify()" - (keyup.enter)="handleModify()" > <app-svg-icon class="icon" [folder]="'ico'" [icon]="'modifyStructure'" [iconClass]="'icon-32'" /> <div class="iconTitle">Modifier la structure</div> - </div> + </button> </section> <!-- Admin menu --> diff --git a/src/app/structure-list/components/structure-details/structure-details.component.scss b/src/app/structure-list/components/structure-details/structure-details.component.scss index cfce32bef4be2202c0e7edb69f5bd0b18b121e72..d30d92d2305ed0d7a32a70b44040add351c43a38 100644 --- a/src/app/structure-list/components/structure-details/structure-details.component.scss +++ b/src/app/structure-list/components/structure-details/structure-details.component.scss @@ -92,13 +92,17 @@ section.actions { } .clickableDiv { - text-align: center; + cursor: pointer; max-width: 65px; + border: 0px solid transparent; display: flex; flex-direction: column; + align-items: center; justify-content: center; gap: 10px; - cursor: pointer; + background-color: $white; + padding: 0; + &:hover { text-decoration: underline; } diff --git a/src/app/structure-list/components/structure-details/structure-details.component.ts b/src/app/structure-list/components/structure-details/structure-details.component.ts index e14acd3f5802e6220e7913486f58b1b98f4c245a..d28eabfb8fe0e8121df892d6c95857b41cc7a761 100644 --- a/src/app/structure-list/components/structure-details/structure-details.component.ts +++ b/src/app/structure-list/components/structure-details/structure-details.component.ts @@ -1,5 +1,5 @@ import { animate, style, transition, trigger } from '@angular/animations'; -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import * as _ from 'lodash'; import { Owner } from '../../../models/owner.model'; @@ -58,6 +58,8 @@ export class StructureDetailsComponent implements OnInit { public FreeWorkshop = FreeWorkshop; public isInStructure = false; + @ViewChild('closeButton', { read: ElementRef }) closeButtonElement: ElementRef; + constructor( private searchService: SearchService, private structureService: StructureService, @@ -106,6 +108,9 @@ export class StructureDetailsComponent implements OnInit { this.categories = categories; this.setServiceCategories(); this.isLoading = false; + setTimeout(() => { + this.closeButtonElement.nativeElement.children[0].focus(); + }); }); }