Merge pull request #4762 from betagouv/form-stylesheet
Usager : amélioration de la clarté et de l'accessibilité des formulaires
This commit is contained in:
commit
d5fcaf7073
13 changed files with 152 additions and 66 deletions
|
@ -3,6 +3,7 @@ $small-page-width: 840px;
|
||||||
|
|
||||||
$default-spacer: 8px;
|
$default-spacer: 8px;
|
||||||
$default-padding: 2 * $default-spacer;
|
$default-padding: 2 * $default-spacer;
|
||||||
|
$default-fields-spacer: 5 * $default-spacer;
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
$two-columns-padding: 60px;
|
$two-columns-padding: 60px;
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%outline {
|
%outline {
|
||||||
&:active,
|
&:active:not(:disabled),
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 3px solid $blue;
|
outline: 3px solid $blue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,13 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0;
|
height: 0;
|
||||||
margin-top: $default-padding;
|
margin-top: $default-padding;
|
||||||
margin-bottom: 2 * $default-padding;
|
margin-bottom: $default-fields-spacer;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid $border-grey;
|
border-bottom: 2px solid $border-grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin notice-text-style {
|
@mixin notice-text-style {
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
color: $grey;
|
color: $grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
font-size: 18px;
|
||||||
margin-bottom: $default-padding;
|
margin-bottom: $default-padding;
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -45,7 +46,6 @@
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
@include notice-text-style;
|
@include notice-text-style;
|
||||||
font-weight: bold;
|
|
||||||
margin-top: - $default-spacer;
|
margin-top: - $default-spacer;
|
||||||
margin-bottom: $default-padding;
|
margin-bottom: $default-padding;
|
||||||
|
|
||||||
|
@ -70,32 +70,80 @@
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Align checkboxes on the top-left side of the label
|
// Move checkbox to the top-left side of the label
|
||||||
&.editable-champ-checkbox,
|
&.editable-champ-checkbox,
|
||||||
&.editable-champ-radio.vertical,
|
|
||||||
&.editable-champ-engagement {
|
&.editable-champ-engagement {
|
||||||
p,
|
p,
|
||||||
label {
|
label {
|
||||||
padding-left: 28px;
|
padding-left: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox],
|
input[type=checkbox] {
|
||||||
input[type=radio] {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 3px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When an (eventual) notice is displayed after the input, give it some bottom margin.
|
||||||
|
.notice {
|
||||||
|
margin-bottom: $default-fields-spacer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.radios {
|
.radios {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
// Horizontal layout (default)
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: inline;
|
margin-right: $default-padding;
|
||||||
margin-left: $default-padding;
|
}
|
||||||
|
|
||||||
|
// Vertical layout
|
||||||
|
&.vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding: $default-padding $default-padding $default-padding $default-spacer;
|
||||||
|
border: 1px solid $border-grey;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: normal;
|
||||||
|
background: $white;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: $default-fields-spacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $light-grey;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
border-color: darken($border-grey, 10);
|
||||||
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type=radio] {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
margin: 4px 0 0 27px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +157,7 @@
|
||||||
select,
|
select,
|
||||||
.attachment {
|
.attachment {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 2 * $default-padding;
|
margin-bottom: $default-fields-spacer;
|
||||||
|
|
||||||
&.small-margin {
|
&.small-margin {
|
||||||
margin-bottom: $default-padding / 2;
|
margin-bottom: $default-padding / 2;
|
||||||
|
@ -117,7 +165,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-row {
|
.add-row {
|
||||||
margin-bottom: 2 * $default-padding;
|
margin-bottom: $default-fields-spacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
|
@ -182,16 +230,14 @@
|
||||||
input[type=radio] {
|
input[type=radio] {
|
||||||
@extend %outline;
|
@extend %outline;
|
||||||
|
|
||||||
margin-left: 5px;
|
// Firefox tends to display some controls smaller than other browsers.
|
||||||
margin-right: 4px;
|
|
||||||
margin-bottom: 2 * $default-padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=checkbox] {
|
|
||||||
// Firefox tends to display checkbox controls smaller than other browsers.
|
|
||||||
// Ensure a consistency of size between browsers.
|
// Ensure a consistency of size between browsers.
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: $default-fields-spacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=date] {
|
input[type=date] {
|
||||||
|
@ -234,7 +280,7 @@
|
||||||
|
|
||||||
.select2-container {
|
.select2-container {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 2 * $default-padding;
|
margin-bottom: $default-fields-spacer;
|
||||||
|
|
||||||
&.select2-container--focus {
|
&.select2-container--focus {
|
||||||
.select2-selection {
|
.select2-selection {
|
||||||
|
@ -304,10 +350,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-section {
|
.header-section {
|
||||||
|
display: inline-block;
|
||||||
color: $blue;
|
color: $blue;
|
||||||
font-weight: bold;
|
font-size: 30px;
|
||||||
font-size: 20px;
|
margin-bottom: 3 * $default-padding;
|
||||||
margin-bottom: 2 * $default-padding;
|
border-bottom: 3px solid $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subsection {
|
||||||
|
font-size: 22px;
|
||||||
|
color: $blue;
|
||||||
|
margin-bottom: $default-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
.explication-libelle {
|
.explication-libelle {
|
||||||
|
@ -317,7 +370,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.explication {
|
.explication {
|
||||||
margin-bottom: $default-padding;
|
margin-bottom: $default-fields-spacer;
|
||||||
padding: $default-padding / 2;
|
padding: $default-padding / 2;
|
||||||
background-color: $light-grey;
|
background-color: $light-grey;
|
||||||
|
|
||||||
|
@ -326,6 +379,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.siret-info {
|
||||||
|
margin-top: -$default-fields-spacer;
|
||||||
|
margin-bottom: $default-fields-spacer;
|
||||||
|
// Ensure the bottom-margin is not collapsed when the element is empty
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.send-wrapper {
|
.send-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -388,15 +448,4 @@
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pj-input {
|
|
||||||
input[type=file] {
|
|
||||||
margin: $default-padding 0 (2 * $default-padding);
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.piece-description {
|
|
||||||
margin-bottom: $default-padding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,11 @@ class Champs::HeaderSectionChamp < Champ
|
||||||
def search_terms
|
def search_terms
|
||||||
# The user cannot enter any information here so it doesn’t make much sense to search
|
# The user cannot enter any information here so it doesn’t make much sense to search
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def section_index
|
||||||
|
dossier
|
||||||
|
.champs
|
||||||
|
.filter { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:header_section) }
|
||||||
|
.index(self) + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,5 +7,6 @@
|
||||||
= render partial: "shared/dossiers/editable_champs/editable_champ", locals: { champ: champ, form: form }
|
= render partial: "shared/dossiers/editable_champs/editable_champ", locals: { champ: champ, form: form }
|
||||||
= form.hidden_field :id
|
= form.hidden_field :id
|
||||||
= form.hidden_field :_destroy, disabled: true
|
= form.hidden_field :_destroy, disabled: true
|
||||||
%button.button.danger.remove-row
|
.flex.row-reverse
|
||||||
Supprimer
|
%button.button.danger.remove-row
|
||||||
|
Supprimer l’élément
|
||||||
|
|
|
@ -78,18 +78,17 @@
|
||||||
|
|
||||||
- if !@procedure.locked?
|
- if !@procedure.locked?
|
||||||
%h2.header-section À qui s’adresse ma démarche ?
|
%h2.header-section À qui s’adresse ma démarche ?
|
||||||
.editable-champ.editable-champ-radio.vertical
|
.radios.vertical
|
||||||
= f.label :for_individual, value: true do
|
= f.label :for_individual, value: true do
|
||||||
|
= f.radio_button :for_individual, true
|
||||||
Ma démarche s’adresse à un particulier
|
Ma démarche s’adresse à un particulier
|
||||||
%p.notice En choisissant cette option, l’usager devra renseigner son nom et prénom avant d’accéder au formulaire
|
%p.notice En choisissant cette option, l’usager devra renseigner son nom et prénom avant d’accéder au formulaire
|
||||||
= f.radio_button :for_individual, true
|
|
||||||
|
|
||||||
.editable-champ.editable-champ-radio.vertical
|
|
||||||
= f.label :for_individual, value: false do
|
= f.label :for_individual, value: false do
|
||||||
|
= f.radio_button :for_individual, false
|
||||||
Ma démarche s’adresse à une personne morale
|
Ma démarche s’adresse à une personne morale
|
||||||
%p.notice
|
%p.notice
|
||||||
En choisissant cette option, l’usager devra renseigner son n° SIRET.<br>Grâce à l’API Entreprise, les informations sur la personne morale (raison sociale, adresse du siège, etc.) seront automatiquement renseignées.
|
En choisissant cette option, l’usager devra renseigner son n° SIRET.<br>Grâce à l’API Entreprise, les informations sur la personne morale (raison sociale, adresse du siège, etc.) seront automatiquement renseignées.
|
||||||
= f.radio_button :for_individual, false
|
|
||||||
|
|
||||||
%p.explication
|
%p.explication
|
||||||
Si votre démarche s’adresse indifféremment à une personne morale ou un particulier, choisissez l'option « Particuliers ». Vous pourrez ajouter un champ SIRET directement dans le formulaire.
|
Si votre démarche s’adresse indifféremment à une personne morale ou un particulier, choisissez l'option « Particuliers ». Vous pourrez ajouter un champ SIRET directement dans le formulaire.
|
||||||
|
|
|
@ -52,16 +52,16 @@
|
||||||
%label Mot de passe
|
%label Mot de passe
|
||||||
%input{ type: "password", value: "12345678" }
|
%input{ type: "password", value: "12345678" }
|
||||||
|
|
||||||
%h2.header-section Bouton radio verticaux
|
%h3.header-subsection Bouton radio verticaux
|
||||||
.editable-champ.editable-champ-radio.vertical
|
.radios.vertical
|
||||||
= f.label :archived, 'Option A', value: true
|
= f.label :archived, value: true do
|
||||||
%p.notice Une option tout à fait valable.
|
= f.radio_button :archived, true
|
||||||
= f.radio_button :archived, true
|
Option A
|
||||||
|
%p.notice Une option tout à fait valable.
|
||||||
.editable-champ.editable-champ-radio.vertical
|
= f.label :archived, value: false do
|
||||||
= f.label :archived, 'Option B', value: false
|
= f.radio_button :archived, false
|
||||||
%p.notice Une autre option, pas mal non plus.
|
Option B
|
||||||
= f.radio_button :archived, false
|
%p.notice Une autre option, pas mal non plus.
|
||||||
|
|
||||||
.send-wrapper
|
.send-wrapper
|
||||||
= f.submit 'Enregistrer un brouillon (formnovalidate)', formnovalidate: true, class: 'button send'
|
= f.submit 'Enregistrer un brouillon (formnovalidate)', formnovalidate: true, class: 'button send'
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
.editable-champ{ class: "editable-champ-#{champ.type_champ}" }
|
.editable-champ{ class: "editable-champ-#{champ.type_champ}" }
|
||||||
- if champ.repetition?
|
- if champ.repetition?
|
||||||
= render partial: 'shared/dossiers/editable_champs/header_section', locals: { champ: champ }
|
%h3.header-subsection= champ.libelle
|
||||||
|
|
||||||
- if champ.description.present?
|
- if champ.description.present?
|
||||||
%p.notice= string_to_html(champ.description, false)
|
%p.notice= string_to_html(champ.description, false)
|
||||||
|
|
||||||
- elsif has_label?(champ)
|
- elsif has_label?(champ)
|
||||||
= render partial: 'shared/dossiers/editable_champs/champ_label', locals: { form: form, champ: champ, seen_at: defined?(seen_at) ? seen_at : nil }
|
= render partial: 'shared/dossiers/editable_champs/champ_label', locals: { form: form, champ: champ, seen_at: defined?(seen_at) ? seen_at : nil }
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
|
- section_index = champ.section_index
|
||||||
|
|
||||||
%h2.header-section
|
%h2.header-section
|
||||||
|
- if section_index
|
||||||
|
= "#{section_index}."
|
||||||
= champ.libelle
|
= champ.libelle
|
||||||
|
|
|
@ -8,16 +8,16 @@
|
||||||
.flex.row-reverse
|
.flex.row-reverse
|
||||||
- if champ.persisted?
|
- if champ.persisted?
|
||||||
%button.button.danger.remove-row{ type: :button }
|
%button.button.danger.remove-row{ type: :button }
|
||||||
Supprimer
|
Supprimer l’élément
|
||||||
- else
|
- else
|
||||||
%button.button.danger{ type: :button }
|
%button.button.danger{ type: :button }
|
||||||
Supprimer
|
Supprimer l’élément
|
||||||
|
|
||||||
- if champ.persisted?
|
- if champ.persisted?
|
||||||
= link_to champs_repetition_path(form.index), class: 'button add-row', data: { remote: true, method: 'POST', params: { champ_id: champ&.id }.to_query } do
|
= link_to champs_repetition_path(form.index), class: 'button add-row', data: { remote: true, method: 'POST', params: { champ_id: champ&.id }.to_query } do
|
||||||
%span.icon.add
|
%span.icon.add
|
||||||
Ajouter une ligne pour « #{champ.libelle} »
|
Ajouter un élément pour « #{champ.libelle} »
|
||||||
- else
|
- else
|
||||||
%a.button.add-row
|
%a.button.add-row
|
||||||
%span.icon.add
|
%span.icon.add
|
||||||
Ajouter une ligne pour « #{champ.libelle} »
|
Ajouter un élément pour « #{champ.libelle} »
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
= form.text_field :value,
|
= form.text_field :value,
|
||||||
placeholder: champ.libelle,
|
placeholder: champ.libelle,
|
||||||
class: 'small-margin',
|
|
||||||
data: { remote: true, debounce: true, url: champs_siret_path(form.index), params: { champ_id: champ&.id }.to_query, spinner: true },
|
data: { remote: true, debounce: true, url: champs_siret_path(form.index), params: { champ_id: champ&.id }.to_query, spinner: true },
|
||||||
required: champ.mandatory?,
|
required: champ.mandatory?,
|
||||||
pattern: "[0-9]{14}",
|
pattern: "[0-9]{14}",
|
||||||
title: "Le numéro de SIRET doit comporter exactement 14 chiffres"
|
title: "Le numéro de SIRET doit comporter exactement 14 chiffres"
|
||||||
.spinner.right.hidden
|
.spinner.right.hidden
|
||||||
%div{ class: "siret-info-#{form.index}" }
|
.siret-info{ class: "siret-info-#{form.index}" }
|
||||||
- if champ.etablissement.present?
|
- if champ.etablissement.present?
|
||||||
= render partial: 'shared/dossiers/editable_champs/etablissement_titre', locals: { etablissement: champ.etablissement }
|
= render partial: 'shared/dossiers/editable_champs/etablissement_titre', locals: { etablissement: champ.etablissement }
|
||||||
|
|
|
@ -107,7 +107,7 @@ feature 'The user' do
|
||||||
fill_in('text', with: 'super texte')
|
fill_in('text', with: 'super texte')
|
||||||
expect(page).to have_field('text', with: 'super texte')
|
expect(page).to have_field('text', with: 'super texte')
|
||||||
|
|
||||||
click_on 'Ajouter une ligne pour'
|
click_on 'Ajouter un élément pour'
|
||||||
|
|
||||||
within '.row-1' do
|
within '.row-1' do
|
||||||
fill_in('text', with: 'un autre texte')
|
fill_in('text', with: 'un autre texte')
|
||||||
|
@ -120,7 +120,7 @@ feature 'The user' do
|
||||||
expect(page).to have_content('Supprimer', count: 2)
|
expect(page).to have_content('Supprimer', count: 2)
|
||||||
|
|
||||||
within '.row-1' do
|
within '.row-1' do
|
||||||
click_on 'Supprimer'
|
click_on 'Supprimer l’élément'
|
||||||
end
|
end
|
||||||
|
|
||||||
click_on 'Enregistrer le brouillon'
|
click_on 'Enregistrer le brouillon'
|
||||||
|
|
26
spec/models/champs/header_section_champ_spec.rb
Normal file
26
spec/models/champs/header_section_champ_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Champs::CheckboxChamp do
|
||||||
|
let(:types_de_champ) do
|
||||||
|
[
|
||||||
|
create(:type_de_champ_header_section),
|
||||||
|
create(:type_de_champ_civilite),
|
||||||
|
create(:type_de_champ_text),
|
||||||
|
create(:type_de_champ_header_section),
|
||||||
|
create(:type_de_champ_email)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:procedure) { create(:procedure, types_de_champ: types_de_champ) }
|
||||||
|
let(:dossier) { create(:dossier, procedure: procedure) }
|
||||||
|
|
||||||
|
describe '#section_index' do
|
||||||
|
let(:first_header) { dossier.champs[0] }
|
||||||
|
let(:second_header) { dossier.champs[3] }
|
||||||
|
|
||||||
|
it 'returns the index of the section (starting from 1)' do
|
||||||
|
expect(first_header.section_index).to eq 1
|
||||||
|
expect(second_header.section_index).to eq 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue