Merge pull request #8300 from mfo/a11y/contact-apge

correctif(a11y.contact-page): #8058 (utiliser le type email sur l'input prenant l'email de l'usager), #8056 (ajuster les erreurs de contraste par l'usage des composants du DSFR)
This commit is contained in:
Colin Darie 2022-12-21 11:00:28 +01:00 committed by GitHub
commit fdb252ed7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 84 additions and 144 deletions

View file

@ -12,7 +12,6 @@
a { a {
color: #FFFFFF; color: #FFFFFF;
text-decoration: underline;
} }
} }

View file

@ -0,0 +1,48 @@
import { ApplicationController } from './application_controller';
import { hide, show } from '@utils';
export class SupportController extends ApplicationController {
static targets = ['inputRadio', 'content'];
declare readonly inputRadioTargets: HTMLInputElement[];
declare readonly contentTargets: HTMLElement[];
connect() {
this.inputRadioTargets.forEach((inputRadio) => {
inputRadio.addEventListener('change', this.onChange.bind(this));
inputRadio.addEventListener('keydown', this.onChange.bind(this));
});
}
onChange(event: Event) {
const target = event.target as HTMLInputElement;
const content = this.getContentForTarget(target);
this.contentTargets.forEach((content) => {
hide(content);
content.setAttribute('aria-hidden', 'true');
});
if (target.checked && content) {
show(content);
content.setAttribute('aria-hidden', 'false');
}
}
getLabelForTarget(target: HTMLInputElement) {
const labelSelector = `label[for="${target.id}"]`;
return document.querySelector(labelSelector);
}
getContentForTarget(target: HTMLInputElement) {
const label = this.getLabelForTarget(target);
if (!label) {
return null;
}
const contentSelector = label.getAttribute('aria-controls');
if (contentSelector) {
return document.getElementById(contentSelector);
}
}
}

View file

