shoelace-rails 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/app/helpers/shoelace/components/error_wrappable.rb +21 -0
  4. data/app/helpers/shoelace/components/sl_checkbox.rb +40 -0
  5. data/app/helpers/shoelace/components/sl_collection_radio_buttons.rb +47 -0
  6. data/app/helpers/shoelace/components/sl_color_picker.rb +30 -0
  7. data/app/helpers/shoelace/components/sl_input.rb +33 -0
  8. data/app/helpers/shoelace/components/sl_radio_button.rb +20 -0
  9. data/app/helpers/shoelace/components/sl_range.rb +22 -0
  10. data/app/helpers/shoelace/components/sl_select.rb +77 -0
  11. data/app/helpers/shoelace/components/sl_switch.rb +23 -0
  12. data/app/helpers/shoelace/components/sl_textarea.rb +20 -0
  13. data/app/helpers/shoelace/form_builder.rb +8 -0
  14. data/app/helpers/shoelace/sl_form_builder.rb +111 -0
  15. data/app/helpers/shoelace/sl_form_helper.rb +139 -0
  16. data/lib/shoelace/rails/version.rb +1 -1
  17. data/lib/shoelace/railtie.rb +2 -4
  18. data/test/helpers/form_builder/sl_checkbox_test.rb +45 -0
  19. data/test/helpers/form_builder/sl_color_picker_test.rb +26 -0
  20. data/test/helpers/form_builder/sl_input_test.rb +126 -0
  21. data/test/helpers/form_builder/sl_radio_group_test.rb +63 -0
  22. data/test/helpers/form_builder/sl_range_test.rb +34 -0
  23. data/test/helpers/form_builder/sl_select_test.rb +320 -0
  24. data/test/helpers/form_builder/sl_switch_test.rb +26 -0
  25. data/test/helpers/form_builder/sl_textarea_test.rb +72 -0
  26. data/test/helpers/form_builder_test.rb +16 -0
  27. data/test/helpers/form_helper_test.rb +3 -442
  28. data/test/helpers/translation_test.rb +2 -4
  29. data/test/test_helper.rb +22 -0
  30. metadata +25 -4
  31. data/app/helpers/shoelace/form_helper.rb +0 -408
