44# additional helpers like fieldset and hint.
55# https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html
66class UswdsFormBuilder < ActionView ::Helpers ::FormBuilder
7+ standard_helpers = %i[ email_field file_field password_field text_area text_field ]
8+
79 def initialize ( *args )
810 super
911 self . options [ :html ] ||= { }
@@ -19,25 +21,33 @@ def initialize(*args)
1921 #
2022 # Example usage:
2123 # <%= f.text_field :foobar, { label: "Custom label text", hint: "Some hint text" } %>
22- %i[ email_field file_field password_field text_area text_field ] . each do |field_type |
24+ standard_helpers . each do |field_type |
2325 define_method ( field_type ) do |attribute , options = { } |
2426 classes = us_class_for_field_type ( field_type , options [ :width ] )
2527 classes += " usa-input--error" if has_error? ( attribute )
26-
27- options [ :class ] ||= ""
28- options [ :class ] . prepend ( "#{ classes } " )
28+ append_to_option ( options , :class , " #{ classes } " )
2929
3030 label_text = options . delete ( :label )
31+ label_class = options . delete ( :label_class ) || ""
32+
33+ label_options = options . except ( :width , :class , :id ) . merge ( {
34+ class : label_class ,
35+ for : options [ :id ]
36+ } )
37+ field_options = options . except ( :label , :hint , :large_label , :label_class )
3138
32- us_form_group ( attribute : attribute ) do
33- us_text_field_label ( attribute , label_text , options ) + super ( attribute , options )
39+ if options [ :hint ]
40+ field_options [ :aria_describedby ] = hint_id ( attribute )
41+ end
42+
43+ form_group ( attribute , options [ :group_options ] || { } ) do
44+ us_text_field_label ( attribute , label_text , label_options ) + super ( attribute , field_options )
3445 end
3546 end
3647 end
3748
3849 def check_box ( attribute , options = { } , *args )
39- options [ :class ] ||= ""
40- options [ :class ] . prepend ( us_class_for_field_type ( :check_box ) )
50+ append_to_option ( options , :class , " #{ us_class_for_field_type ( :check_box ) } " )
4151
4252 label_text = options . delete ( :label )
4353
@@ -47,8 +57,7 @@ def check_box(attribute, options = {}, *args)
4757 end
4858
4959 def radio_button ( attribute , tag_value , options = { } )
50- options [ :class ] ||= ""
51- options [ :class ] . prepend ( us_class_for_field_type ( :radio_button ) )
60+ append_to_option ( options , :class , " #{ us_class_for_field_type ( :radio_button ) } " )
5261
5362 label_text = options . delete ( :label )
5463 label_options = { for : field_id ( attribute , tag_value ) } . merge ( options )
@@ -59,21 +68,21 @@ def radio_button(attribute, tag_value, options = {})
5968 end
6069
6170 def select ( attribute , choices , options = { } , html_options = { } )
62- classes = "usa-select"
63-
64- html_options [ :class ] ||= ""
65- html_options [ :class ] . prepend ( "#{ classes } " )
71+ append_to_option ( html_options , :class , " usa-select" )
6672
6773 label_text = options . delete ( :label )
6874
69- us_form_group ( attribute : attribute ) do
75+ form_group ( attribute ) do
7076 us_text_field_label ( attribute , label_text , options ) + super ( attribute , choices , options , html_options )
7177 end
7278 end
7379
7480 def submit ( value = nil , options = { } )
75- options [ :class ] ||= ""
76- options [ :class ] . prepend ( "usa-button " )
81+ append_to_option ( options , :class , " usa-button" )
82+
83+ if options [ :big ]
84+ append_to_option ( options , :class , " usa-button--big margin-y-6" )
85+ end
7786
7887 super ( value , options )
7988 end
@@ -92,16 +101,49 @@ def honeypot_field
92101 # Custom helpers
93102 ########################################
94103
104+ def tax_id_field ( attribute , options = { } )
105+ options [ :inputmode ] = "numeric"
106+ options [ :placeholder ] = "_________"
107+ options [ :width ] = "md"
108+
109+ append_to_option ( options , :class , " usa-masked" )
110+ append_to_option ( options , :hint , @template . content_tag ( :p , I18n . t ( "us_form_with.tax_id_format" ) ) )
111+
112+ text_field ( attribute , options )
113+ end
114+
115+ def date_picker ( attribute , options = { } )
116+ raw_value = object . send ( attribute ) if object
117+
118+ append_to_option ( options , :hint , @template . content_tag ( :p , I18n . t ( "us_form_with.date_picker_format" ) ) )
119+
120+ group_options = options [ :group_options ] || { }
121+ append_to_option ( group_options , :class , " usa-date-picker" )
122+
123+ if raw_value . is_a? ( Date )
124+ append_to_option ( group_options , :"data-default-value" , raw_value . strftime ( "%Y-%m-%d" ) )
125+ value = raw_value . strftime ( "%m/%d/%Y" ) if raw_value . is_a? ( Date )
126+ end
127+
128+ text_field ( attribute , options . merge ( value : value , group_options : group_options ) )
129+ end
130+
95131 def field_error ( attribute )
96132 return unless has_error? ( attribute )
97133
98134 @template . content_tag ( :span , object . errors [ attribute ] . to_sentence , class : "usa-error-message" )
99135 end
100136
101- def fieldset ( legend , attribute = nil , &block )
102- us_form_group ( attribute : attribute ) do
137+ def fieldset ( legend , options = { } , &block )
138+ legend_classes = "usa-legend"
139+
140+ if options [ :large_legend ]
141+ legend_classes += " usa-legend--large"
142+ end
143+
144+ form_group ( options [ :attribute ] ) do
103145 @template . content_tag ( :fieldset , class : "usa-fieldset" ) do
104- @template . content_tag ( :legend , legend , class : "usa-legend" ) + @template . capture ( &block )
146+ @template . content_tag ( :legend , legend , class : legend_classes ) + @template . capture ( &block )
105147 end
106148 end
107149 end
@@ -121,6 +163,17 @@ def hint(text)
121163 @template . content_tag ( :div , @template . raw ( text ) , class : "usa-hint" )
122164 end
123165
166+ def form_group ( attribute = nil , options = { } , &block )
167+ append_to_option ( options , :class , " usa-form-group" )
168+ children = @template . capture ( &block )
169+
170+ if options [ :show_error ] or ( attribute and has_error? ( attribute ) )
171+ append_to_option ( options , :class , " usa-form-group--error" )
172+ end
173+
174+ @template . content_tag ( :div , children , options )
175+ end
176+
124177 def yes_no ( attribute , options = { } )
125178 yes_options = options [ :yes_options ] || { }
126179 no_options = options [ :no_options ] || { }
@@ -132,7 +185,7 @@ def yes_no(attribute, options = {})
132185 @template . capture do
133186 # Hidden field included for same reason as radio button collections (https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_radio_buttons)
134187 hidden_field ( attribute , value : "" ) +
135- fieldset ( options [ :legend ] || human_name ( attribute ) , attribute ) do
188+ fieldset ( options [ :legend ] || human_name ( attribute ) , { attribute : attribute } ) do
136189 buttons =
137190 radio_button ( attribute , true , yes_options ) +
138191 radio_button ( attribute , false , no_options )
@@ -147,6 +200,16 @@ def yes_no(attribute, options = {})
147200 end
148201
149202 private
203+ def append_to_option ( options , key , value )
204+ current_value = options [ key ] || ""
205+
206+ if current_value . is_a? ( Proc )
207+ options [ key ] = -> { current_value . call + value }
208+ else
209+ options [ key ] = current_value + value
210+ end
211+ end
212+
150213 def us_class_for_field_type ( field_type , width = nil )
151214 case field_type
152215 when :check_box
@@ -167,15 +230,33 @@ def us_class_for_field_type(field_type, width = nil)
167230
168231 # Render the label, hint text, and error message for a form field
169232 def us_text_field_label ( attribute , text = nil , options = { } )
170- hint_text = options . delete ( :hint )
233+ hint_option = options . delete ( :hint )
234+ classes = "usa-label"
235+ for_attr = options [ :for ] || field_id ( attribute )
171236
172- if hint_text
173- hint_id = "#{ attribute } _hint"
174- options [ :aria_describedby ] = hint_id
175- hint = @template . content_tag ( :div , @template . raw ( hint_text ) , id : hint_id , class : "usa-hint" )
237+ if options [ :class ]
238+ classes += " #{ options [ :class ] } "
239+ end
240+
241+ unless text
242+ text = human_name ( attribute )
176243 end
177244
178- label ( attribute , text , { class : "usa-label" } ) + hint + field_error ( attribute )
245+ if options [ :optional ]
246+ text += @template . content_tag ( :span , " (#{ I18n . t ( 'us_form_with.optional' ) . downcase } )" , class : "usa-hint" )
247+ end
248+
249+ if hint_option
250+ if hint_option . is_a? ( Proc )
251+ hint_content = @template . capture ( &hint_option )
252+ else
253+ hint_content = @template . raw ( hint_option )
254+ end
255+
256+ hint = @template . content_tag ( :div , hint_content , id : hint_id ( attribute ) , class : "usa-hint" )
257+ end
258+
259+ label ( attribute , @template . raw ( text ) , { class : classes , for : for_attr } ) + field_error ( attribute ) + hint
179260 end
180261
181262 # Label for a checkbox or radio
@@ -192,11 +273,7 @@ def us_toggle_label(type, attribute, text = nil, options = {})
192273 label ( attribute , label_text , options )
193274 end
194275
195- def us_form_group ( attribute : nil , show_error : nil , &block )
196- children = @template . capture ( &block )
197- classes = "usa-form-group"
198- classes += " usa-form-group--error" if show_error or ( attribute and has_error? ( attribute ) )
199-
200- @template . content_tag ( :div , children , class : classes )
276+ def hint_id ( attribute )
277+ "#{ attribute } _hint"
201278 end
202279end
0 commit comments