Skip to content

Instantly share code, notes, and snippets.

@bradgessler
Last active March 8, 2022 17:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradgessler/f855f03e1ffac3224ef92700ed66870c to your computer and use it in GitHub Desktop.
Save bradgessler/f855f03e1ffac3224ef92700ed66870c to your computer and use it in GitHub Desktop.
Code I'm using in production to replace Rails form helpers. The benefit over Rails or Simple Form? I can keep my views in my views and my logic in Ruby classes. If you compare with Simple Form, their view code is in an initializer, which requires a reboot for every change. Rails form helpers don't have a great way to let users define views in te…
button.button type=type
= value
# frozen_string_literal: true
class FormComponent < ViewComponent::Base
attr_reader :model, :url, :data
renders_many :inputs, -> (field, as: :text, hint: nil, label: nil) do
case as
when :hidden
InputComponent.new(model: model, field: field, type: :hidden)
else
TextFieldComponent.new(model: model, field: field, type: as, hint: hint, label: label)
end
end
renders_many :buttons, -> (type, value) do
ButtonComponent.new(value: value, type: type)
end
def initialize(model:, url:, data:)
@model = model
@url = url
@data = data
end
# Basic functionality for mapping a model attribute.
class InputComponent < ViewComponent::Base
attr_reader :type, :field, :model
def initialize(model:, field:, type:)
@model = model
@field = field
@type = type
end
def name
helpers.field_name model.model_name.param_key, @field
end
def id
"#{model.model_name.param_key}_#{@field}"
end
def value
model.send field
end
end
class HiddenFieldComponent < InputComponent
def type
:hidden
end
end
# Handles more functionality such as the label for the field,
# errors, and other information that an input would need that's
# not hidden and is a textbox.
class TextFieldComponent < InputComponent
attr_reader :hint
def initialize(hint: nil, label: nil, **kwargs)
@label = label
@hint = hint
super(**kwargs)
end
def label
@label ||= field.to_s.humanize
end
def errors
model.errors[field]
end
def error_message
"#{label} #{model.errors.generate_message(field)}"
end
def invalid?
errors.any?
end
def error_classes
"border-red-500"
end
def valid_classes
""
end
def classes
invalid? ? error_classes : valid_classes
end
end
class ButtonComponent < ViewComponent::Base
attr_reader :value, :type
def initialize(value:, type:)
@value = value
@type = type
end
end
end
input type=type value=value name=name id=id /
= article_component title: "Sign in with email", subtitle: "We'll email you a code so you can sign in"
= form_component model: @email_authentication, url: create_url, data: { turbo: false } do |f|
.my-8
= f.input :email, as: :email, hint: "We only use this email address to sign you into your account"
.my-8
= f.button :submit, "Email sign in code →"
label.block.text-gray-700.dark:text-gray-200.font-bold.mb-2 for=id
= label
input.shadow.appearance-none.border.rounded.w-full.py-2.px-3.text-gray-700.mb-1.leading-tight.focus:outline-none.focus:shadow-outline.dark:bg-neutral-800.dark:text-neutral-200 class=classes type=type value=value name=name id=id /
-if invalid?
p.text-red-600.text-sm.my-1=error_message
-if hint
p.hint= hint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment