Created
January 26, 2026 07:23
-
-
Save osbre/167bbfc3f90529397e397eb687e02bec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class ApplicationFormBuilder < ActionView::Helpers::FormBuilder | |
| LABEL_CLASS = "block text-sm/6 font-medium text-gray-900 dark:text-white".freeze | |
| INPUT_CLASS = "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500".freeze | |
| TEXTAREA_CLASS = "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500".freeze | |
| SELECT_CLASS = "col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800 dark:focus:outline-indigo-500".freeze | |
| CHECKBOX_CLASS = "col-start-1 row-start-1 appearance-none rounded border border-gray-300 bg-white checked:border-indigo-600 checked:bg-indigo-600 indeterminate:border-indigo-600 indeterminate:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:checked:border-indigo-500 dark:checked:bg-indigo-500 dark:indeterminate:border-indigo-500 dark:indeterminate:bg-indigo-500 dark:focus-visible:outline-indigo-500 dark:disabled:border-white/5 dark:disabled:bg-white/10 dark:disabled:checked:bg-white/10 forced-colors:appearance-auto".freeze | |
| RADIO_CLASS = "relative size-4 appearance-none rounded-full border border-gray-300 bg-white before:absolute before:inset-1 before:rounded-full before:bg-white checked:border-indigo-600 checked:bg-indigo-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:before:bg-gray-400 dark:border-white/10 dark:bg-white/5 dark:checked:border-indigo-500 dark:checked:bg-indigo-500 dark:focus-visible:outline-indigo-500 dark:disabled:border-white/5 dark:disabled:bg-white/10 dark:disabled:before:bg-white/20 forced-colors:appearance-auto forced-colors:before:hidden [&:not(:checked)]:before:hidden".freeze | |
| SUBMIT_CLASS = "rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:shadow-none dark:focus-visible:outline-indigo-500".freeze | |
| SECONDARY_BUTTON_CLASS = "rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-white dark:shadow-none dark:ring-white/5 dark:hover:bg-white/20".freeze | |
| INPUT_HELPERS = %i[ | |
| text_field email_field password_field search_field telephone_field phone_field url_field | |
| number_field date_field datetime_field datetime_local_field month_field week_field time_field | |
| color_field range_field | |
| ].freeze | |
| INPUT_HELPERS.each do |helper| | |
| define_method(helper) do |method, options = {}| | |
| super(method, with_default_class(options, INPUT_CLASS)) | |
| end | |
| end | |
| def label(method, text = nil, options = {}, &block) | |
| super(method, text, with_default_class(options, LABEL_CLASS), &block) | |
| end | |
| def text_area(method, options = {}) | |
| super(method, with_default_class(options, TEXTAREA_CLASS)) | |
| end | |
| def select(method, choices = nil, options = {}, html_options = {}, &block) | |
| html_options = with_default_class(html_options, SELECT_CLASS) | |
| select_html = super(method, choices, options, html_options, &block) | |
| return select_html if html_options[:multiple] | |
| select_with_icon(select_html) | |
| end | |
| def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) | |
| html_options = with_default_class(html_options, SELECT_CLASS) | |
| select_html = super(method, collection, value_method, text_method, options, html_options) | |
| return select_html if html_options[:multiple] | |
| select_with_icon(select_html) | |
| end | |
| def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") | |
| super(method, with_default_class(options, CHECKBOX_CLASS), checked_value, unchecked_value) | |
| end | |
| def radio_button(method, tag_value, options = {}) | |
| super(method, tag_value, with_default_class(options, RADIO_CLASS)) | |
| end | |
| def submit(value = nil, options = {}) | |
| super(value, with_default_class(options, SUBMIT_CLASS)) | |
| end | |
| def secondary_button(value = nil, options = {}) | |
| options = with_default_class(options, SECONDARY_BUTTON_CLASS) | |
| options[:type] ||= "button" | |
| @template.button_tag(value, options) | |
| end | |
| def checkbox_field(method, label:, description: nil, checked: nil, checked_value: "1", unchecked_value: "0", **options) | |
| options[:checked] = checked unless checked.nil? | |
| checkbox = check_box(method, options, checked_value, unchecked_value) | |
| label_tag = @template.label(@object_name, method, label, class: "font-medium text-gray-900 dark:text-white") | |
| description_tag = description ? @template.tag.p(description, class: "text-gray-500 dark:text-gray-400") : nil | |
| text_content = @template.safe_join([ label_tag, description_tag ].compact) | |
| checkbox_container = @template.content_tag(:div, class: "flex h-6 shrink-0 items-center") do | |
| @template.content_tag(:div, class: "group grid size-4 grid-cols-1") do | |
| check_icon = @template.tag.path(d: "M3 8L6 11L11 3.5", stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", class: "opacity-0 group-has-[:checked]:opacity-100") | |
| indeterminate_icon = @template.tag.path(d: "M3 7H11", stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", class: "opacity-0 group-has-[:indeterminate]:opacity-100") | |
| icon = @template.content_tag(:svg, @template.safe_join([ check_icon, indeterminate_icon ]), viewBox: "0 0 14 14", fill: "none", class: "pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white") | |
| @template.safe_join([ checkbox, icon ]) | |
| end | |
| end | |
| text_container = @template.content_tag(:div, text_content, class: "text-sm/6") | |
| @template.content_tag(:div, class: "flex items-start gap-3") do | |
| @template.safe_join([ checkbox_container, text_container ]) | |
| end | |
| end | |
| private | |
| def with_default_class(options, default_class) | |
| options = options.dup | |
| options[:class] = [ default_class, options[:class] ].compact.join(" ") | |
| options | |
| end | |
| def select_with_icon(select_html) | |
| icon = @template.content_tag( | |
| :svg, | |
| @template.tag.path( | |
| d: "M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z", | |
| "clip-rule": "evenodd", | |
| "fill-rule": "evenodd" | |
| ), | |
| viewBox: "0 0 16 16", | |
| fill: "currentColor", | |
| "data-slot": "icon", | |
| "aria-hidden": "true", | |
| class: "pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4 dark:text-gray-400" | |
| ) | |
| @template.content_tag(:div, class: "grid grid-cols-1") do | |
| @template.safe_join([ select_html, icon ]) | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment