From 60c3a450dee7b61ce9c1d37a2ee78f399b7498d8 Mon Sep 17 00:00:00 2001 From: Mathieu Ponton <mponton@grandlyon.com> Date: Thu, 12 Sep 2024 13:17:32 +0000 Subject: [PATCH] feat(input): add required + autocomplete + example for invalid inputs --- src/app/contact/contact.component.html | 14 ++- .../account-info/account-info.component.html | 8 ++ .../structure-contact.component.html | 2 + .../structure-orientator.component.html | 12 ++- .../mediation-beneficiary-info.component.html | 13 ++- .../mediation-beneficiary-info.component.ts | 2 +- src/app/login/login.component.html | 6 ++ .../newsletter-subscription.component.html | 1 + src/app/profile/edit/edit.component.html | 21 +++- .../structure-add-member-modal.component.html | 1 + .../forgot-password.component.html | 6 +- .../reset-password.component.html | 3 + .../components/input/input.component.html | 9 +- .../components/input/input.component.scss | 9 +- .../components/input/input.component.ts | 95 ++++++++++++++++--- .../shared/components/input/input.stories.ts | 8 ++ .../select-or-create.component.html | 2 + .../select-or-create.component.scss | 5 +- .../textarea/textarea.component.html | 9 +- .../textarea/textarea.component.scss | 40 ++++---- .../components/textarea/textarea.component.ts | 3 + .../components/textarea/textarea.stories.ts | 33 ++++--- .../more-filters/more-filters.component.scss | 3 - src/app/utils/CustomRegExp.ts | 1 + src/styles.scss | 10 +- 25 files changed, 246 insertions(+), 70 deletions(-) diff --git a/src/app/contact/contact.component.html b/src/app/contact/contact.component.html index 0b76199a2..92ef14404 100644 --- a/src/app/contact/contact.component.html +++ b/src/app/contact/contact.component.html @@ -1,22 +1,26 @@ <form class="contactForm" [formGroup]="contactForm" (ngSubmit)="onSubmit()"> <h2>Nous contacter</h2> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> <app-input id="name" label="Nom" size="large" - [status]="contactForm.get('name').invalid ? null : 'success'" + autocomplete="on" + [required]="true" + [status]="contactForm.get('name').value ? (contactForm.get('name').invalid ? 'error' : 'success') : null" [value]="contactForm.get('name').value" (valueChange)="contactForm.get('name').setValue($event)" /> <app-input id="email" + type="email" label="Adresse mail" - autocomplete="on" size="large" + autocomplete="on" + [required]="true" [status]="contactForm.get('email').value ? (contactForm.get('email').invalid ? 'error' : 'success') : null" - [statusText]="contactForm.get('email').hasError('alreadyExist') ? 'Cet email est déjà utilisé' : null" [value]="contactForm.get('email').value" (valueChange)="contactForm.get('email').setValue($event)" /> @@ -25,6 +29,8 @@ id="phone" label="Téléphone" size="large" + type="tel" + autocomplete="on" [status]="contactForm.get('phone').value ? (contactForm.get('phone').invalid ? 'error' : 'success') : null" [value]="contactForm.get('phone').value" (valueChange)="contactForm.get('phone').setValue($event); utils.modifyPhoneInput(contactForm, 'phone', $event)" @@ -34,6 +40,7 @@ id="subject" label="Objet du message" size="large" + [required]="true" [status]="contactForm.get('subject').invalid ? null : 'success'" [value]="contactForm.get('subject').value" (valueChange)="contactForm.get('subject').setValue($event)" @@ -43,6 +50,7 @@ id="message" label="Message" placeholder="Exemple : J'aimerais avoir de l'aide sur Rés'in." + [required]="true" [value]="contactForm.get('message').value" (valueChange)="contactForm.get('message').setValue($event)" /> diff --git a/src/app/form/form-view/account-form/account-info/account-info.component.html b/src/app/form/form-view/account-form/account-info/account-info.component.html index c54f8283e..7d82fa96e 100644 --- a/src/app/form/form-view/account-form/account-info/account-info.component.html +++ b/src/app/form/form-view/account-form/account-info/account-info.component.html @@ -2,6 +2,7 @@ <div class="title"> <h3>Qui êtes-vous ?</h3> <p>Ces informations seront visibles dans l’annuaire des acteurs, accessible uniquement en version connectée</p> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> </div> <div class="formGroup"> @@ -9,6 +10,8 @@ id="name" label="Prénom" size="large" + autocomplete="on" + [required]="true" [status]="accountForm.get('name').value ? (accountForm.get('name').invalid ? 'error' : 'success') : null" [value]="accountForm.get('name').value" (valueChange)="accountForm.get('name').setValue($event); setValidationsForm()" @@ -17,6 +20,8 @@ id="surname" label="Nom" size="large" + autocomplete="on" + [required]="true" [status]="accountForm.get('surname').value ? (accountForm.get('surname').invalid ? 'error' : 'success') : null" [value]="accountForm.get('surname').value" (valueChange)="accountForm.get('surname').setValue($event); setValidationsForm()" @@ -25,6 +30,9 @@ id="phone" label="Téléphone" size="large" + autocomplete="on" + type="tel" + [required]="true" [status]="accountForm.get('phone').value ? (accountForm.get('phone').invalid ? 'error' : 'success') : null" [value]="accountForm.get('phone').value" (valueChange)=" diff --git a/src/app/form/form-view/structure-form/structure-contact/structure-contact.component.html b/src/app/form/form-view/structure-form/structure-contact/structure-contact.component.html index 0090c36ff..bdbbd6870 100644 --- a/src/app/form/form-view/structure-form/structure-contact/structure-contact.component.html +++ b/src/app/form/form-view/structure-form/structure-contact/structure-contact.component.html @@ -9,6 +9,7 @@ id="email" label="Email de la structure" size="large" + type="email" [status]="getContactMailStatus()" [value]="structureForm.get('contactMail').value" (valueChange)="structureForm.get('contactMail').setValue($event); setValidationsForm()" @@ -17,6 +18,7 @@ id="phone" label="Téléphone" size="large" + type="tel" [status]="getContactPhoneStatus()" [value]="structureForm.get('contactPhone').value" (valueChange)="utils.modifyPhoneInput(structureForm, 'contactPhone', $event); setValidationsForm()" diff --git a/src/app/form/orientation-form-view/global-components/structure-orientator/structure-orientator.component.html b/src/app/form/orientation-form-view/global-components/structure-orientator/structure-orientator.component.html index 02c32a754..42cc3156b 100644 --- a/src/app/form/orientation-form-view/global-components/structure-orientator/structure-orientator.component.html +++ b/src/app/form/orientation-form-view/global-components/structure-orientator/structure-orientator.component.html @@ -1,6 +1,7 @@ <div class="orientationForm"> <div class="orientation-header"> <h2>Quelle structure oriente la personne ?</h2> + <p *ngIf="!hasStructures" class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> <div *ngIf="hasStructures && structuresLinked.length >= 2" class="number"> {{ structuresLinked.length }} structures sont associées à votre compte </div> @@ -35,6 +36,7 @@ id="structureName" label="Nom de votre structure" size="large" + [required]="true" [status]="form.get('structureName').value ? (form.get('structureName').invalid ? 'error' : 'success') : null" [value]="form.get('structureName').value" (valueChange)="updatedForm('structureName', $event)" @@ -42,10 +44,12 @@ <app-input id="structureMail" - label="Email de votre structure<sup class='sup-input'>1</sup>" + label="Email de votre structure" description="Facultatif" autocomplete="on" size="large" + sup="1" + type="email" [status]="getStatus('structureMail')" [statusText]="getStatusText('structureMail')" [value]="form.get('structureMail').value" @@ -54,10 +58,12 @@ <app-input id="structurePhone" - label="Téléphone de votre structure<sup class='sup-input'>1</sup>" + label="Téléphone de votre structure" description="Facultatif" autocomplete="on" size="large" + sup="1" + type="tel" [status]="getStatus('structurePhone')" [statusText]="getStatusText('structurePhone')" [value]="form.get('structurePhone').value" @@ -68,7 +74,7 @@ </div> <p *ngIf="!hasStructures"> - <sup class="sup-input">1</sup + <sup>1</sup ><span class="footnote"> Un moyen de communication (email et/ou téléphone) est obligatoire pour valider cette étape</span > diff --git a/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.html b/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.html index 698717397..753e8eed6 100644 --- a/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.html +++ b/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.html @@ -5,6 +5,7 @@ <div *ngIf="!isOrientationRdv" class="title"> <h2>Quel est le nom de la personne que vous orientez ?</h2> <p>Ces informations apparaîtront sur une fiche d’orientation à imprimer et à transmettre à la personne</p> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> </div> <div class="formGroup"> @@ -13,6 +14,7 @@ label="Prénom" autocomplete="on" size="large" + [required]="true" [status]="form.get('name').value ? (form.get('name').invalid ? 'error' : 'success') : null" [value]="form.get('name').value" (valueChange)="updatedForm('name', $event)" @@ -23,6 +25,7 @@ label="Nom" autocomplete="on" size="large" + [required]="true" [status]="form.get('surname').value ? (form.get('surname').invalid ? 'error' : 'success') : null" [value]="form.get('surname').value" (valueChange)="updatedForm('surname', $event)" @@ -33,7 +36,9 @@ id="email" autocomplete="on" size="large" - [label]="'Email' + getSup()" + type="email" + label="Email" + [sup]="getSup()" [status]="form.get('email').value ? (form.get('email').invalid ? 'error' : 'success') : null" [statusText]="form.get('email').value ? (form.get('email').invalid ? 'Email invalide' : 'Email valide') : null" [value]="form.get('email').value" @@ -45,7 +50,9 @@ id="phone" autocomplete="on" size="large" - [label]="'Téléphone' + getSup()" + label="Téléphone" + type="tel" + [sup]="getSup()" [status]="form.get('phone').value ? (form.get('phone').invalid ? 'error' : 'success') : null" [statusText]=" form.get('phone').value ? (form.get('phone').invalid ? 'Téléphone invalide' : 'Téléphone valide') : null @@ -56,7 +63,7 @@ </div> <p *ngIf="isOrientationRdv && isPhone() && isEmail()"> - <sup class="sup-input">1</sup + <sup>1</sup ><span class="footnote"> Un moyen de communication (email et/ou téléphone) est obligatoire pour valider cette étape</span > diff --git a/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.ts b/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.ts index 477a33de7..ac62cd667 100644 --- a/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.ts +++ b/src/app/form/orientation-form-view/online-demarch/online-mediation/mediation-beneficiary-info/mediation-beneficiary-info.component.ts @@ -24,7 +24,7 @@ export class MediationBeneficiaryInfoComponent implements OnInit { return Boolean(this.form.get('email')); } public getSup(): string { - return this.isPhone() && this.isEmail() ? "<sup class='sup-input'>1</sup>" : ''; + return this.isPhone() && this.isEmail() ? '1' : ''; } public updatedForm(field: string, value: string): void { diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html index dbe8c8630..cf61a117e 100644 --- a/src/app/login/login.component.html +++ b/src/app/login/login.component.html @@ -3,6 +3,7 @@ <div class="title"> <h3>{{ isWelcome ? 'Bienvenue !' : 'Connexion' }}</h3> <p>Saisissez votre email pour vous connecter</p> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> </div> <p *ngIf="verificationIssue" class="incorrectId"> Une erreur est survenue lors de la validation de votre email... Veuillez envoyer un mail au support. @@ -13,9 +14,11 @@ id="login" label="Identifiant" size="large" + [required]="true" [status]="f.email.value ? (f.email.invalid || authFailed || isUnverifiedEmail ? 'error' : 'success') : null" [statusText]="getLoginStatusText()" [value]="f.email.value" + [wide]="true" (valueChange)="onChange(); f.email.setValue($event)" /> <app-button @@ -31,13 +34,16 @@ label="Mot de passe" size="large" type="password" + [externalStatusControl]="true" [statusText]=" f.password.invalid ? 'Le mot de passe doit obligatoirement contenir : 8 caractères, une majuscule, une minuscule, un caractère spécial et un chiffre' : '' " + [required]="true" [status]="f.password.value ? (f.password.invalid || authFailed ? 'error' : 'success') : 'info'" [value]="f.password.value" + [wide]="true" (valueChange)="onChange(); f.password.setValue($event)" /> </div> diff --git a/src/app/newsletter-subscription/newsletter-subscription.component.html b/src/app/newsletter-subscription/newsletter-subscription.component.html index 49b52d3e1..292b4597f 100644 --- a/src/app/newsletter-subscription/newsletter-subscription.component.html +++ b/src/app/newsletter-subscription/newsletter-subscription.component.html @@ -7,6 +7,7 @@ label="Adresse email" autocomplete="on" size="large" + type="email" [wide]="true" [status]=" subscriptionForm.get('email').value ? (subscriptionForm.get('email').invalid ? 'error' : 'success') : null diff --git a/src/app/profile/edit/edit.component.html b/src/app/profile/edit/edit.component.html index bfffb8af5..9f3c57a6b 100644 --- a/src/app/profile/edit/edit.component.html +++ b/src/app/profile/edit/edit.component.html @@ -51,6 +51,7 @@ [(value)]="userProfile.surname" /> <app-input + type="tel" [id]="'phone'" [label]="'Téléphone'" [size]="'large'" @@ -166,15 +167,23 @@ (closed)="$event ? confirm() : closeModal()" > <div class="modal-content"> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> <app-input + id="email" + type="email" + [required]="true" [label]="'Nouvel email'" [size]="'large'" [status]="getEmailStatus(newEmail)" + [statusText]="getEmailStatusText()" [wide]="true" [(value)]="newEmail" /> <app-input - [label]="'Confirmer le nouvel email'" + id="confirm-email" + type="email" + [required]="true" + [label]="'Confirmation du nouvel email'" [size]="'large'" [status]="getEmailStatus(newEmailConfirm)" [statusText]="getEmailStatusText()" @@ -192,7 +201,10 @@ (closed)="$event ? confirm() : closeModal()" > <div class="modal-content"> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> <app-input + id="old-password" + [required]="true" [label]="'Ancien mot de passe'" [size]="'large'" [autocomplete]="'on'" @@ -200,18 +212,24 @@ [statusText]="getOldPasswordStatusText(getPasswordStatus(oldPassword))" [type]="'password'" [wide]="true" + [externalStatusControl]="true" [(value)]="oldPassword" /> <app-input + id="new-password" + [required]="true" [label]="'Nouveau mot de passe'" [size]="'large'" [status]="getPasswordStatus(newPassword)" [statusText]="getPasswordStatusText(getPasswordStatus(newPassword))" [type]="'password'" [wide]="true" + [externalStatusControl]="true" [(value)]="newPassword" /> <app-input + id="confirm-new-password" + [required]="true" [label]="'Confirmer le nouveau mot de passe'" [size]="'large'" [autocomplete]="'on'" @@ -219,6 +237,7 @@ [statusText]="getNewPasswordStatusText(getPasswordStatus(newPasswordConfirm, newPassword))" [type]="'password'" [wide]="true" + [externalStatusControl]="true" [(value)]="newPasswordConfirm" /> </div> diff --git a/src/app/profile/structure-add-member-modal/structure-add-member-modal.component.html b/src/app/profile/structure-add-member-modal/structure-add-member-modal.component.html index 11b21962a..acb6f334f 100644 --- a/src/app/profile/structure-add-member-modal/structure-add-member-modal.component.html +++ b/src/app/profile/structure-add-member-modal/structure-add-member-modal.component.html @@ -7,6 +7,7 @@ > <app-input autocomplete="on" + type="email" [label]="'Email du membre à ajouter'" [size]="'large'" [wide]="true" diff --git a/src/app/reset-password/forgot-password.component.html b/src/app/reset-password/forgot-password.component.html index 7c84780f8..c458a4fa5 100644 --- a/src/app/reset-password/forgot-password.component.html +++ b/src/app/reset-password/forgot-password.component.html @@ -2,15 +2,19 @@ <div class="title"> <h1>Mot de passe oublié</h1> <p>Saisissez votre email afin de réinitialiser votre mot de passe</p> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> </div> <form [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()"> <div class="fields"> <app-input + id="account-email" + type="email" size="large" [placeholder]="'exemple@mail.com'" [label]="'Email du compte'" [value]="email.value" - [status]="email.value ? (email.invalid ? 'error' : 'success') : null" + [required]="true" + [wide]="true" (valueChange)="email.setValue($event)" /> </div> diff --git a/src/app/reset-password/reset-password.component.html b/src/app/reset-password/reset-password.component.html index b8c859982..64eef7016 100644 --- a/src/app/reset-password/reset-password.component.html +++ b/src/app/reset-password/reset-password.component.html @@ -1,6 +1,7 @@ <div class="resetPage"> <div class="title"> <h1>Réinitialisation du mot de passe</h1> + <p class="required-text-help">Les champs marqués d'un <sup>*</sup> sont obligatoires.</p> </div> <form [formGroup]="resetPasswordForm" (ngSubmit)="onSubmit()"> <div class="fields"> @@ -10,6 +11,7 @@ size="large" type="password" [value]="password.value" + [required]="true" (valueChange)="password.setValue($event)" /> <div class="passwordConditions"> @@ -63,6 +65,7 @@ label="Vérification du mot de passe" size="large" type="password" + [required]="true" [status]="confirmPassword.value ? (confirmPassword.invalid ? 'error' : 'success') : null" [value]="confirmPassword.value" (valueChange)="confirmPassword.setValue($event)" diff --git a/src/app/shared/components/input/input.component.html b/src/app/shared/components/input/input.component.html index 7bcaf6453..4330dd269 100644 --- a/src/app/shared/components/input/input.component.html +++ b/src/app/shared/components/input/input.component.html @@ -1,6 +1,9 @@ <div class="container" [ngClass]="{ disabled: disabled, readonly: readonly, wide: wide }"> <div class="label"> - <label [ngClass]="status" [htmlFor]="id" [innerHtml]="label"></label> + <div> + <label [ngClass]="status" [htmlFor]="id + '_Input'">{{ label }}</label> + <sup *ngIf="sup">{{ sup }}</sup> + </div> <span *ngIf="description" class="description" [ngClass]="{ disabled: disabled, readOnly: readonly }">{{ description }}</span> @@ -8,13 +11,15 @@ <div class="inputContainer" [ngClass]="{ hasIconInField: hasIconInField }"> <input + [attr.aria-required]="required ? 'true' : 'false'" [type]="type" - [id]="id" + [id]="id + '_Input'" [disabled]="disabled" [readonly]="readonly" [autocomplete]="autocomplete" [ngClass]="classes" [placeholder]="placeholder" + [attr.maxlength]="255" [(ngModel)]="value" (ngModelChange)="onChange()" (blur)="onFinishedEditing()" diff --git a/src/app/shared/components/input/input.component.scss b/src/app/shared/components/input/input.component.scss index 5593ae056..3c1d7b022 100644 --- a/src/app/shared/components/input/input.component.scss +++ b/src/app/shared/components/input/input.component.scss @@ -36,7 +36,12 @@ } } - span { + sup { + margin-left: 4px; + cursor: default; + } + + .description { font-size: $font-size-xxsmall; color: $grey-3; cursor: default; @@ -62,7 +67,7 @@ box-sizing: border-box; border-radius: 4px; border: 1px solid $grey-4; - padding: 8px 0px 8px 16px; + padding: 8px 0 8px 16px; font-size: $font-size-small; transition: all 0.3s ease-in-out; diff --git a/src/app/shared/components/input/input.component.ts b/src/app/shared/components/input/input.component.ts index 1259e448e..fe985ce54 100644 --- a/src/app/shared/components/input/input.component.ts +++ b/src/app/shared/components/input/input.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { CustomRegExp } from 'src/app/utils/CustomRegExp'; @Component({ selector: 'app-input', @@ -9,7 +10,7 @@ export class InputComponent implements OnInit { /** HTML id associated with for */ @Input() id: string; - @Input() type: 'text' | 'password' | 'time' = 'text'; + @Input() type: 'text' | 'password' | 'time' | 'email' | 'tel' = 'text'; @Input() disabled = false; @@ -41,22 +42,44 @@ export class InputComponent implements OnInit { @Input() value: string; + @Input() required = false; + + /** Additional sup information next to the label */ + @Input() sup = ''; + + /** Determines if the parent component controls the status and statusText */ + @Input() externalStatusControl = false; + /** Triggers when input changes */ @Output() valueChange = new EventEmitter<string>(); @Output() finishedEditing = new EventEmitter<string>(); - hasIconInField = false; - - ngOnInit(): void { - if (this.type === 'password' || this.readonly) this.hasIconInField = true; - } + public hasIconInField = false; + private example = ''; public get classes(): string { return [this.size, this.status, this.wide ? 'wide' : ''].join(' '); } + ngOnInit(): void { + if (this.type === 'password' || this.readonly) this.hasIconInField = true; + if (this.required) this.sup = '*'; + console.log({ required: this.required, sup: this.sup }); + } + public onChange(): void { + if (!this.externalStatusControl) { + if (this.type === 'text' && this.isInvalidText()) { + this.setStatusError(this.getStatusText()); + } else if (this.type === 'email' && !Boolean(this.value.match(CustomRegExp.EMAIL))) { + this.setStatusError(this.getExampleText()); + } else if (this.type === 'tel' && !Boolean(this.value.match(CustomRegExp.PHONE))) { + this.setStatusError(this.getExampleText()); + } else { + this.setStatusSuccess(); + } + } this.valueChange.emit(this.value); } @@ -69,15 +92,61 @@ export class InputComponent implements OnInit { } getStatusText(): string { - if (this.statusText) return this.statusText; + if (this.externalStatusControl) { + return this.statusText || ''; + } + if (this.value === '') { + return ''; + } + if (this.status === 'error') { + this.example = this.getExampleText(); + return `${this.label} invalide. ${this.example}`; + } + if (this.status === 'success') { + return `${this.label} valide`; + } + return ''; + } - switch (this.status) { - case 'success': - return `${this.label} valide`; - case 'error': - return `${this.label} invalide`; + private isInvalidText(): boolean { + return ( + (this.id.startsWith('name') || this.id.startsWith('surname') || this.id === 'subject') && + !CustomRegExp.NAME.test(this.value) + ); + } + + private setStatusError(message: string): void { + this.status = 'error'; + this.statusText = message; + } + + private setStatusSuccess(): void { + this.status = 'success'; + this.statusText = ''; + } + + private getExampleText(): string { + switch (true) { + case this.type === 'email': + return 'Exemple: ml@gmail.com'; + case this.type === 'tel': + return 'Exemple: 06 11 22 33 44'; + case this.id.startsWith('name'): + return this.getNameExampleText(); + case this.id.startsWith('surname'): + return 'Exemple: Bocuse'; default: - break; + return ''; + } + } + + private getNameExampleText(): string { + if (this.id.startsWith('name')) { + if (this.label === 'Prénom') { + return 'Exemple : Paul'; + } else { + return 'Exemple : Paul Bocuse'; + } } } } diff --git a/src/app/shared/components/input/input.stories.ts b/src/app/shared/components/input/input.stories.ts index 11c5b288a..b4ff594ad 100644 --- a/src/app/shared/components/input/input.stories.ts +++ b/src/app/shared/components/input/input.stories.ts @@ -88,3 +88,11 @@ export const InputReadonly: Story = { readonly: true, }, }; + +export const InputWithSup: Story = { + args: { + ...Input.args, + id: 'input8', + sup: '1', + }, +}; diff --git a/src/app/shared/components/select-or-create/select-or-create.component.html b/src/app/shared/components/select-or-create/select-or-create.component.html index 9e84c456d..8f4f28d64 100644 --- a/src/app/shared/components/select-or-create/select-or-create.component.html +++ b/src/app/shared/components/select-or-create/select-or-create.component.html @@ -11,6 +11,7 @@ [status]="!isAddingNewItem ? (isSelectedItem ? 'success' : 'error') : null" [description]="'Recherchez votre ' + name.toLowerCase() + ' dans la liste suivante'" [value]="isAddingNewItem ? null : value" + [externalStatusControl]="true" (valueChange)="onAutocompleteValueChange($event)" (click)="onAutocompleteValueChange(value || '')" /> @@ -45,6 +46,7 @@ [placeholder]="'exemple : ' + name.toLowerCase()" [description]="'Renseignez le nom de votre ' + name.toLowerCase()" [status]="isFieldValid() ? 'success' : 'error'" + [externalStatusControl]="true" (valueChange)="onCreateValueChange($event)" /> <app-button label="Annuler la création" [variant]="'primaryBlack'" (click)="toggleAddItem()" /> diff --git a/src/app/shared/components/select-or-create/select-or-create.component.scss b/src/app/shared/components/select-or-create/select-or-create.component.scss index 3b28dfc46..776ea7317 100644 --- a/src/app/shared/components/select-or-create/select-or-create.component.scss +++ b/src/app/shared/components/select-or-create/select-or-create.component.scss @@ -9,7 +9,6 @@ .select { .autocomplete-items { - top: -28px; position: relative; > div { border-bottom: 1px solid $grey-7; @@ -29,10 +28,8 @@ gap: 16px; .createForm { display: flex; - flex-direction: row; + flex-direction: column; gap: 16px; - align-items: center; - flex-grow: 1; } p { @include font-regular-16; diff --git a/src/app/shared/components/textarea/textarea.component.html b/src/app/shared/components/textarea/textarea.component.html index 1cc8cec25..261ec67ae 100644 --- a/src/app/shared/components/textarea/textarea.component.html +++ b/src/app/shared/components/textarea/textarea.component.html @@ -1,7 +1,12 @@ <div class="inputContainer" [ngClass]="{ disabled: disabled }"> - <label [htmlFor]="'textarea-' + id" [ngClass]="status">{{ label }}</label> + <div class="label"> + <div> + <label [htmlFor]="'textarea-' + id" [ngClass]="status">{{ label }}</label> + <span *ngIf="required" class="required">*</span> + </div> + <span *ngIf="description" class="description" [ngClass]="{ disabled: disabled }">{{ description }}</span> + </div> - <span *ngIf="description" class="description" [ngClass]="{ disabled: disabled }">{{ description }}</span> <textarea rows="8" [value]="value" diff --git a/src/app/shared/components/textarea/textarea.component.scss b/src/app/shared/components/textarea/textarea.component.scss index 72754051c..c8285e157 100644 --- a/src/app/shared/components/textarea/textarea.component.scss +++ b/src/app/shared/components/textarea/textarea.component.scss @@ -4,8 +4,9 @@ .inputContainer { display: flex; flex-direction: column; - width: 100vw; + width: 100%; max-width: 600px; + &.disabled { cursor: not-allowed; label { @@ -13,23 +14,30 @@ } } - label { - line-height: 150%; - color: $grey-1; - &.error { - color: $red; + .label { + label { + line-height: 150%; + color: $grey-1; + &.error { + color: $red; + } + &.success { + color: $info-success; + } } - &.success { - color: $info-success; + .required { + margin-left: 4px; + font-size: $font-size-xxsmall; + color: $red; + cursor: default; } - } - - span.description { - margin-top: 4px; - font-size: $font-size-xxsmall; - color: $grey-3; - &.disabled { - color: $grey-6; + span.description { + margin-top: 4px; + font-size: $font-size-xxsmall; + color: $grey-3; + &.disabled { + color: $grey-6; + } } } diff --git a/src/app/shared/components/textarea/textarea.component.ts b/src/app/shared/components/textarea/textarea.component.ts index ee6b4dc6f..30b48b674 100644 --- a/src/app/shared/components/textarea/textarea.component.ts +++ b/src/app/shared/components/textarea/textarea.component.ts @@ -32,6 +32,9 @@ export class TextareaComponent { /** Value */ @Input() value = ''; + /** Is the textarea required ? */ + @Input() required = false; + @Output() valueChange = new EventEmitter<string>(); public onValueChange(): void { diff --git a/src/app/shared/components/textarea/textarea.stories.ts b/src/app/shared/components/textarea/textarea.stories.ts index f4d4f03a7..cc25a8143 100644 --- a/src/app/shared/components/textarea/textarea.stories.ts +++ b/src/app/shared/components/textarea/textarea.stories.ts @@ -22,43 +22,52 @@ type Story = StoryObj<TextareaComponent>; export const Textarea: Story = { args: { - id: 'input1', + id: 'textarea1', label: 'Label', }, }; -export const InputWithDescription: Story = { +export const TextareaWithDescription: Story = { args: { ...Textarea.args, - id: 'input2', + id: 'textarea2', description: 'Texte de description additionnel', }, }; -export const InputDisabled: Story = { +export const TextareaDisabled: Story = { args: { - ...InputWithDescription.args, - id: 'input3', + ...TextareaWithDescription.args, + id: 'textarea3', disabled: true, }, }; -export const InputWithError: Story = { +export const TextareaWithError: Story = { args: { - ...InputWithDescription.args, - id: 'input4', + ...TextareaWithDescription.args, + id: 'textarea4', label: 'Label', status: 'error', statusText: "Texte d'erreur obligatoire", }, }; -export const InputWithSuccess: Story = { +export const TextareaWithSuccess: Story = { args: { - ...InputWithDescription.args, - id: 'input5', + ...TextareaWithDescription.args, + id: 'textarea5', label: 'Label', status: 'success', statusText: 'Texte de succès', }, }; + +export const TextareaRequired: Story = { + args: { + ...Textarea.args, + id: 'textarea6', + label: 'This is required', + required: true, + }, +}; diff --git a/src/app/structure-list/components/more-filters/more-filters.component.scss b/src/app/structure-list/components/more-filters/more-filters.component.scss index 3cbfff626..2e6430faf 100644 --- a/src/app/structure-list/components/more-filters/more-filters.component.scss +++ b/src/app/structure-list/components/more-filters/more-filters.component.scss @@ -80,7 +80,6 @@ #categoryName { padding: 0 0; } - } .collapseContent { display: flex; @@ -89,7 +88,6 @@ padding-block: 16px; padding-left: 24px; border-top: 1px solid $grey-4; - } } } @@ -100,4 +98,3 @@ padding-block: 16px; } } - diff --git a/src/app/utils/CustomRegExp.ts b/src/app/utils/CustomRegExp.ts index 9dfc15812..c23979fe6 100644 --- a/src/app/utils/CustomRegExp.ts +++ b/src/app/utils/CustomRegExp.ts @@ -35,4 +35,5 @@ export class CustomRegExp { * Validate a location request in search bar */ public static readonly LOCATION: RegExp = /^\d+\s[A-z]+\s[A-z]+/g; // NOSONAR + public static readonly NAME: RegExp = /^[\p{L}\s'-]*$/u; // NOSONAR } diff --git a/src/styles.scss b/src/styles.scss index a8322ad8d..3c155b8ce 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -329,12 +329,9 @@ p { } sup { + font-size: $font-size-xxsmall; color: $red; } -.sup-input { - font-size: $font-size-xxxxsmall; - color: unset; -} .formGroup { display: flex; @@ -361,3 +358,8 @@ div.inline { .sb-colorRow { margin-block: 12px !important; } + +p.required-text-help { + color: $grey-1; + font-size: $font-size-xsmall !important; +} -- GitLab