Class: BetterUi::UiFormBuilder

Inherits:
ActionView::Helpers::FormBuilder
  • Object
show all
Defined in:
app/form_builders/better_ui/ui_form_builder.rb

Overview

Custom Rails form builder for rendering form inputs using BetterUi::Forms components.

This form builder integrates seamlessly with ActiveModel objects to automatically populate field values, validation errors, and required status from the model. All form inputs are rendered using ViewComponents from the BetterUi::Forms namespace, ensuring consistent styling and behavior across the application.

Examples:

Basic usage with form_with

<%= form_with model: @user, builder: BetterUi::UiFormBuilder do |f| %>
  <%= f.bui_text_input :name %>
  <%= f.bui_text_input :email, hint: "We'll never share your email" %>
  <%= f.bui_number_input :age, min: 0, max: 120 %>
<% end %>

With custom labels and sizes

<%= form_with model: @product, builder: BetterUi::UiFormBuilder do |f| %>
  <%= f.bui_text_input :title, label: "Product Name", size: :lg %>
  <%= f.bui_number_input :price, label: "Price ($)", min: 0, step: 0.01 %>
<% end %>

With icon slots

<%= form_with model: @user, builder: BetterUi::UiFormBuilder do |f| %>
  <%= f.bui_text_input :email do |component| %>
    <% component.with_prefix_icon do %>
      <svg class="h-5 w-5 text-gray-400">...</svg>
    <% end %>
  <% end %>

  <%= f.bui_number_input :budget do |component| %>
    <% component.with_prefix_icon { "$" } %>
  <% end %>
<% end %>

Automatic error handling

# When @user has validation errors:
<%= form_with model: @user, builder: BetterUi::UiFormBuilder do |f| %>
  <%= f.bui_text_input :email %>
  # Automatically displays error messages and applies error styling
<% end %>

See Also:

Instance Method Summary collapse

Instance Method Details

#bui_checkbox(attribute, options = {}) ⇒ String

Renders a checkbox input field using BetterUi::Forms::CheckboxComponent

Examples:

Basic usage

<%= f.bui_checkbox :newsletter %>

With custom label

<%= f.bui_checkbox :terms, label: "I agree to the terms and conditions" %>

With variant

<%= f.bui_checkbox :active, variant: :success, label: "Active" %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component

Options Hash (options):

  • :label (String) —

    Custom label text (defaults to humanized attribute name)

  • :value (String) —

    The value submitted when checkbox is checked (default: "1")

  • :hint (String) —

    Hint text to display below the checkbox

  • :variant (Symbol) —

    Color variant (:primary, :secondary, :accent, :success, :danger, :warning, :info, :light, :dark)

  • :size (Symbol) —

    Size variant (:xs, :sm, :md, :lg, :xl)

  • :label_position (Symbol) —

    Position of label relative to checkbox (:left, :right)

  • :disabled (Boolean) —

    Whether the checkbox is disabled

  • :readonly (Boolean) —

    Whether the checkbox is readonly

  • :required (Boolean) —

    Whether the checkbox is required

Returns:

  • (String) —

    Rendered component HTML



285
286
287
288
289
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 285

def bui_checkbox(attribute, options = {})
  component_options = build_checkbox_options(attribute, options)

  @template.render(BetterUi::Forms::CheckboxComponent.new(**component_options))
end

#bui_checkbox_group(attribute, collection, options = {}) ⇒ String

Renders a checkbox group using BetterUi::Forms::CheckboxGroupComponent

Examples:

Basic usage

<%= f.bui_checkbox_group :roles, ["Admin", "Editor", "Viewer"] %>

With label/value pairs

<%= f.bui_checkbox_group :permissions, [["Read", "read"], ["Write", "write"]] %>

Horizontal layout

<%= f.bui_checkbox_group :interests, ["Sports", "Music", "Art"], orientation: :horizontal %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • collection (Array) —

    The collection of options, can be:

    • Array of values (e.g., ["Admin", "Editor"])
    • Array of [label, value] pairs (e.g., [["Admin", "admin"], ["Editor", "editor"]])
  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component