@@ -1,408 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Shoelace
4
- module FormHelper
5
- class ShoelaceInputField < ActionView::Helpers::Tags::TextField #:nodoc:
6
- attr_reader :field_type
7
-
8
- def initialize(field_type, *args)
9
- super(*args)
10
- @field_type = field_type
11
- end
12
-
13
- def render(&block)
14
- options = @options.stringify_keys
15
-
16
- value = options.fetch("value") { value_before_type_cast }
17
- options["value"] = value if value.present?
18
-
19
- options["size"] = options["maxlength"] unless options.key?("size")
20
- options["type"] ||= field_type
21
- options["class"] ||= [options["class"], Shoelace.invalid_input_class_name].compact.join(" ") if @object.respond_to?(:errors) && @object.errors[@method_name].present?
22
-
23
- add_default_name_and_id(options)
24
-
25
- @template_object.content_tag('sl-input', '', options, &block)
26
- end
27
- end
28
-
29
- class ShoelaceColorPicker < ActionView::Helpers::Tags::ColorField #:nodoc:
30
- RGB_VALUE_REGEX = /#[0-9a-fA-F]{6}/
31
-
32
- def field_type; nil; end
33
-
34
- def tag(tag_name, *args, &block)
35
- tag_name.to_s == 'input' ? content_tag('sl-color-picker', '', *args, &block) : super
36
- end
37
-
38
- private
39
-
40
- def validate_color_string(string)
41
- string.downcase if RGB_VALUE_REGEX.match?(string)
42
- end
43
- end
44
-
45
- class ShoelaceRange < ActionView::Helpers::Tags::NumberField #:nodoc:
46
- def field_type; nil; end
47
-
48
- def tag(tag_name, *args, &block)
49
- tag_name.to_s == 'input' ? content_tag('sl-range', '', *args, &block) : super
50
- end
51
- end
52
-
53
- class ShoelaceSwitch < ActionView::Helpers::Tags::TextField #:nodoc:
54
- def field_type; nil; end
55
-
56
- def render(&block)
57
- options = @options.stringify_keys
58
- options["value"] = options.fetch("value") { value_before_type_cast }
59
- add_default_name_and_id(options)
60
- label = options.delete('label').presence || @method_name.to_s.humanize
61
-
62
- @template_object.content_tag('sl-switch', label, options, &block)
63
- end
64
- end
65
-
66
- class ShoelaceTextArea < ActionView::Helpers::Tags::TextArea #:nodoc:
67
- def render(&block)
68
- options = @options.stringify_keys
69
- options["value"] = options.fetch("value") { value_before_type_cast }
70
- add_default_name_and_id(options)
71
-
72
- @template_object.content_tag("sl-textarea", '', options, &block)
73
- end
74
- end
75
-
76
- class ShoelaceSelect < ActionView::Helpers::Tags::Select #:nodoc:
77
- def grouped_options_for_select(grouped_options, options)
78
- @template_object.grouped_sl_options_for_select(grouped_options, options)
79
- end
80
-
81
- def options_for_select(container, options = nil)
82
- @template_object.sl_options_for_select(container, options)
83
- end
84
-
85
- def select_content_tag(option_tags, _options, html_options)
86
- html_options = html_options.stringify_keys
87
- html_options['value'] ||= value
88
- add_default_name_and_id(html_options)
89
-
90
- @template_object.content_tag("sl-select", option_tags, html_options)
91
- end
92
- end
93
-
94
- class ShoelaceCollectionSelect < ActionView::Helpers::Tags::CollectionSelect #:nodoc:
95
- def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
96
- @template_object.sl_options_from_collection_for_select(collection, value_method, text_method, selected)
97
- end
98
-
99
- def select_content_tag(option_tags, _options, html_options)
100
- html_options = html_options.stringify_keys
101
- html_options['value'] ||= value
102
- add_default_name_and_id(html_options)
103
-
104
- @template_object.content_tag("sl-select", option_tags, html_options)
105
- end
106
- end
107
-
108
- class ShoelaceGroupedCollectionSelect < ActionView::Helpers::Tags::GroupedCollectionSelect #:nodoc:
109
- def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
110
- @template_object.sl_option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key)
111
- end
112
-
113
- def select_content_tag(option_tags, _options, html_options)
114
- html_options = html_options.stringify_keys
115
- html_options['value'] ||= value
116
- add_default_name_and_id(html_options)
117
-
118
- @template_object.content_tag("sl-select", option_tags, html_options)
119
- end
120
- end
121
-
122
- class ShoelaceCheckBox < ActionView::Helpers::Tags::CheckBox #:nodoc:
123
- def render(&block)
124
- options = @options.stringify_keys
125
- options["value"] = @checked_value
126
- options["checked"] = true if input_checked?(options)
127
- label = options.delete("label") || @method_name.to_s.humanize
128
-
129
- if options["multiple"]
130
- add_default_name_and_id_for_value(@checked_value, options)
131
- options.delete("multiple")
132
- else
133
- add_default_name_and_id(options)
134
- end
135
-
136
- include_hidden = options.delete("include_hidden") { true }
137
-
138
- sl_checkbox_tag = if block_given?
139
- @template_object.content_tag('sl-checkbox', '', options, &block)
140
- else
141
- @template_object.content_tag('sl-checkbox', label || @method_name.to_s.humanize, options)
142
- end
143
-
144
- if include_hidden
145
- hidden_field_for_checkbox(options) + sl_checkbox_tag
146
- else
147
- sl_checkbox_tag
148
- end
149
- end
150
- end
151
-
152
- class ShoelaceRadioButton < ActionView::Helpers::Tags::RadioButton #:nodoc:
153
- def render(&block)
154
- options = @options.stringify_keys
155
- options["value"] = @tag_value
156
- add_default_name_and_id_for_value(@tag_value, options)
157
- options.delete("name")
158
-
159
- @template_object.content_tag('sl-radio', '', options.except("type"), &block)
160
- end
161
- end
162
-
163
- class ShoelaceCollectionRadioButtons < ActionView::Helpers::Tags::CollectionRadioButtons #:nodoc:
164
- class RadioButtonBuilder < Builder # :nodoc:
165
- def label(*)
166
- text
167
- end
168
-
169
- def radio_button(extra_html_options = {}, &block)
170
- html_options = extra_html_options.merge(@input_html_options)
171
- html_options[:skip_default_ids] = false
172
- @template_object.sl_radio_button(@object_name, @method_name, @value, html_options, &block)
173
- end
174
- end
175
-
176
- def render(&block)
177
- render_collection_for(RadioButtonBuilder, &block)
178
- end
179
-
180
- private
181
-
182
- def render_collection(&block)
183
- html_options = @html_options.stringify_keys
184
- html_options["value"] = value
185
- add_default_name_and_id(html_options)
186
- html_options["label"] = @options[:label].presence || @method_name.to_s.humanize
187
-
188
- @template_object.content_tag('sl-radio-group', html_options) { super(&block) }
189
- end
190
-
191
- def hidden_field
192
- ''.html_safe
193
- end
194
-
195
- def render_component(builder)
196
- builder.radio_button { builder.label }
197
- end
198
- end
199
-
200
- class ShoelaceFormBuilder < ActionView::Helpers::FormBuilder #:nodoc:
201
- {
202
- email: :email,
203
- number: :number,
204
- password: :password,
205
- search: :search,
206
- telephone: :tel,
207
- phone: :tel,
208
- text: :text,
209
- url: :url
210
- }.each do |field_type, field_class|
211
- # def email_field(method, **options, &block)
212
- # ShoelaceInputField.new(:email, object_name, method, @template, options.with_defaults(label: label_text(method))).render(&block)
213
- # end
214
- eval <<-RUBY, nil, __FILE__, __LINE__ + 1
215
- def #{field_type}_field(method, **options, &block)
216
- ShoelaceInputField.new(:#{field_class}, object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render(&block)
217
- end
218
- RUBY
219
- end
220
-
221
- def color_field(method, **options)
222
- ShoelaceColorPicker.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render
223
- end
224
- alias color_picker color_field
225
-
226
- def range_field(method, **options)
227
- ShoelaceRange.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render
228
- end
229
- alias range range_field
230
-
231
- def switch_field(method, **options, &block)
232
- if block_given?
233
- ShoelaceSwitch.new(object_name, method, @template, options.with_defaults(object: @object)).render(&block)
234
- else
235
- ShoelaceSwitch.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render(&block)
236
- end
237
- end
238
- alias switch switch_field
239
-
240
- def text_area(method, **options, &block)
241
- ShoelaceTextArea.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method), resize: 'auto')).render(&block)
242
- end
243
-
244
- def check_box(method, options = {}, checked_value = "1", unchecked_value = "0", &block)
245
- ShoelaceCheckBox.new(object_name, method, @template, checked_value, unchecked_value, options.with_defaults(label: label_text(method)).merge(object: @object)).render(&block)
246
- end
247
-
248
- def select(method, choices = nil, options = {}, html_options = {}, &block)
249
- ShoelaceSelect.new(object_name, method, @template, choices, options.with_defaults(object: @object), html_options.with_defaults(label: label_text(method)), &block).render
250
- end
251
-
252
- def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
253
- ShoelaceCollectionSelect.new(object_name, method, @template, collection, value_method, text_method, options.with_defaults(object: @object), html_options.with_defaults(label: label_text(method)), &block).render
254
- end
255
-
256
- def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
257
- ShoelaceGroupedCollectionSelect.new(object_name, method, @template, collection, group_method, group_label_method, option_key_method, option_value_method, options.with_defaults(object: @object), html_options.with_defaults(label: label_text(method))).render
258
- end
259
-
260
- def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
261
- ShoelaceCollectionRadioButtons.new(object_name, method, @template, collection, value_method, text_method, options.with_defaults(object: @object, label: label_text(method)), html_options).render(&block)
262
- end
263
-
264
- def submit(value = nil, options = {})
265
- value, options = nil, value if value.is_a?(Hash)
266
-
267
- @template.sl_submit_tag(value || submit_default_value, **options)
268
- end
269
-
270
- private
271
-
272
- def label_text(method, tag_value = nil)
273
- ::ActionView::Helpers::Tags::Label::LabelBuilder.new(@template, object_name.to_s, method.to_s, object, tag_value).translation
274
- end
275
- end
276
-
277
- DEFAULT_FORM_PARAMETERS = { builder: ShoelaceFormBuilder }
278
- DIVIDER_TAG = "<sl-divider></sl-divider>".html_safe
279
-
280
- private_constant :DEFAULT_FORM_PARAMETERS, :DIVIDER_TAG
281
-
282
- def sl_form_for(*args, **options, &block)
283
- form_for(*args, **DEFAULT_FORM_PARAMETERS, **options, &block)
284
- end
285
-
286
- def sl_form_with(**args, &block)
287
- form_with(**args, **DEFAULT_FORM_PARAMETERS, &block)
288
- end
289
-
290
- # Creates a submit button with the text value as the caption, with the +submit+ attribute.
291
- def sl_submit_tag(value = 'Save changes', **options)
292
- options = options.deep_stringify_keys
293
- tag_options = { "type" => "submit", "variant" => "primary" }.update(options)
294
- set_default_disable_with(value, tag_options)
295
-
296
- content_tag('sl-button', value, tag_options)
297
- end
298
-
299
- # Creates a shoelace text field; use these text fields to input smaller chunks of text like a username or a search
300
- # query.
301
- #
302
- # For the properties available on this tag, please refer to the official documentation:
303
- # https://shoelace.style/components/input?id=properties
304
- #
305
- def sl_text_field_tag(name, value = nil, **options, &block)
306
- content_tag('sl-input', '', { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys), &block)
307
- end
308
-
309
- # Returns a string of +<sl-option>+ tags, like +options_for_select+, but prepends a +<small>+ tag to
310
- # each group.
311
- def grouped_sl_options_for_select(grouped_options, options)
312
- body = "".html_safe
313
-
314
- grouped_options.each_with_index do |container, index|
315
- label, values = container
316
-
317
- body.safe_concat(DIVIDER_TAG) if index > 0
318
- body.safe_concat(content_tag("small", label)) if label.present?
319
- body.safe_concat(sl_options_for_select(values, options))
320
- end
321
-
322
- body
323
- end
324
-
325
- # Accepts an enumerable (hash, array, enumerable, your type) and returns a string of +sl-option+ tags. Given
326
- # an enumerable where the elements respond to +first+ and +last+ (such as a two-element array), the “lasts” serve
327
- # as option values and the “firsts” as option text.
328
- def sl_options_for_select(enumerable, options = nil)
329
- return enumerable if String === enumerable
330
-
331
- selected, disabled = extract_selected_and_disabled(options).map { |r| Array(r).map(&:to_s) }
332
-
333
- enumerable.map do |element|
334
- html_attributes = option_html_attributes(element)
335
- text, value = option_text_and_value(element).map(&:to_s)
336
-
337
- html_attributes[:checked] ||= selected.include?(value)
338
- html_attributes[:disabled] ||= disabled.include?(value)
339
- html_attributes[:value] = value
340
-
341
- tag_builder.content_tag_string('sl-option', text, html_attributes)
342
- end.join("\n").html_safe
343
- end
344
-
345
- def sl_option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
346
- body = "".html_safe
347
-
348
- collection.each_with_index do |group, index|
349
- option_tags = sl_options_from_collection_for_select(value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
350
-
351
- body.safe_concat(DIVIDER_TAG) if index > 0
352
- body.safe_concat(content_tag("small", value_for_collection(group, group_label_method)))
353
- body.safe_concat(option_tags)
354
- end
355
-
356
- body
357
- end
358
-
359
- # Returns a string of +<sl-option>+ tags compiled by iterating over the collection and assigning the result of
360
- # a call to the +value_method+ as the option value and the +text_method+ as the option text.
361
- def sl_options_from_collection_for_select(collection, value_method, text_method, selected = nil)
362
- options = collection.map do |element|
363
- [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)]
364
- end
365
-
366
- selected, disabled = extract_selected_and_disabled(selected)
367
-
368
- select_deselect = {
369
- selected: extract_values_from_collection(collection, value_method, selected),
370
- disabled: extract_values_from_collection(collection, value_method, disabled)
371
- }
372
-
373
- sl_options_for_select(options, select_deselect)
374
- end
375
-
376
- # Returns a +<sl-radio>+ tag for accessing a specified attribute (identified by method) on an object assigned to
377
- # the template (identified by object). If the current value of method is +tag_value+ the radio button will be
378
- # checked.
379
- #
380
- # To force the radio button to be checked pass checked: true in the options hash. You may pass HTML options there
381
- # as well.
382
- def sl_radio_button(object_name, method, tag_value, options = {}, &block)
383
- ShoelaceRadioButton.new(object_name, method, self, tag_value, options).render(&block)
384
- end
385
-
386
- {
387
- email: :email,
388
- number: :number,
389
- password: :password,
390
- search: :search,
391
- telephone: :tel,
392
- phone: :tel,
393
- url: :url
394
- }.each do |field_type, field_class|
395
- # def sl_email_field_tag(method, **options, &block)
396
- # sl_text_field_tag(name, value, options.merge(type: :email))
397
- # end
398
- eval <<-RUBY, nil, __FILE__, __LINE__ + 1
399
- # Creates a text field of type “#{field_type}”.
400
- def sl_#{field_type}_field_tag(method, **options, &block)
401
- sl_text_field_tag(name, value, options.merge(type: :#{field_class}))
402
- end
403
- RUBY
404
- end
405
- end
406
-
407
- FormBuilder = FormHelper::ShoelaceFormBuilder
408
- end