Skip to content

ETQ instructeur, l'erreur de motivation manquante s'affiche au format DSFR#13455

Open
colinux wants to merge 7 commits into
demarche-numerique:mainfrom
colinux:ux-decision-motivation-validation
Open

ETQ instructeur, l'erreur de motivation manquante s'affiche au format DSFR#13455
colinux wants to merge 7 commits into
demarche-numerique:mainfrom
colinux:ux-decision-motivation-validation

Conversation

@colinux

@colinux colinux commented Jul 9, 2026

Copy link
Copy Markdown
Member

Suite des retours UX du parcours d'instruction, pages 4 , 5, 8

Contexte

Dans la modale « Rendre une décision » (unitaire et lot), une motivation obligatoire manquante était signalée par la bulle native du navigateur, hors charte DSFR.

Ce que fait cette PR

  • La validation native continue de bloquer la soumission, mais sa bulle est remplacée par le pattern d'erreur DSFR : les formulaires optent via data-dsfr-validation, le serveur rend le message d'erreur masqué à côté du champ, et le module global form-validation.ts bascule les classes DSFR depuis l'événement invalid (focus sur le champ en erreur, effacement dès que la saisie redevient valide). Mécanisme opt-in, réutilisable tel quel par d'autres formulaires.
  • Filet serveur : les deux endpoints de décision rejettent désormais une motivation vide (flash + redirect) — il n'existait aucune validation serveur.

Pourquoi pas un traitement de formulaire classique ?

Le round-trip serveur avec re-rendu du formulaire en erreur a été prototypé puis écarté : re-rendre la modale détruit l'état client du direct upload du justificatif et oblige à restructurer le formulaire batch imbriqué — environ deux fois plus de code pour la même promesse UX. Ici, l'erreur est instantanée, aucune requête ne part tant que le formulaire est invalide, et l'état de la modale (justificatif compris) n'est jamais perturbé.

Retours UX complémentaires

Trois ajustements sur le même flux de décision, suite à relecture :

  • Alignement du bouton « Ajouter un justificatif » : son contenu était rentré par rapport au bord gauche du textarea, à cause du padding horizontal du bouton DSFR tertiary. Corrigé avec la classe DSFR dédiée fr-btn--align-on-content, qui aligne le contenu du bouton sur le champ (en remplacement d'un fr-ml-0 inopérant côté menu déroulant).
  • Formulaire de motivation « en construction » grisé : alternative au fix déjà mergé, vu après coup le <li> du menu portait la classe inactive pour neutraliser le survol des items du menu, mais cette classe déclenche aussi l'utilitaire globale opacity: 0.5 — le formulaire, pourtant pleinement interactif, s'affichait à moitié transparent. L'exclusion du survol s'appuie désormais sur form-inside, ce qui supprime l'opacité parasite sans toucher à l'utilitaire globale.
  • Ouverture du sélecteur de fichier au clic sur « Ajouter un justificatif » : le bouton se contentait de révéler l'input[type=file] (bouton « Parcourir »), et surtout le focus sautait sur le champ motivation — hide() de coldwired déplace le focus vers l'élément focalisable précédent quand on masque le bouton qui le porte. Le clic ouvre désormais directement le sélecteur natif, et le focus est envoyé sur l'input fichier via l'action focus de coldwired appliquée avant le hide (le bouton ne porte alors plus le focus, coldwired ne le vole plus vers motivation).
Capture d’écran 2026-07-13 à 18 49 16

@colinux
colinux marked this pull request as draft July 9, 2026 16:46
@colinux
colinux force-pushed the ux-decision-motivation-validation branch from 4f5b657 to 89fbb62 Compare July 9, 2026 16:47
@colinux colinux changed the title ETQ instructeur, l'erreur de motivation manquante s'affiche en style DSFR avant toute confirmation WIP ETQ instructeur, l'erreur de motivation manquante s'affiche en style DSFR avant toute confirmation Jul 9, 2026
@colinux colinux changed the title WIP ETQ instructeur, l'erreur de motivation manquante s'affiche en style DSFR avant toute confirmation WIP ETQ instructeur, retours UX sur l'erreur de motivation manquante Jul 9, 2026
@colinux
colinux force-pushed the ux-decision-motivation-validation branch from 89fbb62 to 2f65c9e Compare July 10, 2026 09:38
colinux added 3 commits July 13, 2026 17:20
Native constraint validation keeps blocking the submission of an empty
mandatory motivation, but its browser bubble is replaced by the DSFR error
pattern: forms opt in with data-dsfr-validation, the server renders the
error text hidden next to the input, and the existing global
form-validation module toggles the DSFR error classes from the "invalid"
event (suppressing the native bubble and focusing the first invalid
field). The error clears as soon as the input becomes valid again.

No server round-trip is involved, so the modal state (justificatif direct
upload included) is untouched when validation fails.
Client-side validation is the primary UX, but nothing enforced the
mandatory motivation server-side: a crafted request could refuse or
classify a dossier without any motivation. Guard both decision endpoints
(single and batch) with a flash + redirect safety net.
@colinux colinux changed the title WIP ETQ instructeur, retours UX sur l'erreur de motivation manquante ETQ instructeur, l'erreur de motivation manquante s'affiche au format DSFR Jul 13, 2026
@colinux
colinux force-pushed the ux-decision-motivation-validation branch from 38ea6aa to 98c4e3c Compare July 13, 2026 16:01
colinux added 4 commits July 13, 2026 18:05
No behavior change: event targets are narrowed with the coldwired/utils
type guards already used across controllers.
Use DSFR's fr-btn--align-on-content modifier to cancel the tertiary
button's horizontal padding, so the optional justificatif action lines
up with the motivation textarea's left edge. Replaces the ineffective
fr-ml-0 previously used on the dropdown variant.
The dropdown motivation form was marked with the "inactive" class to
opt its <li> out of the .dropdown-items hover styling. That class also
matches the global .inactive { opacity: 0.5 } utility, so the fully
interactive form rendered at half opacity.

Key the hover exclusion on the already-present "form-inside" class
instead, and drop "inactive" from the menu items. The global utility is
left untouched for its legitimate use (quotient familial).
Clicking "Ajouter un justificatif" hid the button while it held focus. coldwired's hide() moves focus to the previous focusable element, so focus jumped to the motivation textarea. It also only revealed the file input's "Parcourir" button instead of opening the picker.

Now the file input is clicked to open the native picker, and focus is sent to it through coldwired's focus action, applied before hide so the button no longer holds focus and hide does not steal it back to motivation.
@colinux
colinux marked this pull request as ready for review July 16, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant