To keep things simple, I have a custom form builder that appends some classes to keep my UX consistent. I looked at how DaisyUI handles dropdowns, menus, and navbars and decided that a dropdown would perfectly serve my purposes for creating a tag-select.
I already had a tag_field that did some horrific stuff with tom-elect, but I never liked its styling.
lang-ruby
# frozen_string_literal: true
class TailwindFormBuilder < ActionView::Helpers::FormBuilder
include ActionView::Helpers::TagHelper
def tag_field(field_name, options = {})
autocomplete_options = options.delete(:autocomplete_options) || []
tag.div(class: "tag", data: { controller: "tags" }) do
tag.div(class: "dropdown dropdown-top w-full") do
hidden_field(field_name, value: options[:value]) +
@template.text_field_tag(:tag_input, nil,
class: "input input-bordered input-primary w-full",
tabindex: -1,
placeholder: "Enter tags...",
data: {
tags_target: "input",
action: "keydown.enter->tags#addTag " \
"input->tags#filterAutocomplete " \
"focus->tags#showDropdown " \
"blur->tags#hideDropdownDelayed",
}
) +
tag.ul(
class: "dropdown-content bg-base-300 w-full max-h-60 overflow-auto shadow-xl rounded-box z-10 menu p-2",
data: { tags_target: "dropdown" },
) do
tag.li("", class: "text-sm text-gray-500 p-2", data: { tags_target: "createNew" }) +
safe_join(autocomplete_options.map do |option|
tag.li do
tag.a(href: "#", data: { action: "mousedown->tags#selectOption" }) { option }
end
end,
)
end
end + tag.div(class: "mt-2", data: { tags_target: "tagList" })
end
end
private
def combine_options(default_options = {}, options = {})
original_classes = default_options.delete(:class).to_s.split
override_classes = options.delete(:class).to_s.split
returned_classes = (original_classes + override_classes).uniq.join(" ")
default_options.merge(options).merge(class: returned_classes)
end
end
ActiveSupport.on_load(:action_view) do
ActionView::Base.default_form_builder = TailwindFormBuilder
end
It was an iterative process with my partner Claude, who guided me well.