@ -14,7 +14,6 @@ import { registerControllers } from '../shared/stimulus-loader';
import '../new_design/form-validation'; import '../new_design/form-validation';
import '../new_design/procedure-context'; import '../new_design/procedure-context';
import '../new_design/procedure-form'; import '../new_design/procedure-form';
import '../new_design/support';
import { import {
toggleCondidentielExplanation, toggleCondidentielExplanation,

View file

@ -8,6 +8,7 @@
/* Verify README of each component to insert them in the expected order. */ /* Verify README of each component to insert them in the expected order. */
@import '@gouvfr/dsfr/dist/component/alert/alert.css'; @import '@gouvfr/dsfr/dist/component/alert/alert.css';
@import '@gouvfr/dsfr/dist/component/radio/radio.css';
@import '@gouvfr/dsfr/dist/component/badge/badge.css'; @import '@gouvfr/dsfr/dist/component/badge/badge.css';
@import '@gouvfr/dsfr/dist/component/breadcrumb/breadcrumb.css'; @import '@gouvfr/dsfr/dist/component/breadcrumb/breadcrumb.css';
@import '@gouvfr/dsfr/dist/component/callout/callout.css'; @import '@gouvfr/dsfr/dist/component/callout/callout.css';

View file

@ -1,110 +0,0 @@
//
// This content is inspired by w3c aria example, rewritten for better RGAA compatibility.
// https://www.w3.org/TR/wai-aria-practices-1.1/examples/disclosure/disclosure-faq.html
//
class ButtonExpand {
constructor(domNode) {
this.domNode = domNode;
this.keyCode = Object.freeze({
RETURN: 13
});
this.allButtons = [];
this.controlledNode = false;
var id = this.domNode.getAttribute('aria-controls');
if (id) {
this.controlledNode = document.getElementById(id);
}
this.radioInput = this.domNode.querySelector('input[type="radio"]');
this.hideContent();
this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
this.domNode.addEventListener('click', this.handleClick.bind(this));
}
showContent() {
this.radioInput.checked = true;
if (this.controlledNode) {
this.controlledNode.setAttribute('aria-hidden', 'false');
this.controlledNode.classList.remove('hidden');
}
this.allButtons.forEach((b) => {
if (b != this) {
b.hideContent();
}
});
}
hideContent() {
this.radioInput.checked = false;
if (this.controlledNode) {
this.controlledNode.setAttribute('aria-hidden', 'true');
this.controlledNode.classList.add('hidden');
}
}
toggleExpand() {
if (
this.controlledNode &&
this.controlledNode.getAttribute('aria-hidden') === 'true'
) {
this.showContent();
} else {
this.hideContent();
}
}
setAllButtons(buttons) {
this.allButtons = buttons;
}
handleKeydown(event) {
switch (event.keyCode) {
case this.keyCode.RETURN:
this.showContent();
event.stopPropagation();
event.preventDefault();
break;
default:
break;
}
}
handleClick() {
// NOTE: click event is also fired on input and label activations
// ie., not necessarily by a mouse click but any user inputs, like keyboard navigation with arrows keys.
// Cf https://www.w3.org/TR/2012/WD-html5-20121025/content-models.html#interactive-content
this.showContent();
}
}
/* Initialize Hide/Show Buttons */
if (document.querySelector('#contact-form')) {
window.addEventListener(
'DOMContentLoaded',
function () {
var buttons = document.querySelectorAll('fieldset[name=type] label');
var expandButtons = [];
buttons.forEach((button) => {
var be = new ButtonExpand(button);
expandButtons.push(be);
});
expandButtons.forEach((button) => button.setAllButtons(expandButtons));
},
false
);
}

View file

@ -7,7 +7,7 @@
%h1.new-h1 %h1.new-h1
= t('.contact') = t('.contact')
= form_tag contact_path, method: :post, multipart: true, class: 'form' do = form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do
.description .description
%h2= t('.intro_html') %h2= t('.intro_html')
@ -15,59 +15,62 @@
%p.mandatory-explanation= t('asterisk_html', scope: [:utils]) %p.mandatory-explanation= t('asterisk_html', scope: [:utils])
- if !user_signed_in? - if !user_signed_in?
.contact-champ .fr-input-group
= label_tag :email do = label_tag :email, class: 'fr-label' do
Email Email
%span.mandatory * %span.mandatory *
= text_field_tag :email, params[:email], required: true, autocomplete: 'email' = email_field_tag :email, params[:email], required: true, autocomplete: 'email', class: 'fr-input'
%fieldset.radios.vertical{ name: "type" } %fieldset.fr-fieldset{ name: "type" }
%legend.form-label %legend.fr-fieldset__legend
= t('.your_question') = t('.your_question')
%span.mandatory * %span.mandatory *
.fr-fieldset__content
- @options.each do |(question, question_type, link)|
.fr-radio-group
= radio_button_tag :type, question_type, false, required: true, data: {"support-target": "inputRadio" }
= label_tag "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do
= question
- @options.each do |(question, question_type, link)| - if link.present?
= label_tag "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil } do .support.card.featured.mb-4.ml-4.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } }
= radio_button_tag :type, question_type, false, required: true .card-title
= question = t('.our_answer')
.card-content
- if link.present? -# i18n-tasks-use t("support.index.#{question_type}.answer_html")
.support.card.featured.mb-4.ml-5.hidden{ id: "card-#{question_type}", "aria-hidden": true } = t('answer_html', scope: [:support, :index, question_type], base_url: APPLICATION_BASE_URL, "link_#{question_type}": link)
.card-title
= t('.our_answer')
.card-content
-# i18n-tasks-use t("support.index.#{question_type}.answer_html")
= t('answer_html', scope: [:support, :index, question_type], base_url: APPLICATION_BASE_URL, "link_#{question_type}": link)
.contact-champ .fr-input-group
= label_tag :dossier_id, t('file_number', scope: [:utils]) = label_tag :dossier_id, t('file_number', scope: [:utils]), class: 'fr-label'
= text_field_tag :dossier_id, @dossier_id = text_field_tag :dossier_id, @dossier_id, class: 'fr-input'
.contact-champ .fr-input-group
= label_tag :subject do = label_tag :subject, class: 'fr-label' do
= t('subject', scope: [:utils]) = t('subject', scope: [:utils])
%span.mandatory * %span.mandatory *
= text_field_tag :subject, params[:subject], required: true = text_field_tag :subject, params[:subject], required: true, class: 'fr-input'
.contact-champ .fr-input-group
= label_tag :text do = label_tag :text, class: 'fr-label' do
= t('message', scope: [:utils]) = t('message', scope: [:utils])
%span.mandatory * %span.mandatory *
= text_area_tag :text, params[:text], rows: 6, required: true = text_area_tag :text, params[:text], rows: 6, required: true, class: 'fr-input'
.contact-champ .fr-upload-group
= label_tag :piece_jointe do = label_tag :piece_jointe, class: 'fr-label' do
= t('pj', scope: [:utils]) = t('pj', scope: [:utils])
%span.fr-hint-text Taille maximale : 200 Mo. Formats supportés : jpg, png, pdf.
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } } %p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } }
= t('.notice_pj_product') = t('.notice_pj_product')
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AUTRE } } %p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AUTRE } }
= t('.notice_pj_other') = t('.notice_pj_other')
= file_field_tag :piece_jointe = file_field_tag :piece_jointe, class: 'fr-upload', max: 200.megabytes
= hidden_field_tag :tags, @tags&.join(',') = hidden_field_tag :tags, @tags&.join(',')
= invisible_captcha = invisible_captcha
.send-wrapper .send-wrapper.fr-my-3w
= button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'button send primary' = button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'fr-btn send'