Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client
1 result
Show changes
Commits on Source (7)
Showing
with 166 additions and 45 deletions
<div class="visually-hidden">
<p>Utilisez <strong>h</strong> (header) pour revenir au logo Res'in à tout moment.</p>
</div>
<app-header />
<main class="app-container">
<div class="app-body" id="app-body" (scroll)="onScroll($event)">
......
import { Component, OnInit } from '@angular/core';
import { Component, HostListener, OnInit, ViewChild } from '@angular/core';
import {
GuardsCheckStart,
NavigationCancel,
......@@ -8,6 +8,7 @@ import {
Router,
} from '@angular/router';
import { MatomoInitializerService } from 'ngx-matomo-client';
import { HeaderComponent } from './header/header.component';
import { ProfileService } from './profile/services/profile.service';
import { AuthService } from './services/auth.service';
import { ConfigService } from './services/config.service';
......@@ -24,6 +25,36 @@ import { WindowScrollService } from './shared/service/windowScroll.service';
export class AppComponent implements OnInit {
public loading = true;
@ViewChild(HeaderComponent) headerComponent!: HeaderComponent;
// Listener, keyboard shortcuts
@HostListener('window:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent): void {
const target = event.target as HTMLElement;
const tagName = target.tagName.toLowerCase();
// Check if the focus is within an input, textarea
if (tagName === 'input') {
const inputElement = target as HTMLInputElement;
const inputType = inputElement.type.toLowerCase();
// Ignore only text-based inputs, not others like radio, checkbox, etc.
if (!['radio', 'checkbox', 'button', 'submit', 'reset'].includes(inputType)) {
return;
}
} else if (tagName === 'textarea' || target.isContentEditable) {
return;
}
switch (event.key) {
case 'h': // 'h' to go to the header (resin logo)
this.headerComponent.focusLogo();
event.preventDefault();
break;
default:
break;
}
}
constructor(
public printService: PrintService,
private authService: AuthService,
......
......@@ -53,7 +53,6 @@
label {
@include font-bold-14;
color: $red;
min-width: 26px;
}
}
.topSpacing {
......
<div class="footerForm">
<ng-container *ngIf="!failedOrientation">
<app-button
*ngIf="currentStep !== null && !(isPrevHidden || isLastStep)"
*ngIf="showPrevButton()"
#prevButton
[variant]="'secondary'"
[label]="'Précédent'"
[iconName]="'arrowBack'"
(action)="prevPage()"
/>
<app-button
*ngIf="!hideNavButtons"
*ngIf="showNextButton()"
#nextButton
[variant]="'primary'"
[label]="isLastStep ? 'Imprimer' : 'Suivant'"
[iconName]="isLastStep ? 'printer' : 'arrowForward'"
......
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ButtonComponent } from '../../../../shared/components';
import { NeedsType, OnlineDemarche } from '../../enums/orientation.enums';
import { MediationStepType, MediationType } from '../../types/orientation.types';
......@@ -22,11 +23,21 @@ export class NavigationComponent {
@Output() goPrev = new EventEmitter<any>();
@Output() goReset = new EventEmitter<any>();
@ViewChild('prevButton', { read: ButtonComponent }) prevButton: ButtonComponent;
@ViewChild('nextButton', { read: ButtonComponent }) nextButton: ButtonComponent;
public NeedsTypeEnum = NeedsType;
constructor(
private router: Router,
private route: ActivatedRoute,
) {}
public showPrevButton(): boolean {
return this.currentStep !== null && !(this.isPrevHidden || this.isLastStep);
}
public showNextButton(): boolean {
return !this.hideNavButtons;
}
public nextPage(isPrint?: boolean): void {
this.goNext.emit(isPrint);
}
......@@ -41,4 +52,11 @@ export class NavigationComponent {
public resetOrientation(): void {
this.goReset.emit();
}
public focusFirstButton(): void {
if (this.showPrevButton()) {
this.prevButton.focus();
} else if (this.showNextButton()) {
this.nextButton.focus();
}
}
}
......@@ -7,7 +7,7 @@
[id]="lang.key"
[label]="lang.title"
[selected]="selected === lang.key"
(selectedEvent)="handleSelect(lang.key)"
(click)="handleSelect(lang.key)"
/>
</div>
</div>
......@@ -7,7 +7,7 @@
[label]="option.title"
[description]="option.hint"
[selected]="selected === option.key"
(selectedEvent)="handleSelect(option.key)"
(click)="handleSelect(option.key)"
/>
</div>
</div>
<div class="orientation">
<h1 class="visually-hidden">Orientation</h1>
<div class="visually-hidden">
<p>Utilisez <strong>f</strong> (footer) pour aller directement aux boutons de validation.</p>
</div>
<app-progress-bar [currentPage]="currentStep" [nbSteps]="nbSteps" [formType]="formType.orientation" />
<div class="container" [ngClass]="{ 'no-max-width': fullScreen }">
<app-needs-selection
......
import { AfterContentChecked, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { AfterContentChecked, ChangeDetectorRef, Component, HostListener, OnInit, ViewChild } from '@angular/core';
import { AbstractControl, FormGroup, UntypedFormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { lastValueFrom } from 'rxjs';
......@@ -28,6 +28,7 @@ import {
OnlineDemarchesCommonSteps,
StructuresListSteps,
} from './enums/orientation.enums';
import { NavigationComponent } from './global-components/navigation/navigation.component';
import { IAppointment } from './interfaces/appointment.interface';
import { FiltersForm } from './interfaces/filtersForm.interface';
import { IOnlineMediation } from './interfaces/onlineMediation.interface';
......@@ -116,6 +117,36 @@ export class OrientationFormViewComponent implements OnInit, AfterContentChecked
public showConfirmationModal = false;
private resolve: CanExitResolver;
@ViewChild(NavigationComponent) navComponent!: NavigationComponent;
// Listener, keyboard shortcuts
@HostListener('window:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent): void {
const target = event.target as HTMLElement;
const tagName = target.tagName.toLowerCase();
// Check if the focus is within an input, textarea
if (tagName === 'input') {
const inputElement = target as HTMLInputElement;
const inputType = inputElement.type.toLowerCase();
// Ignore only text-based inputs, not others like radio, checkbox, etc.
if (!['radio', 'checkbox', 'button', 'submit', 'reset'].includes(inputType)) {
return;
}
} else if (tagName === 'textarea' || target.isContentEditable) {
return;
}
switch (event.key) {
case 'f': // 'f' to go to the navigation footer
this.navComponent.focusFirstButton();
event.preventDefault();
break;
default:
break;
}
}
constructor(
public orientationService: OrientationService,
private notificationService: NotificationService,
......@@ -124,12 +155,13 @@ export class OrientationFormViewComponent implements OnInit, AfterContentChecked
private profileService: ProfileService,
private searchService: SearchService,
private structureService: StructureService,
private cdref: ChangeDetectorRef,
private cdRef: ChangeDetectorRef,
private indicatorService: IndicatorService,
private router: Router,
) {
this.setCategories();
}
async ngOnInit(): Promise<void> {
this.orientationService.rdvUser = null;
if (history.state.rdvUser) {
......@@ -167,7 +199,7 @@ export class OrientationFormViewComponent implements OnInit, AfterContentChecked
}
ngAfterContentChecked(): void {
this.cdref.detectChanges();
this.cdRef.detectChanges();
}
public validatePage(event: boolean): void {
......
......@@ -10,6 +10,7 @@
/>
</div>
<div
#clickableLogo
class="logo clickable"
aria-label="Retour accueil"
role="button"
......
import { animate, animateChild, query, style, transition, trigger } from '@angular/animations';
import { Component } from '@angular/core';
import { Component, ElementRef, ViewChild } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { Structure } from '../models/structure.model';
import { ProfileService } from '../profile/services/profile.service';
......@@ -39,6 +39,8 @@ export class HeaderComponent {
private displayDataShare = false;
private loadingDataShare = false;
@ViewChild('clickableLogo') clickableLogoDiv: ElementRef;
constructor(
private authService: AuthService,
private profileService: ProfileService,
......@@ -85,6 +87,12 @@ export class HeaderComponent {
}
}
public focusLogo(): void {
if (this.clickableLogoDiv) {
this.clickableLogoDiv.nativeElement.focus();
}
}
public get isLoggedIn(): boolean {
return this.authService.isLoggedIn();
}
......
......@@ -132,16 +132,14 @@
(createValueChange)="onCreateValueChangeEmployer($event)"
/>
<div>
<app-select-or-create
[name]="'Fonction'"
[isFeminineWord]="true"
[autocompleteFunction]="profileService.getJobs.bind(profileService)"
[(value)]="jobName"
(selectItem)="selectJob($event)"
(createValueChange)="onCreateValueChangeJob($event)"
/>
</div>
<app-select-or-create
[name]="'Fonction'"
[isFeminineWord]="true"
[autocompleteFunction]="profileService.getJobs.bind(profileService)"
[(value)]="jobName"
(selectItem)="selectJob($event)"
(createValueChange)="onCreateValueChangeJob($event)"
/>
<app-appointment-choice
*ngIf="hasPersonalOffer"
......
......@@ -105,7 +105,8 @@
::ng-deep .selectOrCreate {
gap: 24px;
padding: 24px;
border: 1px solid $grey-6;
border: 1px solid $grey-7;
border-radius: 4px;
}
.credentialsTab {
......
<button
#buttonElement
[attr.aria-label]="ariaLabel"
[type]="type"
[ngClass]="classes"
......
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { SpriteFolderType } from '../svg-icon/SpriteFolder.type';
/** values will be used for css selectors */
......@@ -54,7 +54,13 @@ export class ButtonComponent {
/** Click handler */
@Output() action = new EventEmitter<Event>();
@ViewChild('buttonElement') buttonElement!: ElementRef;
public get classes(): string[] {
return [this.variant, this.size, this.wide ? 'wide' : ''];
}
public focus(): void {
this.buttonElement.nativeElement.focus();
}
}
......@@ -7,8 +7,10 @@
<div *ngIf="!day.value.open" class="openingTime">Fermé</div>
<div *ngIf="day.value.open" class="openingTime">
<ng-container *ngFor="let timeRange of day.value.time; let isLast = last">
{{ timeRange.formatOpeningDate() }} - {{ timeRange.formatClosingDate() }}
<span *ngIf="!isLast"> / </span>
<div class="time-range">
{{ timeRange.formatOpeningDate() }} - {{ timeRange.formatClosingDate() }}
</div>
<div *ngIf="!isLast" class="separator">/</div>
</ng-container>
</div>
</li>
......
......@@ -9,34 +9,36 @@ ul.hoursContainer {
list-style: none;
padding: 0;
margin: 0;
@media (max-width: 370px) {
width: 80%;
}
li.hours {
width: 350px;
display: flex;
flex-direction: row;
align-items: center;
align-items: flex-start;
@media (max-width: 370px) {
width: 100%;
}
span.circle {
padding: 0 8px;
background-repeat: no-repeat;
background-position: center center;
background-size: 11px 10px;
background-image: url('../../../../assets/form/hours-open.svg');
&.closed {
background-image: url('../../../../assets/form/hours-closed.svg');
}
&::before {
content: '';
position: absolute;
border-left: 1px solid $border-hours;
z-index: -1;
top: 7px;
left: 8px;
height: 90%;
}
padding: 0px 8px;
background-repeat: no-repeat;
background-position: center center;
background-size: 11px 10px;
background-image: url('../../../../assets/form/hours-open.svg');
&.closed {
background-image: url('../../../../assets/form/hours-closed.svg');
}
}
.left {
display: flex;
gap: 8px;
......@@ -50,14 +52,24 @@ ul.hoursContainer {
@include font-regular-13;
color: $grey-4-5-1;
display: flex;
flex-direction: row;
gap: 8px;
.time-range {
white-space: nowrap;
}
@media (max-width: 370px) {
font-size: 12px;
flex-direction: column;
.separator {
display: none;
}
}
}
}
&.compact {
gap: 6px;
li.hours {
.left.day {
.left .day {
@include font-bold-12;
}
.openingTime {
......
......@@ -10,9 +10,10 @@
<ng-template #svgIcon>
<svg
aria-hidden="true"
class="icon"
role="presentation"
[attr.role]="ariaLabel ? 'image' : 'presentation'"
[attr.aria-hidden]="!ariaLabel ? 'true' : 'false'"
[attr.aria-label]="ariaLabel ? ariaLabel : null"
[ngClass]="iconClass"
[attr.fill]="iconColor"
[attr.stroke]="iconColor"
......
......@@ -19,5 +19,6 @@ export class SvgIconComponent {
/** If should be interactive */
@Input() asButton = false;
@Input() ariaLabel?: string = null;
@Output() action = new EventEmitter<void>();
}
......@@ -3,14 +3,16 @@
<span>{{ tclStop.name }}</span>
<div class="inline listPoints">
<p *ngFor="let sub of tclStop.subLines">
<app-svg-icon [folder]="'tcl'" [icon]="sub" [iconClass]="iconClass" />
<app-svg-icon [folder]="'tcl'" [icon]="sub" [iconClass]="iconClass" [ariaLabel]="'Métro ' + sub"/>
</p>
<p *ngFor="let tram of tclStop.tramLines">
<app-svg-icon [folder]="'tcl'" [icon]="tram" [iconClass]="iconClass" />
<app-svg-icon [folder]="'tcl'" [icon]="tram" [iconClass]="iconClass" [ariaLabel]="'Tram ' + tram"/>
</p>
<p *ngFor="let bus of tclStop.busLines">
<app-svg-icon [folder]="'tcl'" [icon]="bus" [iconClass]="iconClass" />
<app-svg-icon [folder]="'tcl'" [icon]="bus" [iconClass]="iconClass" [ariaLabel]="'Bus ' + bus"/>
</p>
</div>
</div>
</div>