Class: ShadcnViewComponent::FormBuilder
- Inherits:
-
ActionView::Helpers::FormBuilder
- Object
- ActionView::Helpers::FormBuilder
- ShadcnViewComponent::FormBuilder
- Defined in:
- lib/shadcn_view_component/form_builder.rb
Overview
Bridges the components to Rails forms.
shadcn's Field family is the right shape for this — label, control,
description, error — but on its own it knows nothing about a model. This
builder wires the two together: ids and names come from Rails, the error
text and aria-invalid come from ActiveModel::Errors, and Field gets the
data-invalid its styles key off.
<%= shadcn_form_with model: @user do |f| %>
<%= f.shadcn_field :email, label: "Email", description: "We never share it." do %>
<%= f.shadcn_input :email, type: "email" %>
<% end %>
<% end %>
Or, when the control needs no arguments of its own:
<%= f.shadcn_input_field :email, label: "Email", type: "email" %>
Instance Method Summary collapse
-
#shadcn_calendar(method, mode: :single, to: nil, **options) ⇒ Object
A calendar bound to the model, in any of its three modes.
- #shadcn_checkbox(method, **options) ⇒ Object
-
#shadcn_field(method, label: nil, description: nil, orientation: :vertical, **options, &block) ⇒ Object
A labelled control with its description and any validation errors, wired together by id.
- #shadcn_input(method, **options) ⇒ Object
-
#shadcn_native_select(method, choices = [], **options) ⇒ Object
A real
<select>: browser validation, autofill and keyboard behaviour for free. -
#shadcn_radio_group(method, choices = [], **options) ⇒ Object
Each option is rendered as block content rather than through the
itemslot: the items have to sit inside their own label rows, and slot content is emitted before block content, which would pull them all out of place. -
#shadcn_select(method, choices = [], placeholder: nil, **options) ⇒ Object
The Radix-style listbox.
- #shadcn_submit(text = nil, **options, &block) ⇒ Object
- #shadcn_switch(method, **options) ⇒ Object
- #shadcn_textarea(method, **options) ⇒ Object
Instance Method Details
#shadcn_calendar(method, mode: :single, to: nil, **options) ⇒ Object
A calendar bound to the model, in any of its three modes.
f.shadcn_calendar :starts_on
f.shadcn_calendar :dates, mode: :multiple
f.shadcn_calendar :starts_on, mode: :range, to: :ends_on
to: is what makes a range Rails-shaped: two dates are usually two
columns, so the two ends are named after two attributes and each comes
back as itself. Without it a range submits name[from] and name[to]
under one parameter, which suits a form object rather than a record.
The month shown follows the value rather than today, so reopening a form lands on the date already chosen.
113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/shadcn_view_component/form_builder.rb', line 113 def shadcn_calendar(method, mode: :single, to: nil, **) selected = calendar_selection(method, mode:, to:) @template.render(Shadcn::Calendar::Component.new( mode:, selected:, name: to ? [ field_name(method), field_name(to) ] : field_name(method), month: .delete(:month) || Array(selected).compact.first, **aria_labelled(method, ) )) end |
#shadcn_checkbox(method, **options) ⇒ Object
125 126 127 128 129 130 131 |
# File 'lib/shadcn_view_component/form_builder.rb', line 125 def shadcn_checkbox(method, **) @template.render(Shadcn::Checkbox::Component.new( name: field_name(method), checked: !!value_for(method), **aria_labelled(method, **(method, **)) )) end |
#shadcn_field(method, label: nil, description: nil, orientation: :vertical, **options, &block) ⇒ Object
A labelled control with its description and any validation errors, wired
together by id. Field styles itself from data-invalid, so that is set
from the model rather than passed in.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/shadcn_view_component/form_builder.rb', line 30 def shadcn_field(method, label: nil, description: nil, orientation: :vertical, **, &block) = errors_for(method) described[method.to_s] = description.present? @template.render(Shadcn::Field::Component.new( orientation:, "data-invalid": (true if .any?), ** )) do |field| field.with_label(id: label_id(method), for: field_id(method)) { label } if label field.with_field_content { @template.capture(&block) } field.with_description(id: description_id(method)) { description } if description field.with_error(id: error_id(method), errors: ) if .any? end end |
#shadcn_input(method, **options) ⇒ Object
59 60 61 |
# File 'lib/shadcn_view_component/form_builder.rb', line 59 def shadcn_input(method, **) @template.render(Shadcn::Input::Component.new(**(method, **))) end |
#shadcn_native_select(method, choices = [], **options) ⇒ Object
A real <select>: browser validation, autofill and keyboard behaviour for
free. Prefer it over shadcn_select unless you need the styled listbox.
71 72 73 74 75 |
# File 'lib/shadcn_view_component/form_builder.rb', line 71 def shadcn_native_select(method, choices = [], **) @template.render(Shadcn::NativeSelect::Component.new(**(method, **))) do @template.safe_join(Array(choices).map { |choice| native_option(method, choice) }) end end |
#shadcn_radio_group(method, choices = [], **options) ⇒ Object
Each option is rendered as block content rather than through the item
slot: the items have to sit inside their own label rows, and slot content
is emitted before block content, which would pull them all out of place.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/shadcn_view_component/form_builder.rb', line 144 def shadcn_radio_group(method, choices = [], **) selected = value_for(method).to_s @template.render(Shadcn::RadioGroup::Component.new( name: field_name(method), value: selected, ** )) do @template.safe_join(Array(choices).map { |choice| label, value = option_pair(choice) id = "#{field_id(method)}_#{value.parameterize.underscore}" @template.tag.div(class: "flex items-center gap-2") do @template.safe_join([ @template.render(Shadcn::RadioGroup::Item::Component.new( value:, checked: value == selected, id: )), @template.render(Shadcn::Label::Component.new(for: id)) { label } ]) end }) end end |
#shadcn_select(method, choices = [], placeholder: nil, **options) ⇒ Object
The Radix-style listbox. It submits through a hidden input, so it has no
browser validation — required will not stop the form.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/shadcn_view_component/form_builder.rb', line 79 def shadcn_select(method, choices = [], placeholder: nil, **) selected = value_for(method).to_s @template.render(Shadcn::Select::Component.new( name: field_name(method), value: selected, placeholder:, ** )) do |select| select.with_trigger(**aria_labelled(method, id: field_id(method), placeholder: selected.blank?, "aria-invalid": invalid(method))) do |trigger| trigger.with_value(placeholder:) { label_for(choices, selected) } end select.with_select_content do @template.safe_join(Array(choices).map { |choice| label, value = option_pair(choice) @template.render(Shadcn::Select::Item::Component.new(value:, selected: value == selected)) { label } }) end end end |
#shadcn_submit(text = nil, **options, &block) ⇒ Object
166 167 168 169 170 |
# File 'lib/shadcn_view_component/form_builder.rb', line 166 def shadcn_submit(text = nil, **, &block) @template.render(Shadcn::Button::Component.new(type: "submit", **)) do block ? @template.capture(&block) : (text || submit_default_value) end end |
#shadcn_switch(method, **options) ⇒ Object
133 134 135 136 137 138 139 |
# File 'lib/shadcn_view_component/form_builder.rb', line 133 def shadcn_switch(method, **) @template.render(Shadcn::Switch::Component.new( name: field_name(method), checked: !!value_for(method), **aria_labelled(method, **(method, **)) )) end |