pantographe / view_component-form

Rails FormBuilder for ViewComponent
MIT License
204 stars 16 forks source link

Add missing block arg for fields #155

Open wooly opened 10 months ago

wooly commented 10 months ago

The dynamic methods created by the form helper are missing the block param which makes it impossible to render slots in custom components.

Does this need a bit of test coverage before it can go in?

Spone commented 10 months ago

Thanks for your contribution @wooly! Yes, please add test coverage for this.

The methods do not include the block argument because our aim is to mirror Rails' helpers (see for instance radio_button)

wooly commented 10 months ago

Ah yes, I see. Perhaps there's a different way to do what I want to do, if you may have some suggestions?

I'm trying to allow the user to specify a piece of html to be rendered next to the input, like the go button in the below screenshot:

Screenshot 2023-10-27 at 9 13 33 am

The way I was thinking to do it was to provide a slot for my text field component called append, and then use it like this:

<%= f.text_field :append_example, label: 'With append', placeholder: 'With append' do |c| %>
  <% c.with_append do %>
    BTN
  <% end %>
<% end %>

but this isn't currently possible, since it appears that in my child the block isn't yielded so the with_append is never called. Unpolished code is below to have a squizz at.

module Gaia
  class TextFieldComponent < ViewComponent::Form::TextFieldComponent
    include FormHelpers
    include ViewComponentsHelper

    renders_one :append

    def html_class
      class_names('gaia-input', 'has-error': method_errors?)
    end

    def call
      wrapper_tag do
        concat label_tag

        if append
          concat input_with_inline(super)
        else
          concat super
        end

        if has_errors?
          concat content_tag(:span, error_message, class: 'gaia-input-error')
        elsif help_message.present?
          concat content_tag(:span, help_message, class: 'gaia-input-help')
        end
      end
    end

    private

    def input_with_inline(input)
      render(Inline::Component.new(spacing: :xxsmall)) do
        concat input
        concat append
      end
    end
  end
end

Can you think of a neater way? Any input greatly appreciated!

Spone commented 10 months ago

Thanks for elaborating, I understand your use case better.

My approach for this is to create another helper, such as group, that's in charge of wrapping your main field, adding a label, a hint, a prefix / suffix, etc.

That the 2. in this comment.

Let me know if it helps or if you'd like further guidance!

wooly commented 10 months ago

Thanks for the quick reply! I had seen the group, but felt it wouldn't fit my use case. The reasoning for this is that the appended HTML has the addon below and beside the input field itself.

So in the case of no append, the DOM would look something like this:

<div class="stack">
  <label>Foo</label>
  <input>
  <help>
</div>

But in the append case, the DOM would look roughly like this:

<div class="stack">
  <label>Foo</label>
  <div class="inline">
    <input>
    <button>Some Button</button>
  </div>
  <help>
</div>

And I couldn't quite figure out how to replicate this behaviour with the group.

wooly commented 10 months ago

@Spone do you have any suggestions on how to test this?

I don't think I can use a shared example set since it's the base component that is rendered rather than a custom one with a slot. My plan was to add a text_field_with_slot_component here, and verify (somewhere) that it would render the slot HTML.

What do you think?