diff --git a/app/assets/stylesheets/contact.scss b/app/assets/stylesheets/contact.scss
index 17e006a5c..3e6cd4496 100644
--- a/app/assets/stylesheets/contact.scss
+++ b/app/assets/stylesheets/contact.scss
@@ -9,12 +9,7 @@ $contact-padding: $default-space * 2;
padding-bottom: $contact-padding;
}
- .hidden {
- display: none;
- }
-
ul {
margin-bottom: $default-space;
}
-
}
diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss
index d3292149c..e5ad12353 100644
--- a/app/assets/stylesheets/forms.scss
+++ b/app/assets/stylesheets/forms.scss
@@ -30,7 +30,8 @@
color: $dark-red;
}
- label {
+ label,
+ legend.form-label {
font-size: 18px;
margin-bottom: $default-padding;
display: block;
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 518ed3147..e66cfdf7e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -30,6 +30,12 @@ module ApplicationHelper
class_names.join(' ')
end
+ def flash_role(level)
+ return "status" if level == "notice"
+
+ 'alert'
+ end
+
def react_component(name, props = {}, html = {})
tag.div(**html.merge(data: { controller: 'react', react_component_value: name, react_props_value: props.to_json }))
end
diff --git a/app/javascript/components/shared/FlashMessage.tsx b/app/javascript/components/shared/FlashMessage.tsx
index 413eae83a..4b358df3c 100644
--- a/app/javascript/components/shared/FlashMessage.tsx
+++ b/app/javascript/components/shared/FlashMessage.tsx
@@ -17,7 +17,12 @@ export function FlashMessage({
invariant(element, 'Flash messages root element not found');
return createPortal(
-
{message}
+
+ {message}
+
,
element
);
@@ -35,3 +40,7 @@ function flashClassName(level: string, sticky = false, fixed = false) {
}
return className.join(' ');
}
+
+function roleName(level: string) {
+ return level == 'notice' ? 'status' : 'alert';
+}
diff --git a/app/javascript/new_design/support.js b/app/javascript/new_design/support.js
index 923f8417e..b87f54a89 100644
--- a/app/javascript/new_design/support.js
+++ b/app/javascript/new_design/support.js
@@ -1,5 +1,5 @@
//
-// This content is inspired by w3c aria example
+// 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
//
@@ -20,22 +20,21 @@ class ButtonExpand {
this.controlledNode = document.getElementById(id);
}
- this.domNode.setAttribute('aria-expanded', 'false');
+ 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));
- this.domNode.addEventListener('focus', this.handleFocus.bind(this));
- this.domNode.addEventListener('blur', this.handleBlur.bind(this));
}
showContent() {
- this.domNode.setAttribute('aria-expanded', 'true');
- this.domNode.classList.add('primary');
+ this.radioInput.checked = true;
+
if (this.controlledNode) {
+ this.controlledNode.setAttribute('aria-hidden', 'false');
this.controlledNode.classList.remove('hidden');
}
- this.formInput.value = this.domNode.getAttribute('data-question-type');
this.allButtons.forEach((b) => {
if (b != this) {
@@ -45,18 +44,22 @@ class ButtonExpand {
}
hideContent() {
- this.domNode.setAttribute('aria-expanded', 'false');
- this.domNode.classList.remove('primary');
+ this.radioInput.checked = false;
+
if (this.controlledNode) {
+ this.controlledNode.setAttribute('aria-hidden', 'true');
this.controlledNode.classList.add('hidden');
}
}
toggleExpand() {
- if (this.domNode.getAttribute('aria-expanded') === 'true') {
- this.hideContent();
- } else {
+ if (
+ this.controlledNode &&
+ this.controlledNode.getAttribute('aria-hidden') === 'true'
+ ) {
this.showContent();
+ } else {
+ this.hideContent();
}
}
@@ -64,14 +67,10 @@ class ButtonExpand {
this.allButtons = buttons;
}
- setFormInput(formInput) {
- this.formInput = formInput;
- }
-
- handleKeydown() {
+ handleKeydown(event) {
switch (event.keyCode) {
case this.keyCode.RETURN:
- this.toggleExpand();
+ this.showContent();
event.stopPropagation();
event.preventDefault();
@@ -83,17 +82,11 @@ class ButtonExpand {
}
handleClick() {
- event.stopPropagation();
- event.preventDefault();
- this.toggleExpand();
- }
+ // 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
- handleFocus = function () {
- this.domNode.classList.add('focus');
- };
-
- handleBlur() {
- this.domNode.classList.remove('focus');
+ this.showContent();
}
}
@@ -103,18 +96,14 @@ if (document.querySelector('#contact-form')) {
window.addEventListener(
'DOMContentLoaded',
function () {
- var buttons = document.querySelectorAll(
- 'button[aria-expanded][aria-controls], button.button-without-hint'
- );
+ var buttons = document.querySelectorAll('fieldset[name=type] label');
var expandButtons = [];
- var formInput = document.querySelector('form input#type');
buttons.forEach((button) => {
var be = new ButtonExpand(button);
expandButtons.push(be);
});
expandButtons.forEach((button) => button.setAllButtons(expandButtons));
- expandButtons.forEach((button) => button.setFormInput(formInput));
},
false
);
diff --git a/app/views/layouts/_account_dropdown.haml b/app/views/layouts/_account_dropdown.haml
index 7e1a3328d..6b6ed92a9 100644
--- a/app/views/layouts/_account_dropdown.haml
+++ b/app/views/layouts/_account_dropdown.haml
@@ -1,6 +1,6 @@
.dropdown.header-menu-opener{ data: { controller: 'menu-button' } }
%button.button.dropdown-button.icon-only.header-menu-button{ title: "Mon compte", data: { menu_button_target: 'button' } }
- .hidden Mon compte
+ %span.hidden= t("my_account", scope: [:layouts])
= image_tag "icons/account-circle.svg", alt: 'Mon compte', width: 24, height: 24, loading: 'lazy'
%ul.header-menu.dropdown-content#mon_compte_menu{ data: { menu_button_target: 'menu' } }
%li
diff --git a/app/views/layouts/_flash_messages.html.haml b/app/views/layouts/_flash_messages.html.haml
index 0206cd3e6..7eb9812f7 100644
--- a/app/views/layouts/_flash_messages.html.haml
+++ b/app/views/layouts/_flash_messages.html.haml
@@ -5,10 +5,10 @@
- sticky = defined?(sticky) ? sticky : false
- fixed = defined?(fixed) ? fixed : false
- if value.class == Array
- .alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
+ .alert{ class: flash_class(key, sticky: sticky, fixed: fixed), role: flash_role(key) }
- value.each do |message|
= sanitize(message)
%br
- else
- .alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
+ .alert{ class: flash_class(key, sticky: sticky, fixed: fixed), role: flash_role(key) }
= sanitize(value)
diff --git a/app/views/layouts/_locale_dropdown.html.haml b/app/views/layouts/_locale_dropdown.html.haml
index e5cbf9d9a..f0cd75a98 100644
--- a/app/views/layouts/_locale_dropdown.html.haml
+++ b/app/views/layouts/_locale_dropdown.html.haml
@@ -1,7 +1,8 @@
.dropdown.locale-dropdown.header-menu-opener{ data: { controller: 'menu-button' } }
%button.button.dropdown-button.icon-only.header-menu-button{ title: t('.languages'), data: { menu_button_target: 'button' } }
- .hidden t('.languages')
- = image_tag "icons/translate-icon.svg", alt: t('.languages'), width: 24, height: 24, lazy: true, aria: { hidden: true }
+ %span.hidden
+ = t('.languages')
+ = image_tag "icons/translate-icon.svg", alt: t('.languages'), width: 24, height: 24, loading: :lazy, aria: { hidden: true }
%ul.header-menu.dropdown-content{ data: { menu_button_target: 'menu' } }
%li
= active_link_to save_locale_path(locale: :fr), method: :post, class: "menu-item menu-link", active: I18n.locale == :fr do
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 098ace1a6..567ff034e 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -34,6 +34,8 @@
= vite_stylesheet_tag 'application', media: 'all'
= stylesheet_link_tag 'application', media: 'all'
+ = yield(:invisible_captcha_styles)
+
%body{ id: content_for(:page_id), class: browser.platform.ios? ? 'ios' : nil }
.page-wrapper
= render partial: "layouts/outdated_browser_banner"
@@ -44,7 +46,7 @@
Env Test
= render partial: "layouts/header"
- %main
+ %main{ role: :main }
= render partial: "layouts/flash_messages"
= content_for?(:content) ? yield(:content) : yield
diff --git a/app/views/layouts/commencer/_no_procedure.html.haml b/app/views/layouts/commencer/_no_procedure.html.haml
index 79d93df32..6c064c5ee 100644
--- a/app/views/layouts/commencer/_no_procedure.html.haml
+++ b/app/views/layouts/commencer/_no_procedure.html.haml
@@ -1,5 +1,5 @@
.no-procedure
- = image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo", alt: "moins de papier"
+ = image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo", alt: ""
.baseline.center
%p
%span.simple= t('.line1')
diff --git a/app/views/support/index.html.haml b/app/views/support/index.html.haml
index f96fe14f7..4ba710c45 100644
--- a/app/views/support/index.html.haml
+++ b/app/views/support/index.html.haml
@@ -7,7 +7,7 @@
%h1.new-h1
= t('.contact')
- = form_tag contact_path, method: :post, multipart: true, class: 'form' do |f|
+ = form_tag contact_path, method: :post, multipart: true, class: 'form' do
.description
%p= t('.intro_html')
@@ -21,26 +21,24 @@
%span.mandatory *
= text_field_tag :email, params[:email], required: true, autocomplete: 'email'
- .contact-champ
- = label_tag :type do
+ %fieldset.radios.vertical{ name: "type" }
+ %legend.form-label
= t('.your_question')
- = hidden_field_tag :type, params[:type]
+ %span.mandatory *
- %dl
- @options.each do |(question, question_type, link)|
- %dt
- - if link.present?
- %button.button{ 'aria-expanded': 'false', 'aria-controls': question_type, data: { 'question-type': question_type } }= question
- - else
- %button.button.button-without-hint{ data: { 'question-type': question_type } }= question
+ = label_tag "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil } do
+ = radio_button_tag :type, question_type, false, required: true
+ = question
+
- if link.present?
- %dd
- .support.card.featured.hidden{ id: question_type }
- .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)
+ .support.card.featured.mb-4.ml-5.hidden{ id: "card-#{question_type}", "aria-hidden": true }
+ .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
= label_tag :dossier_id, t('file_number', scope: [:utils])
diff --git a/app/views/users/passwords/reset_link_sent.html.haml b/app/views/users/passwords/reset_link_sent.html.haml
index a5771b1bb..c224890c5 100644
--- a/app/views/users/passwords/reset_link_sent.html.haml
+++ b/app/views/users/passwords/reset_link_sent.html.haml
@@ -4,7 +4,7 @@
= render partial: 'root/footer'
#link-sent.container
- = image_tag('user/confirmation-email.svg')
+ = image_tag('user/confirmation-email.svg', "aria-hidden": true)
%h1
= t('views.users.passwords.reset_link_sent.got_it')
%br
diff --git a/app/views/users/sessions/_resume_procedure.html.haml b/app/views/users/sessions/_resume_procedure.html.haml
deleted file mode 100644
index effb02c79..000000000
--- a/app/views/users/sessions/_resume_procedure.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- if @dossier
- .panel.panel-default
- .panel-body
- = link_to 'X', users_no_procedure_url, class: 'btn btn-xs', style: 'float: right;'
-
- - if @dossier.procedure.euro_flag
- #euro_flag.flag
- = image_tag('drapeau_europe.png')
-
- #logo_procedure.flag
- = image_tag(dossier.procedure.logo_url)
-
- %h2#titre-procedure.text-info
- = @dossier.procedure.libelle
- %p.procedure-description
- = h string_to_html(@dossier.procedure.description)
diff --git a/app/views/users/sessions/link_sent.html.haml b/app/views/users/sessions/link_sent.html.haml
index 1b26f8f9c..505b2cd5d 100644
--- a/app/views/users/sessions/link_sent.html.haml
+++ b/app/views/users/sessions/link_sent.html.haml
@@ -4,7 +4,7 @@
= render partial: 'root/footer'
#link-sent.container
- = image_tag('user/confirmation-email.svg')
+ = image_tag('user/confirmation-email.svg', "aria-hidden": true)
%h1 Encore une petite étape :)
%section.link-sent-info
diff --git a/app/views/users/sessions/new.html.haml b/app/views/users/sessions/new.html.haml
index bfebfda9f..f184c9cba 100644
--- a/app/views/users/sessions/new.html.haml
+++ b/app/views/users/sessions/new.html.haml
@@ -1,4 +1,5 @@
= content_for(:page_id, 'auth')
+= content_for(:title, t('metas.signin.title'))
#session-new.auth-form.sign-in-form
@@ -8,7 +9,7 @@
= render partial: 'shared/france_connect_login', locals: { url: france_connect_particulier_path }
= f.label :email, t('views.users.sessions.new.email')
- = f.text_field :email, type: :email, autocomplete: 'username', autofocus: true
+ = f.text_field :email, type: :email, autocomplete: 'email', autofocus: true
= f.label :password, t('views.users.sessions.new.password', min_length: PASSWORD_MIN_LENGTH)
= f.password_field :password, autocomplete: 'current-password'
diff --git a/config/initializers/invisible_captcha.rb b/config/initializers/invisible_captcha.rb
index 141f247fb..939f90d16 100644
--- a/config/initializers/invisible_captcha.rb
+++ b/config/initializers/invisible_captcha.rb
@@ -3,7 +3,7 @@ InvisibleCaptcha.setup do |config|
# config.visual_honeypots = false
# config.timestamp_threshold = 2
config.timestamp_enabled = !Rails.env.test?
- # config.injectable_styles = false
+ config.injectable_styles = true
config.spinner_enabled = !Rails.env.test?
# Leave these unset if you want to use I18n (see below)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index fc2089ce0..289f528cc 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -65,6 +65,7 @@ en:
line2: to manage dematerialized
line3: administrative forms.
are_you_new: First time on %{app_name}?
+ my_account: My account
locale_dropdown:
languages: "Languages"
notifications:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 3a7ddaaf4..7ec1ba670 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -55,6 +55,7 @@ fr:
line2: pour gérer les formulaires
line3: administratifs dématérialisés.
are_you_new: Vous êtes nouveau sur %{app_name} ?
+ my_account: Mon compte
locale_dropdown:
languages: "Langues"
notifications:
diff --git a/config/locales/metas.en.yml b/config/locales/metas.en.yml
new file mode 100644
index 000000000..0d8e22bae
--- /dev/null
+++ b/config/locales/metas.en.yml
@@ -0,0 +1,4 @@
+en:
+ metas:
+ signin:
+ title: "Sign-in"
diff --git a/config/locales/metas.fr.yml b/config/locales/metas.fr.yml
new file mode 100644
index 000000000..732ba7c51
--- /dev/null
+++ b/config/locales/metas.fr.yml
@@ -0,0 +1,4 @@
+fr:
+ metas:
+ signin:
+ title: "Se connecter"