Open Spone opened 3 years ago
Here are some syntax options for groups, which one do you prefer? Or maybe we can implement both?
We could pass strings:
<%= f.text_field :first_name, label: "First name", hint: "How should we call you?" %>
<div>
<label for="user_first_name">First name</label>
<span>How should we call you?</span>
<input type="text" id="user_first_name" name"user[first_name]" />
</div>
or booleans... in this case the label and the hint come from the locale files:
<%= f.text_field :first_name, label: true, hint: true %>
# config/locale/en.yml
helpers:
label:
user:
first_name: First name
hint:
user:
first_name: How should we call you?
We can also pass hashes with a :text
key. This allows us to add more params later (for instance to add class
or position
). That's similar to what GOV.UK does.
<%= f.text_field :first_name, label: { text: "First name" }, hint: { text: "How should we call you?" } %>
<%= f.text_field :first_name, label: { text: "First name", class: "my-custom-label" }, hint: { text: "How should we call you?", position: :after_input } %>
<div>
<label for="user_first_name" class="my-custom-label">First name</label>
<input type="text" id="user_first_name" name"user[first_name]" />
<span>How should we call you?</span>
</div>
group
helper, with a block<%= f.group :first_name, hint: "How should we call you?" do %>
<%= f.text_field :first_name %>
<% end %>
<div>
<label for="user_first_name">First name</label>
<span>How should we call you?</span>
<input type="text" id="user_first_name" name"user[first_name]" />
</div>
I personally prefer the second one
After discussing it, we'll go for the second option (Using a group
helper, with a block).
While working on a first implementation, we hit the following roadblocks:
.form-group
you may want the input to have .form-group-input
)aria-describedby
attribute of the input (by the way, #field_id can be used for this)<label>
element for the group label, since the labels are already attached to each checkbox / radioA potential solution to 1. 2. 3. is for the block to receive its own FormBuilder instance as an argument, so instead of:
<%= f.group :first_name, hint: "How should we call you?" do %>
<%= f.text_field :first_name %>
<% end %>
we would have
<%= f.group :first_name, hint: "How should we call you?" do |g| %>
<%= g.text_field :first_name %>
<% end %>
Then we could inject the class
, placeholder
, aria-describedby
.
For 4. we may need to create another helper, such as collection_group
or maybe pass a param label_tag: :div
to group
?
Feel free to contribute ideas :pray:
Hi, it would be great to have the "primitives" for this in the code... I think having such a group method could be the second step, as the primitives "hints" and "error message" are always useful.
I created something like this. Maybe it is useful for anyone: https://gist.github.com/tmaier/22966c6bddac86e3612c8eddc072b919
Hi, it would be great to have the "primitives" for this in the code... I think having such a group method could be the second step, as the primitives "hints" and "error message" are always useful.
Hi @tmaier I opened an issue for this (#97) would you like to contribute a PR for this?
Related to #127
I've been playing around with a slot-based solution for this. There are some downsides as the syntax is not exactly the same as using a proper FormBuilder.
# frozen_string_literal: true
class Form::GroupComponent < ViewComponent::Form::FieldComponent
renders_one :input, ->(input, options = {}) do
"Form::#{input.to_s.camelize}FieldComponent".constantize.new(
form,
object_name,
method_name,
default_input_options.merge(options)
)
end
def show_label?
options[:hide_label] != true
end
def hint
options[:hint] # || t(some.translation.key.default)
end
def default_input_options
return {} unless hint.present?
{aria: {describedby: hint_id}}
end
def hint_id
form.field_id(method_name, :hint)
end
end
<fieldset>
<% if show_label? %>
<label for="<%= method_name %>" class="block text-sm font-medium leading-6 text-gray-900"><%= label_text %></label>
<% end %>
<div class="mt-2">
<div class="relative rounded-md shadow-sm">
<%= input %>
<% if method_errors? %>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<%= render IconComponent.new(name: :circle_exclamation, scheme: :alert) %>
</div>
<% end %>
</div>
<%= form.hint method_name, hint, id: hint_id %>
<%= form.error_message method_name %>
</div>
</fieldset>
<%= form.group :email, hint: "What is the email you signed up with?" do |group| %>
<%= group.with_input :email, autocomplete: "email", class: "w-full" %>
<% end %>
<fieldset>
<label for="email" class="block text-sm font-medium leading-6 text-gray-900">Email</label>
<div class="mt-2">
<div class="relative rounded-md shadow-sm">
<input aria-describedby="admin_email_hint" autocomplete="email"
class="w-full block rounded-md border-0 shadow-sm py-1.5 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 text-slate-900 ring-slate-300 placeholder:text-slate-400 focus:ring-slate-500"
type="email" name="admin[email]" id="admin_email">
</div>
<div skip_default_ids="false" allow_method_names_outside_object="true" id="admin_email_hint"
object="#<Admin:0x0000000170f4ac88>" class="mt-1 text-sm text-gray-500">What is the email you signed up with?
</div>
</div>
</fieldset>
It needs some more work (I have no idea, why it outputs an Admin instance), but I think there is some potential.
A common use case when building forms is the need to group labels and fields, or multiple fields together. Let's discuss these cases.
A label + a field
It's the most common use case. That's for instance what is generated by Rails scaffolding:
When the field is a
check_box
or aradio_button
, you usually want to invert the label and the input:We could have a
ViewComponent::Form::GroupComponent
for this purpose.We could also add a
label
option to some helpers (see #16).Errors
When a field has errors, it's a good practice to display them next to the field. The errors could be handled by the
ViewComponent::Form::GroupComponent
.Hints
Some fields require additional information to help the user. The
ViewComponent::Form::GroupComponent
could accept ahint
option for this. See GOV.UK for an example implementation.A group of fields (and their labels)
The
<fieldset>
element is used for this.Rails does not provide a dedicated helper for this element.
We could have a
ViewComponent::Form::FieldsetComponent
for this purpose, or reuse theViewComponent::Form::GroupComponent
(but it would make it more complex).