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"