Merge pull request #9258 from demarches-simplifiees/DSFR-champs

Passe les champs au DSFR
This commit is contained in:
mfo 2023-09-04 09:11:09 +00:00 committed by GitHub
commit 08bb62d417
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 432 additions and 161 deletions

View file

@ -1 +1 @@
16.14.0
18.17.0

View file

@ -316,13 +316,12 @@ ul.dropdown-items {
margin-bottom: 2 * $default-spacer;
}
input:not(.fr-btn),
select {
input:not(.fr-btn) {
width: 200px;
display: inline-block;
background-color: $light-grey;
border: 1px solid $border-grey;
}
}
[disabled] {
display: none;

View file

@ -99,6 +99,21 @@ fieldset {
width: max-content;
}
button.fr-tag-bug {
background-color: $blue-france-500;
color: #FFFFFF;
&:hover {
background-color: #1212FF;
color: #FFFFFF;
}
.tag-dismiss {
font-size: 1rem;
margin-left: 10px;
}
}
// on veut ferrer à droite le dropdown de sélecteur de langue
@media (min-width: 62em) {
.fr-nav__item.custom-fr-translate-flex-end {

View file

@ -20,7 +20,8 @@
font-style: italic;
}
.fr-input-group {
.fr-input-group,
.fr-select-group {
margin-bottom: 1rem;
}
@ -125,7 +126,6 @@
&.editable-champ-checkbox {
label {
padding-left: 28px;
font-weight: normal;
}
@ -210,18 +210,17 @@
}
}
.fr-label {
.fr-label .fr-hint-text > *,
.fr-fieldset__legend .fr-hint-text > * {
// la description d'un champ peut contenir du markup (markdown->html),
// on herite donc la fontsize/mrgin/padding du fr-hint-text
.fr-hint-text > * {
font-size: inherit;
margin: inherit;
padding: inherit;
}
font-size: inherit;
margin: inherit;
padding: inherit;
}
input[type=password],
select {
select:not(.fr-select) {
display: block;
margin-bottom: 0;
@ -246,9 +245,7 @@
}
}
input[type=text]:not([data-address='true']),
select {
border-radius: 4px;
input[type=text]:not([data-address='true']) {
border: solid 1px $border-grey;
padding: $default-padding;

View file

@ -17,6 +17,10 @@ class Dsfr::InputComponent < ApplicationComponent
@required = required
end
def dsfr_champ_container
:div
end
# add invalid class on input when input is invalid
# and and valid on input only if another input is invalid
def input_group_opts
@ -50,6 +54,10 @@ class Dsfr::InputComponent < ApplicationComponent
get_slot(:label).presence || default_label
end
def dsfr_input_classname
'fr-input'
end
# kind of input helpers
def password?
@input_type == :password_field

View file

@ -8,6 +8,30 @@ module Dsfr
renders_one :hint
def dsfr_group_classname
if dsfr_champ_container == :fieldset
'fr-fieldset'
else
"#{dsfr_input_classname}-group"
end
end
def input_group_error_class_names
{
"#{dsfr_group_classname}--error" => errors_on_attribute?,
"#{dsfr_group_classname}--valid" => !errors_on_attribute? && errors_on_another_attribute?
}
end
def errors_on_attribute?
errors.has_key?(attribute_or_rich_body)
end
# errors helpers
def error_full_messages
errors.full_messages_for(attribute_or_rich_body)
end
private
# lookup for edge case from `form.rich_text_area`
@ -21,15 +45,14 @@ module Dsfr
end
end
def input_group_error_class_names
{
"fr-input-group--error": errors_on_attribute?,
"fr-input-group--valid": !errors_on_attribute? && errors_on_another_attribute?
}
def fr_fieldset?
!['fr-input', 'fr-radio', 'fr-select'].include?(dsfr_input_classname)
end
def input_error_class_names
{ 'fr-input--error': errors_on_attribute? }
{
"#{dsfr_input_classname}--error": errors_on_attribute?
}
end
def input_error_opts
@ -70,23 +93,10 @@ module Dsfr
@opts
end
def describedby_id
dom_id(@champ, :error_full_messages)
end
def errors_on_another_attribute?
!errors.empty?
end
def errors_on_attribute?
errors.has_key?(attribute_or_rich_body)
end
# errors helpers
def error_full_messages
errors.full_messages_for(attribute_or_rich_body)
end
def map_array_to_hash_with_true(array_or_string_or_nil)
Array(array_or_string_or_nil).to_h { [_1, true] }
end

View file

@ -0,0 +1,13 @@
module Dsfr
class InputStatusMessageComponent < ApplicationComponent
def initialize(errors_on_attribute:, error_full_messages:, described_by:)
@errors_on_attribute = errors_on_attribute
@error_full_messages = error_full_messages
@described_by = described_by
end
def render?
@errors_on_attribute
end
end
end

View file

@ -0,0 +1,3 @@
.fr-messages-group{ id: @describedby_id }
- @error_full_messages.each do |error_message|
%p{ class: class_names('fr-message' => true, "fr-message--#{@errors_on_attribute ? 'error' : 'valid'}" => true) }= error_message

View file

@ -1,2 +1,5 @@
class EditableChamp::AnnuaireEducationComponent < EditableChamp::ComboSearchComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,9 +1,12 @@
= # 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, id: @champ.labelledby_id, for: @champ.input_id, class: 'fr-label' do
- render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at
- elsif @champ.legend_label?
%legend.fr-fieldset__legend.fr-text--regular{ id: @champ.labelledby_id }= render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at
- elsif @champ.single_checkbox?
-# no label to add
- else
.fr-label.mb-4{ id: @champ.labelledby_id }
= render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at

View file

@ -1,2 +1,9 @@
class EditableChamp::CheckboxComponent < EditableChamp::EditableChampBaseComponent
def dsfr_champ_container
:fieldset
end
def dsfr_input_classname
'fr-radio'
end
end

View file

@ -1,4 +1,9 @@
= @form.check_box :value,
{ required: @champ.required?, id: @champ.input_id, checked: @champ.true?, aria: { describedby: @champ.describedby_id }, class: class_names('required' => @champ.required?)},
'true',
'false'
.fr-fieldset__element
.fr-checkbox-group
= @form.check_box :value,
{ required: @champ.required?, id: @champ.input_id, checked: @champ.true?, aria: { describedby: @champ.describedby_id }, class: class_names('required' => @champ.required?)},
'true',
'false'
%label.fr-label{ for: @champ.input_id, id: @champ.labelledby_id }
= render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at

View file

@ -1,6 +1,10 @@
class EditableChamp::CommunesComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_champ_container
:fieldset
end
private
def commune_options

View file

@ -1,4 +1,5 @@
---
en:
postal_code: Enter the postal code then select the municipality from the list
postal_code: "Enter <strong>the postal code</strong>"
commune: "Select <strong>the municipality</strong> from the list"
not_found: No municipality found for postal code %{postal_code}. Please check that you haven't made any mistakes.

View file

@ -1,4 +1,5 @@
---
fr:
postal_code: Renseignez le code postal puis sélectionnez la commune dans la liste
postal_code: "Renseignez le <strong>code postal</strong>"
commune: "Sélectionnez la commune dans la liste"
not_found: Aucune commune trouvée pour le code postal %{postal_code}. Verifiez que vous n'avez pas fait derreur.

View file

@ -1,13 +1,23 @@
%label.notice{ for: code_postal_input_id }= t('.postal_code')
= @form.text_field :code_postal, required: @champ.required?, id: code_postal_input_id, class: "width-33-desktop width-100-mobile small-margin"
- if @champ.code_postal?
.fr-fieldset__element.fr-mb-0
.fr-input-group
= @form.label :code_postal, t('.postal_code').html_safe, class: 'fr-label', for: code_postal_input_id
= @form.text_field :code_postal, required: @champ.required?, id: code_postal_input_id, class: "width-33-desktop width-100-mobile small-margin fr-input"
- if @champ.code_postal?
- if commune_options.empty?
.fr-error-text.mb-4= t('.not_found', postal_code: @champ.code_postal)
.fr-fieldset__element.fr-mb-0
- if commune_options.empty?
.fr-error-text.mb-4= t('.not_found', postal_code: @champ.code_postal)
-# noop
- elsif commune_options.size <= 3
%fieldset.radios
%fieldset.fr-fieldset
.fr-fieldset__legend--regular.fr-fieldset__legend= t('.commune').html_safe
- commune_options.each.with_index do |(option, value), index|
%label
= @form.radio_button :value, value, checked: @champ.selected == value, id: index == 0 ? @champ.input_id : nil
= option
.fr-fieldset__element
.fr-radio-group
= @form.radio_button :value, value, checked: @champ.selected == value, id: index == 0 ? @champ.input_id : "radio-#{index}-#{value.parameterize}"
= @form.label :value, option, for: index == 0 ? @champ.input_id : "radio-#{index}-#{value.parameterize}", class: 'fr-label'
- else
= @form.select :value, commune_options, commune_select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"
= @form.label :value, t('.commune').html_safe, for: @champ.input_id, class: 'fr-label'
= @form.select :value, commune_options, commune_select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile fr-select"

View file

@ -1,2 +1,5 @@
class EditableChamp::DateComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::DatetimeComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def formatted_value_for_datetime_locale
if @champ.valid? && @champ.value.present?
# convert to a format that the datetime-local input can understand

View file

@ -1,2 +1,5 @@
class EditableChamp::DecimalNumberComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -3,6 +3,10 @@ class EditableChamp::DepartementsComponent < EditableChamp::EditableChampBaseCom
private
def dsfr_input_classname
'fr-select'
end
def options
APIGeoService.departements.map { ["#{_1[:code]} #{_1[:name]}", _1[:code]] }
end

View file

@ -1 +1 @@
= @form.select :value, options, select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"
= @form.select :value, options, select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "fr-select"

View file

@ -1,2 +1,5 @@
class EditableChamp::DgfipComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::DossierLinkComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def dossier
@dossier ||= @champ.blank? ? nil : Dossier.visible_by_administration.find_by(id: @champ.to_s)
end

View file

@ -1,6 +1,14 @@
class EditableChamp::DropDownListComponent < EditableChamp::EditableChampBaseComponent
def select_class_names
class_names('width-100': contains_long_option?)
class_names('width-100': contains_long_option?, 'fr-select': true)
end
def dsfr_input_classname
'fr-select'
end
def dsfr_champ_container
@champ.render_as_radios? ? :fieldset : :div
end
def contains_long_option?

View file

@ -1,20 +1,23 @@
- if @champ.options?
- if @champ.render_as_radios?
%fieldset.radios
- @champ.enabled_non_empty_options.each do |option|
%label
= @form.radio_button :value, option
= option
.fr-fieldset__content
- @champ.enabled_non_empty_options.each_with_index do |option, index|
.fr-radio-group
= @form.radio_button :value, option, id: "#{@champ.id}_radio_option_#{index}"
%label.fr-label{ for: "#{@champ.id}_radio_option_#{index}" }
= option
- if !@champ.mandatory?
%label.blank-radio
= @form.radio_button :value, '', checked: @champ.value.blank? && !@champ.other?
Non renseigné
.fr-radio-group
= @form.radio_button :value, '', checked: @champ.value.blank? && !@champ.other?, id: "#{@champ.id}_radio_option_blank"
%label.fr-label{ for: "#{@champ.id}_radio_option_blank" }
Non renseigné
- if @champ.drop_down_other?
%label
= @form.radio_button :value, Champs::DropDownListChamp::OTHER, checked: @champ.other?
Autre
.fr-radio-group
= @form.radio_button :value, Champs::DropDownListChamp::OTHER, checked: @champ.other?, id: "#{@champ.id}_radio_option_other"
%label.fr-label{ for: "#{@champ.id}_radio_option_other" }
Autre
- else
= @form.select :value, @champ.options_without_empty_value_when_mandatory(@champ.options), { selected: @champ.selected }, required: @champ.required?, id: @champ.input_id, class: select_class_names, aria: { describedby: @champ.describedby_id }

View file

@ -1,4 +1,5 @@
.drop_down_other
.notice
%label{ for: dom_id(@champ, :value_other) } Veuillez saisir votre autre choix
= @form.text_field :value_other, maxlength: 200, size: nil, id: dom_id(@champ, :value_other)
.drop_down_other.fr-mt-2w
.fr-input-group
%label.fr-label{ for: dom_id(@champ, :value_other) } Veuillez saisir votre autre choix
= @form.text_field :value_other, maxlength: 200, size: nil, id: dom_id(@champ, :value_other), class: 'fr-input'

View file

@ -1,6 +1,18 @@
class EditableChamp::EditableChampBaseComponent < ApplicationComponent
include Dsfr::InputErrorable
def dsfr_champ_container
:div
end
def dsfr_input_classname
nil
end
def describedby_id
@champ.describedby_id
end
def initialize(form:, champ:, seen_at: nil, opts: {})
@form, @champ, @seen_at, @opts = form, champ, seen_at, opts
@attribute = :value

View file

@ -1,18 +1,21 @@
class EditableChamp::EditableChampComponent < ApplicationComponent
include Dsfr::InputErrorable
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
@attribute = :value
end
def champ_component
@champ_component ||= component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
end
private
def has_label?(champ)
types_without_label = [
TypeDeChamp.type_champs.fetch(:header_section),
TypeDeChamp.type_champs.fetch(:explication),
TypeDeChamp.type_champs.fetch(:repetition)
TypeDeChamp.type_champs.fetch(:repetition),
TypeDeChamp.type_champs.fetch(:linked_drop_down_list)
]
!types_without_label.include?(@champ.type_champ)
end
@ -28,8 +31,8 @@ class EditableChamp::EditableChampComponent < ApplicationComponent
'editable-champ': true,
"editable-champ-#{@champ.type_champ}": true,
"hidden": !@champ.visible?,
"fr-input-group": true
}.merge(input_group_error_class_names)
champ_component.dsfr_group_classname => true
}.merge(champ_component.input_group_error_class_names)
),
id: @champ.input_group_id,
data: { controller: stimulus_controller, **data_dependent_conditions, **stimulus_values }

View file

@ -1,15 +1,9 @@
= content_tag(:div, html_options) do
= content_tag((champ_component.dsfr_champ_container), html_options) do
- if has_label?(@champ)
= render EditableChamp::ChampLabelComponent.new form: @form, champ: @champ, seen_at: @seen_at
= render component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
- if errors_on_attribute?
- if error_full_messages.size == 1
%p.fr-error-text{ id: describedby_id }= error_full_messages.first
- else
.fr-error-text{ id: describedby_id }
%ul.list-style-type-none.fr-pl-0
- error_full_messages.each do |error_message|
%li= error_message
= render champ_component
= render Dsfr::InputStatusMessageComponent.new(errors_on_attribute: champ_component.errors_on_attribute?, error_full_messages: champ_component.error_full_messages, described_by: @champ.describedby_id)
= @form.hidden_field :id, value: @champ.id

View file

@ -1,4 +1,8 @@
class EditableChamp::EmailComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def email?
true
end

View file

@ -1,6 +1,10 @@
class EditableChamp::EpciComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_champ_container
:fieldset
end
private
def departement_options

View file

@ -1,5 +1,11 @@
%label.notice{ for: @champ.code_departement_input_id } Le département de lEPCI
= @form.select :code_departement, departement_options, departement_select_options, required: @champ.required?, id: @champ.code_departement_input_id, class: "width-33-desktop width-100-mobile"
.fr-fieldset__element
= @form.label :code_departement, for: @champ.code_departement_input_id, class: 'fr-label' do
- "Le département de lEPCI"
= @form.select :code_departement, departement_options, departement_select_options, required: @champ.required?, id: @champ.code_departement_input_id, class: "width-33-desktop width-100-mobile fr-select"
- if @champ.departement?
= @form.label "EPCI", for: @champ.epci_input_id
= @form.select :value, epci_options, epci_select_options, required: @champ.required?, id: @champ.epci_input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"
.fr-fieldset__element
.fr-select-group
= @form.label :value, for: @champ.epci_input_id, class: 'fr-label' do
- "EPCI"
= @form.select :value, epci_options, epci_select_options, required: @champ.required?, id: @champ.epci_input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile fr-select"

View file

@ -1,2 +1,5 @@
class EditableChamp::IbanComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,2 +1,5 @@
class EditableChamp::IntegerNumberComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::LinkedDropDownListComponent < EditableChamp::EditableChampBaseComponent
def dsfr_champ_container
:fieldset
end
private
def secondary_label

View file

@ -1,12 +1,19 @@
- if @champ.options?
= @form.select :primary_value, @champ.primary_options, {}, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }
.fr-fieldset__element.fr-mb-0
.fr-select-group
= render EditableChamp::ChampLabelComponent.new form: @form, champ: @champ, seen_at: @seen_at
= @form.select :primary_value, @champ.primary_options, {}, required: @champ.required?, class: 'fr-select fr-mb-3v', id: @champ.input_id, aria: { describedby: @champ.describedby_id }
- if @champ.has_secondary_options_for_primary?
.secondary
= @form.label :secondary_value, for: "#{@champ.input_id}-secondary" do
- sanitize(secondary_label)
- if @champ.drop_down_secondary_description.present?
.notice{ id: "#{@champ.describedby_id}-secondary" }
= render SimpleFormatComponent.new(@champ.drop_down_secondary_description, allow_a: true)
= @form.select :secondary_value, @champ.secondary_options[@champ.primary_value], {}, required: @champ.required?, id: "#{@champ.input_id}-secondary", aria: { describedby: "#{@champ.describedby_id}-secondary" }
.fr-fieldset__element
.fr-select-group
= @form.label :secondary_value, for: "#{@champ.input_id}-secondary", class: 'fr-label' do
- sanitize(secondary_label)
- if @champ.drop_down_secondary_description.present?
.notice{ id: "#{@champ.describedby_id}-secondary" }
= render SimpleFormatComponent.new(@champ.drop_down_secondary_description, allow_a: true)
= @form.select :secondary_value, @champ.secondary_options[@champ.primary_value], {}, required: @champ.required?, class: 'fr-select', id: "#{@champ.input_id}-secondary", aria: { describedby: "#{@champ.describedby_id}-secondary" }
- else
= @form.hidden_field :secondary_value, value: ''

View file

@ -1,2 +1,5 @@
class EditableChamp::MesriComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,3 +1,11 @@
class EditableChamp::MultipleDropDownListComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_input_classname
'fr-select'
end
def dsfr_champ_container
@champ.render_as_checkboxes? ? :fieldset : :div
end
end

View file

@ -1,15 +1,19 @@
- if @champ.options?
- if @champ.render_as_checkboxes?
= @form.collection_check_boxes(:value, @champ.enabled_non_empty_options, :to_s, :to_s) do |b|
- tag.div(class: 'editable-champ editable-champ-checkbox') do
- b.label(for: @champ.checkbox_id(b.value)) do
- b.check_box({ multiple: true, checked: @champ.selected_options.include?(b.value), aria: { describedby: @champ.describedby_id }, id: @champ.checkbox_id(b.value) }) + b.text
= @form.collection_check_boxes :value, @champ.enabled_non_empty_options, :to_s, :to_s do |b|
- capture do
.fr-fieldset__element
.fr-checkbox-group
= b.check_box(checked: @champ.selected_options.include?(b.value), aria: { describedby: @champ.describedby_id }, id: @champ.checkbox_id(b.value), class: 'fr-checkbox-group__checkbox')
%label.fr-label{ for: @champ.checkbox_id(b.value) }
= b.text
- else
%div{ 'data-turbo-focus-group': true }
- if @champ.selected_options.present?
.fr-mb-2w
.fr-mb-2w{ "data-turbo": "true" }
- @champ.selected_options.each do |option|
= render NestedForms::OwnedButtonComponent.new(formaction: champs_options_path(@champ.id, option:), http_method: :delete, opt: { class: 'fr-tag fr-tag--dismiss fr-mb-1w fr-mr-1w', id: @champ.checkbox_id(option) }) do
= render NestedForms::OwnedButtonComponent.new(formaction: champs_options_path(@champ.id, option:), http_method: :delete, opt: { aria: {pressed: true }, class: 'fr-tag fr-tag-bug fr-mb-1w fr-mr-1w', id: @champ.checkbox_id(option) }) do
= option
- if @champ.unselected_options.present?
= @form.select :value, @champ.unselected_options, { selected: '', include_blank: '' }, id: @champ.input_id, aria: { describedby: @champ.describedby_id }
= @form.select :value, @champ.unselected_options, { selected: '', include_blank: '' }, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: 'fr-select fr-mt-2v'

View file

@ -1,2 +1,5 @@
class EditableChamp::NumberComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,6 +1,10 @@
class EditableChamp::PaysComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_input_classname
'fr-select'
end
private
def options

View file

@ -1 +1 @@
= @form.select :value, options, select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"
= @form.select :value, options, select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile fr-select"

View file

@ -1,2 +1,5 @@
class EditableChamp::PhoneComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::PieceJustificativeComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def view_as
if @champ.dossier.brouillon?
:link

View file

@ -1,6 +1,10 @@
class EditableChamp::RegionsComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
def dsfr_input_classname
'fr-select'
end
private
def options

View file

@ -1 +1 @@
= @form.select :value, options, select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"
= @form.select :value, options, select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile fr-select"

View file

@ -1,2 +1,5 @@
class EditableChamp::RNAComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,2 +1,5 @@
class EditableChamp::SiretComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,2 +1,5 @@
class EditableChamp::TextComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,3 +1,6 @@
class EditableChamp::TextareaComponent < EditableChamp::EditableChampBaseComponent
include HtmlToStringHelper
def dsfr_input_classname
'fr-input'
end
end

View file

@ -1,4 +1,8 @@
class EditableChamp::TitreIdentiteComponent < EditableChamp::EditableChampBaseComponent
def dsfr_input_classname
'fr-input'
end
def user_can_destroy?
!@champ.mandatory? || @champ.dossier.brouillon?
end

View file

@ -1,2 +1,5 @@
class EditableChamp::YesNoComponent < EditableChamp::EditableChampBaseComponent
def dsfr_champ_container
:fieldset
end
end

View file

@ -1,11 +1,11 @@
%fieldset.fr-fieldset
%legend.fr-fieldset__legend.visually-hidden
= t(".legend")
%label{ for: @champ.yes_input_id }
.fr-fieldset__element.fr-fieldset__element--inline
.fr-radio-group
= @form.radio_button :value, true, id: @champ.yes_input_id
= t(".yes")
%label{ for: @champ.no_input_id }
%label.fr-label{ for: @champ.yes_input_id }
= t(".yes")
.fr-fieldset__element.fr-fieldset__element--inline
.fr-radio-group
= @form.radio_button :value, false, id: @champ.no_input_id
= t(".no")
%label.fr-label{ for: @champ.no_input_id }
= t(".no")

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to admin_procedure_administrateurs_path(@procedure), id: 'administrateurs', class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
%div
%span.icon.accept
%p.fr-tile-status-accept Validé

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to annotations_admin_procedure_path(@procedure), class: 'fr-tile fr-enlarge-link', title: error_messages do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if error_messages.present?
%div
%span.icon.refuse

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to jeton_admin_procedure_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.api_entreprise_token.present?
%div
%span.icon.accept

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to admin_procedure_api_particulier_path(@procedure), class: 'fr-tile fr-enlarge-link', id: 'api-particulier' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.api_particulier_token.present?
%div
%span.icon.accept

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to edit_admin_procedure_attestation_template_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.attestation_template&.activated?
%div
- if error_messages.present?

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to champs_admin_procedure_path(@procedure), class: 'fr-tile fr-enlarge-link', title: error_messages do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if error_messages.present?
%div
%span.icon.refuse

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to edit_admin_procedure_dossier_submitted_message_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.active_dossier_submitted_message.present?
%div
%span.icon.accept

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to admin_procedure_mail_templates_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
%div
- if error_messages.present?
%span.icon.refuse

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to admin_procedure_experts_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
%div
%span.icon.preview
%p.fr-tile-status-todo À configurer

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to admin_procedure_groupe_instructeurs_path(@procedure), id: 'groupe-instructeurs', class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.routing_enabled? || @procedure.instructeurs.present?
%div
%span.icon.accept

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to modifications_admin_procedure_path(@procedure), id: 'modifications', class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
%div
%span.icon.accept
%p.fr-tile-status-accept Activée

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to monavis_admin_procedure_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.monavis_embed.present?
%div
%span.icon.accept

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to edit_admin_procedure_path(@procedure), id: 'presentation', class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
%div
%span.icon.accept
%p.fr-tile-status-accept Validé

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to service_link, class: 'fr-tile fr-enlarge-link', id: 'service' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.service_id.present?
%div
%span.icon.accept

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to edit_admin_procedure_sva_svr_path(@procedure), class: 'fr-tile fr-enlarge-link', id: 'sva' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.sva_svr_enabled?
%div
%span.icon.accept

View file

@ -1,6 +1,6 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to zones_admin_procedure_path(@procedure), id: 'zones', class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.justify-between
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.zones.size >= 1
%div
%span.icon.accept

View file

@ -32,6 +32,7 @@
.cell.flex.justify-start.column.flex-grow
= form.label :type_champ, "Type de champ", for: dom_id(type_de_champ, :type_champ)
= form.select :type_champ, grouped_options_for_select(types_of_type_de_champ, type_de_champ.type_champ), {}, class: 'fr-select small-margin small inline width-100', id: dom_id(type_de_champ, :type_champ), disabled: coordinate.used_by_routing_rules?
.flex.column.justify-start.flex-grow
.cell
.flex.align-center

View file

@ -160,6 +160,14 @@ class Champ < ApplicationRecord
true
end
def legend_label?
false
end
def single_checkbox?
false
end
def input_group_id
"champ-#{html_id}"
end

View file

@ -7,11 +7,23 @@ class Champs::CheckboxChamp < Champs::BooleanChamp
mandatory? && (blank? || !true?)
end
def legend_label?
false
end
# TODO remove when normalize_checkbox_values is over
def true?
value_with_legacy == TRUE_VALUE
end
def html_label?
false
end
def single_checkbox?
true
end
private
# TODO remove when normalize_checkbox_values is over

View file

@ -88,6 +88,14 @@ class Champs::CommuneChamp < Champs::TextChamp
end
end
def html_label?
false
end
def legend_label?
true
end
private
def safe_to_s

View file

@ -22,6 +22,14 @@ class Champs::DropDownListChamp < Champ
end
end
def html_label?
!render_as_radios?
end
def legend_label?
render_as_radios?
end
def selected
other? ? OTHER : value
end

View file

@ -22,6 +22,14 @@ class Champs::EpciChamp < Champs::TextChamp
code_departement.present?
end
def html_label?
false
end
def legend_label?
true
end
def code?
code.present?
end

View file

@ -43,6 +43,18 @@ class Champs::MultipleDropDownListChamp < Champ
enabled_non_empty_options.size <= THRESHOLD_NB_OPTIONS_AS_CHECKBOX
end
def html_label?
!render_as_checkboxes?
end
def legend_label?
true
end
def single_checkbox?
render_as_checkboxes?
end
def blank?
selected_options.blank?
end

View file

@ -1,4 +1,12 @@
class Champs::YesNoChamp < Champs::BooleanChamp
def legend_label?
true
end
def html_label?
false
end
def yes_input_id
"#{input_id}-yes"
end

View file

@ -51,7 +51,7 @@
= render partial: 'shared/help/help_dropdown_instructeur'
- else
// NB: on mobile in order to have links correctly aligned, we need a left icon
= link_to t('help'), t("links.common.faq.url"), class: 'fr-btn fr-icon-questionnaire-line fr-btn--icon-left', title: new_tab_suffix(t('help')), **external_link_attributes
= link_to t('help'), t("links.common.faq.url"), class: 'fr-btn dropdown-button', title: new_tab_suffix(t('help')), **external_link_attributes
@ -100,4 +100,3 @@
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
= yield(:notice_info)

View file

@ -44,8 +44,7 @@
%form.form
= form_for @dossier, url: '', html: { class: 'form' } do |f|
= f.fields_for :champs_public do |champ_form|
= render EditableChamp::EditableChampComponent.new champ: champ_form.object, form: champ_form
= render EditableChamp::SectionComponent.new(champs: @dossier.champs_public)
.editable-champ.editable-champ-text
%label Mot de passe

View file

@ -3,7 +3,7 @@
"@coldwired/actions": "^0.11.2",
"@coldwired/turbo-stream": "^0.11.1",
"@coldwired/utils": "^0.11.1",
"@gouvfr/dsfr": "^1.7.2",
"@gouvfr/dsfr": "^1.10.0",
"@graphiql/plugin-explorer": "^0.2.0",
"@graphiql/toolkit": "^0.8.4",
"@headlessui/react": "^1.6.6",
@ -94,7 +94,7 @@
"coverage": "vitest run --coverage"
},
"engines": {
"node": ">= 16.*"
"node": ">= 18.17.0"
},
"graphdoc": {
"schemaFile": "./app/graphql/schema.json",

View file

@ -20,12 +20,15 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do
expect(page).to have_field(text_repetition_libelle, with: text_repetition_value)
expect(page).to have_field(integer_repetition_libelle, with: integer_repetition_value)
expect(page).to have_field(type_de_champ_datetime.libelle, with: datetime_value)
expect(page).to have_css('label', text: type_de_champ_multiple_drop_down_list.libelle)
expect(page).to have_css('legend', text: type_de_champ_multiple_drop_down_list.libelle)
expect(page).to have_content(multiple_drop_down_list_values.first)
expect(page).to have_content(multiple_drop_down_list_values.last)
expect(page).to have_field("Le département de lEPCI", with: epci_value.first)
expect(page).to have_selector("option[value='#{epci_value.last}'][selected]")
expect(page).to have_field(type_de_champ_dossier_link.libelle, with: dossier_link_value)
label_commune = page.find(:label, text: commune_libelle)
radio_commune = page.find("##{label_commune['for']}")
expect(radio_commune).to be_checked
expect(page).to have_field(commune_libelle, with: '01457')
expect(page).to have_content(annuaire_education_value.last)
expect(page).to have_content(address_value.last)

View file

@ -21,15 +21,15 @@ describe 'The user' do
fill_in('decimal_number', with: '17')
fill_in('integer_number', with: '12')
scroll_to(find_field('checkbox'), align: :center)
check('checkbox')
find('label', text: 'checkbox').click
choose('Madame')
fill_in('email', with: 'loulou@yopmail.com')
fill_in('phone', with: '0123456789')
scroll_to(find_field('Non'), align: :center)
choose('Non')
choose('val2')
check('val1')
check('val3')
find('label', text: 'Non').click
find('.fr-radio-group label', text: 'val2').click
find('.fr-checkbox-group label', text: 'val1').click
find('.fr-checkbox-group label', text: 'val3').click
select('bravo', from: form_id_for('simple_choice_drop_down_list_long'))
select('alpha', from: form_id_for('multiple_choice_drop_down_list_long'))
select('charly', from: form_id_for('multiple_choice_drop_down_list_long'))
@ -37,8 +37,9 @@ describe 'The user' do
select('Australie', from: form_id_for('pays'))
select('Martinique', from: form_id_for('regions'))
select('02 Aisne', from: form_id_for('departements'))
fill_in('Renseignez le code postal puis sélectionnez la commune dans la liste', with: '60400')
select('Brétigny (60400)', from: form_id_for('communes'))
fill_in('Renseignez le code postal', with: '60400')
# wait_until { all('label', text: 'Sélectionnez la commune dans la liste').size == 1 }
select('Brétigny (60400)', from: form_id_for('commune'))
# communes needs more time to be updated
wait_until { champ_value_for('communes') == "Brétigny" }
@ -97,7 +98,7 @@ describe 'The user' do
expect(page).to have_button('alpha')
expect(page).to have_button('charly')
end
expect(page).to have_selected_value('communes', selected: 'Brétigny (60400)')
expect(page).to have_selected_value('commune', selected: 'Brétigny (60400)')
expect(page).to have_selected_value('pays', selected: 'Australie')
expect(page).to have_field('dossier_link', with: '123')
expect(page).to have_text('file.pdf')
@ -359,15 +360,15 @@ describe 'The user' do
fill_individual
expect(page).to have_no_css('label', text: 'champ_c', visible: true)
check('champ_a')
find('label', text: 'champ_a').click # check
wait_for_autosave
expect(page).to have_css('label', text: 'champ_c', visible: true)
uncheck('champ_a')
find('label', text: 'champ_a').click # uncheck
wait_for_autosave
expect(page).to have_no_css('label', text: 'champ_c', visible: true)
check('champ_b')
find('label', text: 'champ_b').click # check
wait_for_autosave
expect(page).to have_css('label', text: 'champ_c', visible: true)
@ -432,17 +433,17 @@ describe 'The user' do
fill_individual
expect(page).to have_css('label', text: 'age du candidat', visible: true)
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('legend h2', text: 'info voiture', visible: true)
expect(page).to have_no_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('legend', text: 'info voiture', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
fill_in('age du candidat (facultatif)', with: '18')
expect(page).to have_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_css('legend h2', text: 'info voiture', visible: true)
expect(page).to have_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_css('legend', text: 'info voiture', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
choose('Oui')
expect(page).to have_css('label', text: 'permis de conduire', visible: true)
page.find('label', text: 'Oui').click
expect(page).to have_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_css('label', text: 'tonnage', visible: true)
fill_in('tonnage', with: '1')
@ -453,7 +454,7 @@ describe 'The user' do
expect(page).to have_no_css('label', text: 'parking', visible: true)
fill_in('age du candidat (facultatif)', with: '2')
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
click_on 'Déposer le dossier'
@ -461,14 +462,14 @@ describe 'The user' do
click_on 'Modifier mon dossier'
expect(page).to have_css('label', text: 'age du candidat', visible: true)
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
fill_in('age du candidat (facultatif)', with: '18')
wait_for_autosave
# the champ keeps their previous value so they are all displayed
expect(page).to have_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_css('legend', text: 'permis de conduire', visible: true)
expect(page).to have_css('label', text: 'tonnage', visible: true)
end
end
@ -554,7 +555,7 @@ describe 'The user' do
end
def fill_individual
choose 'Monsieur'
find('label', text: 'Monsieur').click
fill_in('individual_prenom', with: 'prenom')
fill_in('individual_nom', with: 'nom')
click_on 'Continuer'

View file

@ -23,7 +23,7 @@ describe 'dropdown list with other option activated', js: true, retry: 3 do
scenario 'Select other option and the other input hidden must appear', js: true, retry: 3 do
fill_individual
find('.radios').find('label:last-child').find('input').select_option
find('.fr-fieldset__content .fr-radio-group:last-of-type input').select_option
expect(page).to have_selector('.drop_down_other', visible: true)
end

View file

@ -1360,10 +1360,10 @@
dependencies:
"@floating-ui/dom" "^1.3.0"
"@gouvfr/dsfr@^1.7.2":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@gouvfr/dsfr/-/dsfr-1.8.1.tgz#2484eb8ad9a73b5f01c5f41f49146d1aa8a71b7b"
integrity sha512-XpVFdvhtalA5jSAhzzNaMd+/Hvf8Ef9jCdAZhuukEEMo2/cWvCgzz9tfbE+9QTJDIVP+EwJ7aCGWcXUSWkOHJg==
"@gouvfr/dsfr@^1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@gouvfr/dsfr/-/dsfr-1.10.0.tgz#a3f4939c728e35d8c9ecf06f7a62369ae46256db"
integrity sha512-fHqGCvc8jGtkX2AKRCWi9o3jf/kirP/BeeZ2FBVGwS9Oxd8KsG22xk/6Tls8nA+qPaI+2ZkZQxATBV8KlhwNhQ==
"@graphiql/plugin-explorer@^0.2.0":
version "0.2.0"