Options Hash (options):

  • :legend (String) —

    Legend text for the fieldset (defaults to humanized attribute name)

  • :hint (String) —

    Hint text to display below the checkboxes

  • :variant (Symbol) —

    Color variant for all checkboxes (:primary, :secondary, etc.)

  • :size (Symbol) —

    Size variant (:xs, :sm, :md, :lg, :xl)

  • :orientation (Symbol) —

    Layout orientation (:vertical, :horizontal)

  • :disabled (Boolean) —

    Whether all checkboxes are disabled

  • :required (Boolean) —

    Whether the field is required

Returns:

  • (String) —

    Rendered component HTML



315
316
317
318
319
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 315

def bui_checkbox_group(attribute, collection, options = {})
  component_options = build_checkbox_group_options(attribute, collection, options)

  @template.render(BetterUi::Forms::CheckboxGroupComponent.new(**component_options))
end

#bui_date_input(attribute, options = {}, &block) ⇒ String

Renders a date input field using BetterUi::Forms::TextInputComponent with type: :date

Examples:

Basic usage

<%= f.bui_date_input :birthday %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component (see #bui_text_input)

Returns:

  • (String) —

    Rendered component HTML



116
117
118
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 116

def bui_date_input(attribute, options = {}, &block)
  bui_text_input(attribute, options.merge(type: :date), &block)
end

#bui_email_input(attribute, options = {}, &block) ⇒ String

Renders an email input field using BetterUi::Forms::TextInputComponent with type: :email

Examples:

Basic usage

<%= f.bui_email_input :email %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component (see #bui_text_input)

Returns:

  • (String) —

    Rendered component HTML



92
93
94
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 92

def bui_email_input(attribute, options = {}, &block)
  bui_text_input(attribute, options.merge(type: :email), &block)
end

#bui_number_input(attribute, options = {}, &block) ⇒ String

Renders a number input field using BetterUi::Forms::NumberInputComponent

Examples:

Basic usage

<%= f.bui_number_input :age %>

With range and step

<%= f.bui_number_input :price, min: 0, max: 10000, step: 0.01 %>

Without spinners

<%= f.bui_number_input :price, show_spinner: false %>

With icon

<%= f.bui_number_input :price do |c| %>
  <% c.with_prefix_icon { "$" } %>
<% end %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component

Options Hash (options):

  • :label (String) —

    Custom label text (defaults to humanized attribute name)

  • :hint (String) —

    Hint text to display below the input

  • :placeholder (String) —

    Placeholder text

  • :size (Symbol) —

    Size variant (:xs, :sm, :md, :lg, :xl)

  • :disabled (Boolean) —

    Whether the input is disabled

  • :readonly (Boolean) —

    Whether the input is readonly

  • :required (Boolean) —

    Whether the input is required

  • :min (Numeric) —

    Minimum value

  • :max (Numeric) —

    Maximum value

  • :step (Numeric) —

    Step value

  • :show_spinner (Boolean) —

    Whether to show up/down spinner arrows (default: true)

Returns:

  • (String) —

    Rendered component HTML



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 162

def bui_number_input(attribute, options = {}, &block)
  component_options = build_input_options(attribute, options)

  # Add number-specific options
  component_options[:min] = options[:min] if options.key?(:min)
  component_options[:max] = options[:max] if options.key?(:max)
  component_options[:step] = options[:step] if options.key?(:step)
  component_options[:show_spinner] = options.fetch(:show_spinner, true)

  if block_given?
    @template.render(BetterUi::Forms::NumberInputComponent.new(**component_options), &block)
  else
    @template.render(BetterUi::Forms::NumberInputComponent.new(**component_options))
  end
end

#bui_password_input(attribute, options = {}, &block) ⇒ String

Renders a password input field using BetterUi::Forms::PasswordInputComponent

Examples:

Basic usage

<%= f.bui_password_input :password %>

With confirmation field

<%= f.bui_password_input :password, hint: "Must be at least 8 characters" %>
<%= f.bui_password_input :password_confirmation %>

With icon

<%= f.bui_password_input :password do |c| %>
  <% c.with_prefix_icon do %>
    <svg class="h-5 w-5 text-gray-400">...</svg>
  <% end %>
<% end %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component

Options Hash (options):

  • :label (String) —

    Custom label text (defaults to humanized attribute name)

  • :hint (String) —

    Hint text to display below the input

  • :placeholder (String) —

    Placeholder text

  • :size (Symbol) —

    Size variant (:xs, :sm, :md, :lg, :xl)

  • :disabled (Boolean) —

    Whether the input is disabled

  • :readonly (Boolean) —

    Whether the input is readonly

  • :required (Boolean) —

    Whether the input is required

Returns:

  • (String) —

    Rendered component HTML



204
205
206
207
208
209
210
211
212
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 204

def bui_password_input(attribute, options = {}, &block)
  component_options = build_input_options(attribute, options)

  if block_given?
    @template.render(BetterUi::Forms::PasswordInputComponent.new(**component_options), &block)
  else
    @template.render(BetterUi::Forms::PasswordInputComponent.new(**component_options))
  end
end

#bui_select(attribute, collection, options = {}, &block) ⇒ String

Renders a custom select dropdown using BetterUi::Forms::SelectComponent

Examples:

Basic usage

<%= f.bui_select :country, [["Italy", "it"], ["France", "fr"]] %>

With options

<%= f.bui_select :country, [["Italy", "it"]], clearable: true, size: :lg %>

With prefix icon

<%= f.bui_select :country, [["Italy", "it"]] do |c| %>
  <% c.with_prefix_icon { icon_svg } %>
<% end %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • collection (Array) —

    The collection of options, can be:

    • Array of values (e.g., ["Red", "Blue"])
    • Array of [label, value] pairs (e.g., [["Italy", "it"], ["France", "fr"]])
  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component

Options Hash (options):

  • :label (String) —

    Custom label text (defaults to humanized attribute name)

  • :hint (String) —

    Hint text to display below the select

  • :placeholder (String) —

    Placeholder text

  • :size (Symbol) —

    Size variant (:xs, :sm, :md, :lg, :xl)

  • :disabled (Boolean) —

    Whether the select is disabled

  • :readonly (Boolean) —

    Whether the select is readonly

  • :required (Boolean) —

    Whether the select is required

  • :clearable (Boolean) —

    Whether to show a clear button

  • :dropdown_classes (String) —

    Custom CSS classes for the dropdown

Returns:

  • (String) —

    Rendered component HTML



349
350
351
352
353
354
355
356
357
358
359
360
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 349

def bui_select(attribute, collection, options = {}, &block)
  component_options = build_input_options(attribute, options)
  component_options[:collection] = collection
  component_options[:clearable] = options.fetch(:clearable, false)
  component_options[:dropdown_classes] = options[:dropdown_classes] if options.key?(:dropdown_classes)

  if block_given?
    @template.render(BetterUi::Forms::SelectComponent.new(**component_options), &block)
  else
    @template.render(BetterUi::Forms::SelectComponent.new(**component_options))
  end
end

#bui_tel_input(attribute, options = {}, &block) ⇒ String

Renders a telephone input field using BetterUi::Forms::TextInputComponent with type: :tel

Examples:

Basic usage

<%= f.bui_tel_input :phone %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component (see #bui_text_input)

Returns:

  • (String) —

    Rendered component HTML



104
105
106
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 104

def bui_tel_input(attribute, options = {}, &block)
  bui_text_input(attribute, options.merge(type: :tel), &block)
end

#bui_text_input(attribute, options = {}, &block) ⇒ String

Renders a text input field using BetterUi::Forms::TextInputComponent

Examples:

Basic usage

<%= f.bui_text_input :email %>

With options

<%= f.bui_text_input :email, size: :lg, hint: "We'll never share your email" %>

With icon

<%= f.bui_text_input :email do |c| %>
  <% c.with_prefix_icon { "📧" } %>
<% end %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component

Options Hash (options):

  • :label (String) —

    Custom label text (defaults to humanized attribute name)

  • :hint (String) —

    Hint text to display below the input

  • :placeholder (String) —

    Placeholder text

  • :size (Symbol) —

    Size variant (:xs, :sm, :md, :lg, :xl)

  • :disabled (Boolean) —

    Whether the input is disabled

  • :readonly (Boolean) —

    Whether the input is readonly

  • :required (Boolean) —

    Whether the input is required

Returns:

  • (String) —

    Rendered component HTML



73
74
75
76
77
78
79
80
81
82
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 73

def bui_text_input(attribute, options = {}, &block)
  component_options = build_input_options(attribute, options)
  component_options[:type] = options[:type] if options.key?(:type)

  if block_given?
    @template.render(BetterUi::Forms::TextInputComponent.new(**component_options), &block)
  else
    @template.render(BetterUi::Forms::TextInputComponent.new(**component_options))
  end
end

#bui_textarea(attribute, options = {}, &block) ⇒ String

Renders a textarea field using BetterUi::Forms::TextareaComponent

Examples:

Basic usage

<%= f.bui_textarea :description %>

With custom rows and maxlength

<%= f.bui_textarea :bio, rows: 6, maxlength: 500, hint: "Maximum 500 characters" %>

With resize disabled

<%= f.bui_textarea :notes, resize: :none %>

With icon

<%= f.bui_textarea :comment do |c| %>
  <% c.with_prefix_icon do %>
    <svg class="h-5 w-5 text-gray-400">...</svg>
  <% end %>
<% end %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component

Options Hash (options):

  • :label (String) —

    Custom label text (defaults to humanized attribute name)

  • :hint (String) —

    Hint text to display below the textarea

  • :placeholder (String) —

    Placeholder text

  • :size (Symbol) —

    Size variant (:xs, :sm, :md, :lg, :xl)

  • :disabled (Boolean) —

    Whether the textarea is disabled

  • :readonly (Boolean) —

    Whether the textarea is readonly

  • :required (Boolean) —

    Whether the textarea is required

  • :rows (Integer) —

    Number of visible text lines (default: 4)

  • :cols (Integer) —

    Width in characters (optional)

  • :maxlength (Integer) —

    Maximum number of characters allowed

  • :resize (Symbol) —

    CSS resize behavior (:none, :vertical, :horizontal, :both)

Returns:

  • (String) —

    Rendered component HTML



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 246

def bui_textarea(attribute, options = {}, &block)
  component_options = build_input_options(attribute, options)

  # Add textarea-specific options
  component_options[:rows] = options.fetch(:rows, 4)
  component_options[:cols] = options[:cols] if options.key?(:cols)
  component_options[:maxlength] = options[:maxlength] if options.key?(:maxlength)
  component_options[:resize] = options.fetch(:resize, :vertical)

  if block_given?
    @template.render(BetterUi::Forms::TextareaComponent.new(**component_options), &block)
  else
    @template.render(BetterUi::Forms::TextareaComponent.new(**component_options))
  end
end

#bui_time_input(attribute, options = {}, &block) ⇒ String

Renders a time input field using BetterUi::Forms::TextInputComponent with type: :time

Examples:

Basic usage

<%= f.bui_time_input :start_time %>

Parameters:

  • attribute (Symbol) —

    The attribute name

  • options (Hash) (defaults to: {}) —

    Additional options to pass to the component (see #bui_text_input)

Returns:

  • (String) —

    Rendered component HTML



128
129
130
# File 'app/form_builders/better_ui/ui_form_builder.rb', line 128

def bui_time_input(attribute, options = {}, &block)
  bui_text_input(attribute, options.merge(type: :time), &block)
end