Merge pull request #4869 from betagouv/4839-accessibilite-formulaire
Usager : amélioration de l'accessibilité des labels du formulaire
This commit is contained in:
commit
77c81cd1ae
18 changed files with 135 additions and 16 deletions
|
@ -44,6 +44,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
margin-bottom: $default-padding;
|
||||
}
|
||||
|
||||
.notice {
|
||||
@include notice-text-style;
|
||||
margin-top: - $default-spacer;
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
input[type=email] {
|
||||
width: auto;
|
||||
margin-bottom: 0;
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
.button {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@import "common";
|
||||
@import "constants";
|
||||
@import "mixins";
|
||||
@import "utils";
|
||||
|
||||
$header-landing-breakpoint: 1040px;
|
||||
$header-mobile-breakpoint: 550px;
|
||||
|
@ -148,6 +149,10 @@ $header-mobile-breakpoint: 550px;
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
label.hidden {
|
||||
@extend .hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
@extend %outline;
|
||||
|
||||
|
|
|
@ -80,6 +80,10 @@ class Champ < ApplicationRecord
|
|||
type_de_champ.to_typed_id
|
||||
end
|
||||
|
||||
def html_label?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def needs_dossier_id?
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
class Champs::CiviliteChamp < Champ
|
||||
def html_label?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,6 +13,10 @@ class Champs::DatetimeChamp < Champ
|
|||
value.present? ? I18n.l(Time.zone.parse(value)) : ""
|
||||
end
|
||||
|
||||
def html_label?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format_before_save
|
||||
|
|
|
@ -51,4 +51,8 @@ class Champs::PieceJustificativeChamp < Champ
|
|||
piece_justificative_file.service_url
|
||||
end
|
||||
end
|
||||
|
||||
def html_label?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,8 +15,12 @@
|
|||
= form_tag dossier_invites_path(dossier), remote: true, method: :post, class: 'form' do
|
||||
.row
|
||||
.col
|
||||
= email_field_tag :invite_email, '', class: 'small', placeholder: 'adresse email', required: true
|
||||
%span
|
||||
= label_tag :invite_email, "Adresse email"
|
||||
= email_field_tag :invite_email, '', class: 'small', placeholder: 'Adresse email', required: true
|
||||
.col
|
||||
%span
|
||||
= label_tag :invite_message, "Ajouter un message à la personne invitée (optionnel)"
|
||||
= text_area_tag :invite_message, '', class: 'small', placeholder: 'Ajouter un message à la personne invitée (optionnel)'
|
||||
.col
|
||||
= submit_tag 'Envoyer une invitation', class: 'button accepted'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.dropdown.header-menu-opener
|
||||
%button.button.dropdown-button.header-menu-button{ title: "Mon compte" }
|
||||
.hidden Mon compte
|
||||
= image_tag "icons/account-circle.svg", alt: ''
|
||||
%ul.header-menu.dropdown-content
|
||||
%li
|
||||
|
|
|
@ -47,9 +47,10 @@
|
|||
%li
|
||||
.header-search{ role: 'search' }
|
||||
= form_tag recherche_dossiers_path, method: :post, class: "form" do
|
||||
= label_tag :dossier_id, "Numéro de dossier", class: 'hidden'
|
||||
= text_field_tag :dossier_id, "", placeholder: "Numéro de dossier"
|
||||
%button{ title: "Rechercher" }
|
||||
= image_tag "icons/search-blue.svg", alt: ''
|
||||
= image_tag "icons/search-blue.svg", alt: 'Rechercher', 'aria-hidden':'true'
|
||||
|
||||
- if instructeur_signed_in? || user_signed_in?
|
||||
%li
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
= form.label champ.main_value_name do
|
||||
#{champ.libelle}
|
||||
- if champ.mandatory?
|
||||
%span.mandatory *
|
||||
|
||||
- if champ.updated_at.present? && seen_at.present?
|
||||
%span.updated-at{ class: highlight_if_unseen_class(seen_at, champ.updated_at) }
|
||||
= "modifié le #{try_format_datetime(champ.updated_at)}"
|
||||
= # we do this trick because some html elements should use 'label' and some should be plain paragraphs
|
||||
- if champ.html_label?
|
||||
= form.label champ.main_value_name do
|
||||
= render partial: 'shared/dossiers/editable_champs/champ_label_content', locals: { champ: champ, seen_at: seen_at }
|
||||
- else
|
||||
%h4.form-label
|
||||
= render partial: 'shared/dossiers/editable_champs/champ_label_content', locals: { champ: champ, seen_at: seen_at }
|
||||
|
||||
- if champ.description.present?
|
||||
.notice{ id: describedby_id(champ) }= string_to_html(champ.description)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#{champ.libelle}
|
||||
- if champ.mandatory?
|
||||
%span.mandatory *
|
||||
|
||||
- if champ.updated_at.present? && seen_at.present?
|
||||
%span.updated-at{ class: highlight_if_unseen_class(seen_at, champ.updated_at) }
|
||||
= "modifié le #{try_format_datetime(champ.updated_at)}"
|
|
@ -1,4 +1,6 @@
|
|||
.radios
|
||||
%fieldset.radios
|
||||
%legend.mandatory-explanation
|
||||
Sélectionnez une des valeurs
|
||||
%label
|
||||
= form.radio_button :value, Individual::GENDER_MALE
|
||||
Monsieur
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
champ.primary_options,
|
||||
{ required: champ.mandatory? },
|
||||
{ data: { secondary_options: champ.secondary_options } }
|
||||
%span
|
||||
= form.label :secondary_value, "Valeur secondaire dépendant de la première", class: 'hidden'
|
||||
= form.select :secondary_value,
|
||||
champ.secondary_options[champ.primary_value],
|
||||
{ required: champ.mandatory? },
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.radios
|
||||
%fieldset.radios
|
||||
%legend.mandatory-explanation
|
||||
Sélectionnez une des deux valeurs
|
||||
%label
|
||||
= form.radio_button :value, true
|
||||
Oui
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
%p.mb-1 Merci de remplir vos informations personnelles pour accéder à la démarche.
|
||||
|
||||
%label
|
||||
%span.form-label
|
||||
%span.mandatory *
|
||||
champs requis
|
||||
|
||||
|
|
49
config/initializers/date_select.rb
Normal file
49
config/initializers/date_select.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
# We monkey patch the DateTimeSelector in order to add accessibility labels
|
||||
# https://stackoverflow.com/a/47836699
|
||||
module ActionView
|
||||
module Helpers
|
||||
class DateTimeSelector
|
||||
# Given an ordering of datetime components, create the selection HTML
|
||||
# and join them with their appropriate separators.
|
||||
def build_selects_from_types(order)
|
||||
select = ""
|
||||
order.reverse_each do |type|
|
||||
separator = separator(type)
|
||||
select.insert(0, separator.to_s + send("select_#{type}").to_s)
|
||||
end
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
select.html_safe
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def datetime_accessibility_label(n, label)
|
||||
prefix_re = @options[:prefix].match('(.*)\[(.*)\]\[(\d+)\]')
|
||||
if prefix_re.nil? || prefix_re.size < 2
|
||||
prefix = []
|
||||
else
|
||||
prefix = prefix_re.to_a.drop(1)
|
||||
end
|
||||
field_for = "#{prefix.join('_')}_#{@options[:field_name]}"
|
||||
|
||||
"<span class='hidden'><label for='#{field_for}_#{n}i'>#{label}</label></span>"
|
||||
end
|
||||
|
||||
# Returns the separator for a given datetime component.
|
||||
def separator(type)
|
||||
return "" if @options[:use_hidden]
|
||||
case type
|
||||
when :year
|
||||
datetime_accessibility_label(1, 'Année')
|
||||
when :month
|
||||
datetime_accessibility_label(2, 'Mois')
|
||||
when :day
|
||||
datetime_accessibility_label(3, 'Jour')
|
||||
when :hour
|
||||
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] + datetime_accessibility_label(4, 'Heure')
|
||||
when :minute, :second
|
||||
@options[:"discard_#{type}"] ? "" : datetime_accessibility_label(5, 'Minute')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,7 +19,7 @@ feature 'The user' do
|
|||
fill_in('text', with: 'super texte')
|
||||
fill_in('textarea', with: 'super textarea')
|
||||
fill_in('date', with: '12-12-2012')
|
||||
select_date_and_time(Time.zone.parse('06/01/1985 7h05'), form_id_for('datetime'))
|
||||
select_date_and_time(Time.zone.parse('06/01/1985 7h05'), form_id_for_datetime('datetime'))
|
||||
fill_in('number', with: '42')
|
||||
check('checkbox')
|
||||
choose('Madame')
|
||||
|
@ -74,7 +74,7 @@ feature 'The user' do
|
|||
expect(page).to have_field('text', with: 'super texte')
|
||||
expect(page).to have_field('textarea', with: 'super textarea')
|
||||
expect(page).to have_field('date', with: '2012-12-12')
|
||||
check_date_and_time(Time.zone.parse('06/01/1985 7:05'), form_id_for('datetime'))
|
||||
check_date_and_time(Time.zone.parse('06/01/1985 7:05'), form_id_for_datetime('datetime'))
|
||||
expect(page).to have_field('number', with: '42')
|
||||
expect(page).to have_checked_field('checkbox')
|
||||
expect(page).to have_checked_field('Madame')
|
||||
|
@ -250,6 +250,32 @@ feature 'The user' do
|
|||
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
|
||||
end
|
||||
|
||||
def form_id_for_datetime(libelle)
|
||||
# The HTML for datetime is a bit specific since it has 5 selects, below here is a sample HTML
|
||||
# So, we want to find the partial id of a datetime (partial because there are 5 ids:
|
||||
# dossier_champs_attributes_3_value_1i, 2i, ... 5i) ; we are interested in the 'dossier_champs_attributes_3_value' part
|
||||
# which is then completed in select_date_and_time and check_date_and_time
|
||||
#
|
||||
# We find the H2, find the first select in the next .datetime div, then strip the last 3 characters
|
||||
#
|
||||
# <h4 class="form-label">
|
||||
# libelle
|
||||
# </h4>
|
||||
# <div class="datetime">
|
||||
# <span class="hidden">
|
||||
# <label for="dossier_champs_attributes_3_value_3i">Jour</label></span>
|
||||
# <select id="dossier_champs_attributes_3_value_3i" name="dossier[champs_attributes][3][value(3i)]">
|
||||
# <option value=""></option>
|
||||
# <option value="1">1</option>
|
||||
# <option value="2">2</option>
|
||||
# <!-- … -->
|
||||
# </select>
|
||||
# <!-- … 4 other selects for month, year, minute and seconds -->
|
||||
# </div>
|
||||
e = find(:xpath, ".//h4[contains(text()[normalize-space()], '#{libelle}')]")
|
||||
e.sibling('.datetime').first('select')[:id][0..-4]
|
||||
end
|
||||
|
||||
def champ_value_for(libelle)
|
||||
champs = user_dossier.champs
|
||||
champs.find { |c| c.libelle == libelle }.value
|
||||
|
|
Loading…
Reference in a new issue