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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ead8cbfce57323de6e03b0b4dc638eac355b3a9e7495747a17aaf79682e81ed
4
- data.tar.gz: 9c84316ec90e6a29fb270b3d0c56d9dbc93b947c51aab3fa3c07bac5620f4399
3
+ metadata.gz: 43e6f607b577eb1c8619ca818ad1e1ad29077d948789579a109a740273b1ba6e
4
+ data.tar.gz: 266799cb74342d48ac0fb97e9fd9a902c84fe5dfc468c5f5fdf7c8ec98872759
5
5
  SHA512:
6
- metadata.gz: 511f5b435164a36c9fe094ebdc905d05c3759cf469ec9559b9fd437171291b3b0b07361ecc674d120ed5b59b0683044582f1d5726c50f05df0e57ff972a185fc
7
- data.tar.gz: 74f6c3f146f2aea79e9cce672c36840c4f416adf6ea14748dbfdf63ca0c92bbb1ad9041407df6ca91c93fd0334e6cb12e2e038fcb18d3eb13cdd970413459262
6
+ metadata.gz: e11ac8b6752b1c8cb9303b972008f088e57e56dfb022c36d6ff4ae5784102df6a0afc1143101fc497c1926ff6f494608d13738759347c9f767676c6fc76c0589
7
+ data.tar.gz: 6c8f7506829f93b98e673701cf290e5f116a8f283f5abb42c2507f94c248b12223795e7670530dadd1d812e5f3999fa7bccc212f8c85a69a0b3da8a32231e542
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## Unreleased
2
+
3
+ #### 🚨 Breaking Changes
4
+
5
+ - Deprecate `config.shoelace.invalid_input_class_name` in favor of `data-invalid` and `data-use-invalid`
6
+
7
+ #### ⭐️ Features
8
+
9
+ - Add `invalid` and `data-invalid` to the `Shoelace::FormBuilder`
10
+ - Add the ability to specify a method for rendering slot for input components
11
+ - Add `FormBuilder#date_field` method
12
+
1
13
  ## v0.6.2
2
14
 
3
15
  #### 🐞Bug Fixes
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoelace
4
+ module Components
5
+ module ErrorWrappable
6
+ def error_wrapping(html_tag)
7
+ if object_has_errors? && field_error_proc
8
+ @template_object.instance_exec(html_tag, self, &field_error_proc)
9
+ else
10
+ html_tag
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def field_error_proc
17
+ Shoelace::SlFormBuilder.field_error_proc
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlCheckbox < ActionView::Helpers::Tags::CheckBox #:nodoc:
8
+ include ErrorWrappable
9
+
10
+ def render(&block)
11
+ options = @options.stringify_keys
12
+ options["value"] = @checked_value
13
+ options["checked"] = true if input_checked?(options)
14
+ options["invalid"] = options["data-invalid"] = "" if object_has_errors?
15
+ label = options.delete("label")
16
+
17
+ if options["multiple"]
18
+ add_default_name_and_id_for_value(@checked_value, options)
19
+ options.delete("multiple")
20
+ else
21
+ add_default_name_and_id(options)
22
+ end
23
+
24
+ include_hidden = options.delete("include_hidden") { true }
25
+
26
+ sl_checkbox_tag = if block_given?
27
+ @template_object.content_tag('sl-checkbox', '', options, &block)
28
+ else
29
+ @template_object.content_tag('sl-checkbox', label, options)
30
+ end
31
+
32
+ if include_hidden
33
+ hidden_field_for_checkbox(options) + sl_checkbox_tag
34
+ else
35
+ sl_checkbox_tag
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlCollectionRadioButtons < ActionView::Helpers::Tags::CollectionRadioButtons #:nodoc:
8
+ include ErrorWrappable
9
+
10
+ class RadioButtonBuilder < Builder # :nodoc:
11
+ def label(*)
12
+ text
13
+ end
14
+
15
+ def radio_button(extra_html_options = {}, &block)
16
+ html_options = extra_html_options.merge(@input_html_options)
17
+ html_options[:skip_default_ids] = false
18
+ @template_object.sl_radio_button(@object_name, @method_name, @value, html_options, &block)
19
+ end
20
+ end
21
+
22
+ def render(&block)
23
+ render_collection_for(RadioButtonBuilder, &block)
24
+ end
25
+
26
+ private
27
+
28
+ def render_collection(&block)
29
+ html_options = @html_options.stringify_keys
30
+ html_options["value"] = value
31
+ add_default_name_and_id(html_options)
32
+ html_options["label"] = @options[:label].presence
33
+ html_options["invalid"] = html_options["data-invalid"] = "" if object_has_errors?
34
+
35
+ @template_object.content_tag('sl-radio-group', html_options) { super(&block) }
36
+ end
37
+
38
+ def hidden_field
39
+ ''.html_safe
40
+ end
41
+
42
+ def render_component(builder)
43
+ builder.radio_button { builder.label }
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlColorPicker < ActionView::Helpers::Tags::ColorField #:nodoc:
8
+ RGB_VALUE_REGEX = /#[0-9a-fA-F]{6}/.freeze
9
+
10
+ include ErrorWrappable
11
+
12
+ def field_type; nil; end
13
+
14
+ def tag(tag_name, *args, &block)
15
+ tag_name.to_s == 'input' ? content_tag('sl-color-picker', '', *args, &block) : super
16
+ end
17
+
18
+ def render
19
+ @options["invalid"] = @options["data-invalid"] = "" if object_has_errors?
20
+ super
21
+ end
22
+
23
+ private
24
+
25
+ def validate_color_string(string)
26
+ string.downcase if RGB_VALUE_REGEX.match?(string)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlInput < ActionView::Helpers::Tags::TextField #:nodoc:
8
+ include ErrorWrappable
9
+
10
+ attr_reader :field_type
11
+
12
+ def initialize(field_type, *args)
13
+ super(*args)
14
+ @field_type = field_type
15
+ end
16
+
17
+ def render(&block)
18
+ options = @options.stringify_keys
19
+
20
+ value = options.fetch("value") { value_before_type_cast }
21
+ options["value"] = value if value.present?
22
+
23
+ options["size"] = options["maxlength"] unless options.key?("size")
24
+ options["type"] ||= field_type
25
+ options["invalid"] = options["data-invalid"] = "" if object_has_errors?
26
+
27
+ add_default_name_and_id(options)
28
+
29
+ @template_object.content_tag('sl-input', '', options, &block)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlRadioButton < ActionView::Helpers::Tags::RadioButton #:nodoc:
8
+ include ErrorWrappable
9
+
10
+ def render(&block)
11
+ options = @options.stringify_keys
12
+ options["value"] = @tag_value
13
+ add_default_name_and_id_for_value(@tag_value, options)
14
+ options.delete("name")
15
+
16
+ @template_object.content_tag('sl-radio', '', options.except("type"), &block)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlRange < ActionView::Helpers::Tags::NumberField #:nodoc:
8
+ include ErrorWrappable
9
+
10
+ def field_type; nil; end
11
+
12
+ def render
13
+ @options["invalid"] = @options["data-invalid"] = "" if object_has_errors?
14
+ super
15
+ end
16
+
17
+ def tag(tag_name, *args, &block)
18
+ tag_name.to_s == 'input' ? content_tag('sl-range', '', *args, &block) : super
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ module SlSelectRenderable
8
+ EMPTY = "".html_safe.freeze
9
+
10
+ def select_content_tag(option_tags, _options, html_options)
11
+ html_options = html_options.stringify_keys
12
+ html_options['value'] ||= value
13
+ html_options["invalid"] = html_options["data-invalid"] = "" if object_has_errors?
14
+ add_default_name_and_id(html_options)
15
+
16
+ @template_object.content_tag("sl-select", html_options) do
17
+ "".html_safe.tap do |html|
18
+ html.safe_concat(option_tags)
19
+ html.safe_concat(@template_object.capture(&slot) || EMPTY) if slot
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ class SlSelect < ActionView::Helpers::Tags::Select #:nodoc:
26
+ include ErrorWrappable
27
+ include SlSelectRenderable
28
+
29
+ attr_reader :slot
30
+
31
+ def initialize(object_name, method_name, template_object, choices, options, html_options, slot)
32
+ @slot = slot
33
+ super(object_name, method_name, template_object, choices, options, html_options)
34
+ end
35
+
36
+ def grouped_options_for_select(grouped_options, options)
37
+ @template_object.grouped_sl_options_for_select(grouped_options, options)
38
+ end
39
+
40
+ def options_for_select(container, options = nil)
41
+ @template_object.sl_options_for_select(container, options)
42
+ end
43
+ end
44
+
45
+ class SlCollectionSelect < ActionView::Helpers::Tags::CollectionSelect #:nodoc:
46
+ include ErrorWrappable
47
+ include SlSelectRenderable
48
+
49
+ attr_reader :slot
50
+
51
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options, slot)
52
+ @slot = slot
53
+ super(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
54
+ end
55
+
56
+ def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
57
+ @template_object.sl_options_from_collection_for_select(collection, value_method, text_method, selected)
58
+ end
59
+ end
60
+
61
+ class SlGroupedCollectionSelect < ActionView::Helpers::Tags::GroupedCollectionSelect #:nodoc:
62
+ include ErrorWrappable
63
+ include SlSelectRenderable
64
+
65
+ attr_reader :slot
66
+
67
+ def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options, slot)
68
+ @slot = slot
69
+ super(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
70
+ end
71
+
72
+ def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
73
+ @template_object.sl_option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlSwitch < ActionView::Helpers::Tags::NumberField #:nodoc:
8
+ include ErrorWrappable
9
+
10
+ def field_type; nil; end
11
+
12
+ def render(&block)
13
+ options = @options.stringify_keys
14
+ options["value"] = options.fetch("value") { value_before_type_cast }
15
+ add_default_name_and_id(options)
16
+ label = options.delete('label').presence
17
+ options["invalid"] = options["data-invalid"] = "" if object_has_errors?
18
+
19
+ content_tag('sl-switch', label, options, &block)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './error_wrappable'
4
+
5
+ module Shoelace
6
+ module Components
7
+ class SlTextarea < ActionView::Helpers::Tags::NumberField #:nodoc:
8
+ include ErrorWrappable
9
+
10
+ def render(&block)
11
+ options = @options.stringify_keys
12
+ options["value"] = options.fetch("value") { value_before_type_cast }
13
+ options["invalid"] = options["data-invalid"] = "" if object_has_errors?
14
+ add_default_name_and_id(options)
15
+
16
+ @template_object.content_tag("sl-textarea", '', options, &block)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './sl_form_builder'
4
+
5
+ module Shoelace
6
+ FormBuilder = SlFormBuilder
7
+ deprecate_constant :FormBuilder
8
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './components/sl_checkbox'
4
+ require_relative './components/sl_collection_radio_buttons'
5
+ require_relative './components/sl_color_picker'
6
+ require_relative './components/sl_input'
7
+ require_relative './components/sl_radio_button'
8
+ require_relative './components/sl_range'
9
+ require_relative './components/sl_select'
10
+ require_relative './components/sl_switch'
11
+ require_relative './components/sl_textarea'
12
+
13
+ module Shoelace
14
+ class SlFormBuilder < ActionView::Helpers::FormBuilder #:nodoc:
15
+ # Same concept as the Rails field_error_proc, but for Shoelace components. Set to `nil` to avoid messing up the
16
+ # Shoelace components by default.
17
+ cattr_accessor :field_error_proc
18
+
19
+ # Similar concept to the Rails field_error_proc, but for Shoelace's commonly used `help-text slot`.
20
+ cattr_accessor :default_input_slot_method
21
+
22
+ {
23
+ date: :date,
24
+ email: :email,
25
+ number: :number,
26
+ password: :password,
27
+ search: :search,
28
+ telephone: :tel,
29
+ phone: :tel,
30
+ text: :text,
31
+ url: :url
32
+ }.each do |field_type, field_class|
33
+ # def email_field(method, **options, &block)
34
+ # slot = wrap_with_default_slot(method, &block)
35
+ # Components::SlInput.new(:email, object_name, method, @template, options.with_defaults(label: label_text(method))).render(&block)
36
+ # end
37
+ eval <<-RUBY, nil, __FILE__, __LINE__ + 1
38
+ def #{field_type}_field(method, **options, &block)
39
+ slot = wrap_with_default_slot(method, &block)
40
+ Components::SlInput.new(:#{field_class}, object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render(&slot)
41
+ end
42
+ RUBY
43
+ end
44
+
45
+ def color_field(method, **options)
46
+ Components::SlColorPicker.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render
47
+ end
48
+ alias color_picker color_field
49
+
50
+ def range_field(method, **options)
51
+ Components::SlRange.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render
52
+ end
53
+ alias range range_field
54
+
55
+ def switch_field(method, **options, &block)
56
+ if block_given?
57
+ Components::SlSwitch.new(object_name, method, @template, options.with_defaults(object: @object)).render(&block)
58
+ else
59
+ Components::SlSwitch.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method))).render(&block)
60
+ end
61
+ end
62
+ alias switch switch_field
63
+
64
+ def text_area(method, **options, &block)
65
+ Components::SlTextarea.new(object_name, method, @template, options.with_defaults(object: @object, label: label_text(method), resize: 'auto')).render(&block)
66
+ end
67
+
68
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0", &block)
69
+ Components::SlCheckbox.new(object_name, method, @template, checked_value, unchecked_value, options.with_defaults(label: label_text(method)).merge(object: @object)).render(&block)
70
+ end
71
+
72
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
73
+ slot = wrap_with_default_slot(method, &block)
74
+ Components::SlSelect.new(object_name, method, @template, choices, options.with_defaults(object: @object), html_options.with_defaults(label: label_text(method)), slot).render
75
+ end
76
+
77
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
78
+ slot = wrap_with_default_slot(method, &block)
79
+ Components::SlCollectionSelect.new(object_name, method, @template, collection, value_method, text_method, options.with_defaults(object: @object), html_options.with_defaults(label: label_text(method)), slot).render
80
+ end
81
+
82
+ def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}, &block)
83
+ slot = wrap_with_default_slot(method, &block)
84
+ Components::SlGroupedCollectionSelect.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)), slot).render
85
+ end
86
+
87
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
88
+ Components::SlCollectionRadioButtons.new(object_name, method, @template, collection, value_method, text_method, options.with_defaults(object: @object, label: label_text(method)), html_options).render(&block)
89
+ end
90
+
91
+ def submit(value = nil, options = {})
92
+ value, options = nil, value if value.is_a?(Hash)
93
+
94
+ @template.sl_submit_tag(value || submit_default_value, **options)
95
+ end
96
+
97
+ private
98
+
99
+ def label_text(method, tag_value = nil)
100
+ ::ActionView::Helpers::Tags::Label::LabelBuilder.new(@template, object_name.to_s, method.to_s, object, tag_value).translation
101
+ end
102
+
103
+ def wrap_with_default_slot(method_name, &user_defined_slot)
104
+ if default_input_slot_method && !block_given?
105
+ -> { @template.method(default_input_slot_method).call(object, method_name) }
106
+ else
107
+ user_defined_slot
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './sl_form_builder'
4
+
5
+ module Shoelace
6
+ module SlFormHelper
7
+ ShoelaceFormBuilder = SlFormBuilder
8
+ deprecate_constant :ShoelaceFormBuilder
9
+
10
+ DEFAULT_FORM_PARAMETERS = { builder: SlFormBuilder }
11
+ DIVIDER_TAG = "<sl-divider></sl-divider>".html_safe
12
+
13
+ private_constant :DEFAULT_FORM_PARAMETERS, :DIVIDER_TAG
14
+
15
+ def sl_form_for(*args, **options, &block)
16
+ form_for(*args, **DEFAULT_FORM_PARAMETERS, **options, &block)
17
+ end
18
+
19
+ def sl_form_with(**args, &block)
20
+ form_with(**args, **DEFAULT_FORM_PARAMETERS, &block)
21
+ end
22
+
23
+ # Creates a submit button with the text value as the caption, with the +submit+ attribute.
24
+ def sl_submit_tag(value = 'Save changes', **options)
25
+ options = options.deep_stringify_keys
26
+ tag_options = { "type" => "submit", "variant" => "primary" }.update(options)
27
+ set_default_disable_with(value, tag_options)
28
+
29
+ content_tag('sl-button', value, tag_options)
30
+ end
31
+
32
+ # Creates a shoelace text field; use these text fields to input smaller chunks of text like a username or a search
33
+ # query.
34
+ #
35
+ # For the properties available on this tag, please refer to the official documentation:
36
+ # https://shoelace.style/components/input?id=properties
37
+ #
38
+ def sl_text_field_tag(name, value = nil, **options, &block)
39
+ content_tag('sl-input', '', { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys), &block)
40
+ end
41
+
42
+ # Returns a string of +<sl-option>+ tags, like +options_for_select+, but prepends a +<small>+ tag to
43
+ # each group.
44
+ def grouped_sl_options_for_select(grouped_options, options)
45
+ body = "".html_safe
46
+
47
+ grouped_options.each_with_index do |container, index|
48
+ label, values = container
49
+
50
+ body.safe_concat(DIVIDER_TAG) if index > 0
51
+ body.safe_concat(content_tag("small", label)) if label.present?
52
+ body.safe_concat(sl_options_for_select(values, options))
53
+ end
54
+
55
+ body
56
+ end
57
+
58
+ # Accepts an enumerable (hash, array, enumerable, your type) and returns a string of +sl-option+ tags. Given
59
+ # an enumerable where the elements respond to +first+ and +last+ (such as a two-element array), the “lasts” serve
60
+ # as option values and the “firsts” as option text.
61
+ def sl_options_for_select(enumerable, options = nil)
62
+ return enumerable if String === enumerable
63
+
64
+ selected, disabled = extract_selected_and_disabled(options).map { |r| Array(r).map(&:to_s) }
65
+
66
+ enumerable.map do |element|
67
+ html_attributes = option_html_attributes(element)
68
+ text, value = option_text_and_value(element).map(&:to_s)
69
+
70
+ html_attributes[:checked] ||= selected.include?(value)
71
+ html_attributes[:disabled] ||= disabled.include?(value)
72
+ html_attributes[:value] = value
73
+
74
+ tag_builder.content_tag_string('sl-option', text, html_attributes)
75
+ end.join("\n").html_safe
76
+ end
77
+
78
+ def sl_option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
79
+ body = "".html_safe
80
+
81
+ collection.each_with_index do |group, index|
82
+ option_tags = sl_options_from_collection_for_select(value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
83
+
84
+ body.safe_concat(DIVIDER_TAG) if index > 0
85
+ body.safe_concat(content_tag("small", value_for_collection(group, group_label_method)))
86
+ body.safe_concat(option_tags)
87
+ end
88
+
89
+ body
90
+ end
91
+
92
+ # Returns a string of +<sl-option>+ tags compiled by iterating over the collection and assigning the result of
93
+ # a call to the +value_method+ as the option value and the +text_method+ as the option text.
94
+ def sl_options_from_collection_for_select(collection, value_method, text_method, selected = nil)
95
+ options = collection.map do |element|
96
+ [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)]
97
+ end
98
+
99
+ selected, disabled = extract_selected_and_disabled(selected)
100
+
101
+ select_deselect = {
102
+ selected: extract_values_from_collection(collection, value_method, selected),
103
+ disabled: extract_values_from_collection(collection, value_method, disabled)
104
+ }
105
+
106
+ sl_options_for_select(options, select_deselect)
107
+ end
108
+
109
+ # Returns a +<sl-radio>+ tag for accessing a specified attribute (identified by method) on an object assigned to
110
+ # the template (identified by object). If the current value of method is +tag_value+ the radio button will be
111
+ # checked.
112
+ #
113
+ # To force the radio button to be checked pass checked: true in the options hash. You may pass HTML options there
114
+ # as well.
115
+ def sl_radio_button(object_name, method, tag_value, options = {}, &block)
116
+ Components::SlRadioButton.new(object_name, method, self, tag_value, options).render(&block)
117
+ end
118
+
119
+ {
120
+ email: :email,
121
+ number: :number,
122
+ password: :password,
123
+ search: :search,
124
+ telephone: :tel,
125
+ phone: :tel,
126
+ url: :url
127
+ }.each do |field_type, field_class|
128
+ # def sl_email_field_tag(method, **options, &block)
129
+ # sl_text_field_tag(name, value, options.merge(type: :email))
130
+ # end
131
+ eval <<-RUBY, nil, __FILE__, __LINE__ + 1
132
+ # Creates a text field of type “#{field_type}”.
133
+ def sl_#{field_type}_field_tag(method, **options, &block)
134
+ sl_text_field_tag(name, value, options.merge(type: :#{field_class}))
135
+ end
136
+ RUBY
137
+ end
138
+ end
139
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Shoelace
4
4
  module Rails
5
- VERSION = "0.6.2"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
@@ -30,7 +30,7 @@ module Shoelace
30
30
  # Path to the shoelace assets.
31
31
  config.shoelace.dist_path = "node_modules/@shoelace-style/shoelace/dist"
32
32
 
33
- # Class name that is added to a form input when the corresponding attribute has an `ActiveModel` error.
33
+ # Deprecated.
34
34
  config.shoelace.invalid_input_class_name = nil
35
35
 
36
36
  initializer "shoelace.use_rack_middleware" do |app|
@@ -45,9 +45,7 @@ module Shoelace
45
45
  end
46
46
 
47
47
  initializer "shoelace.form_helper" do |app|
48
- ActiveSupport.on_load :action_view do
49
- Shoelace.invalid_input_class_name = app.config.shoelace.invalid_input_class_name
50
- end
48
+ # This exists only for maintaining backwards compatibility with the `invalid_input_class_name` option.
51
49
  end
52
50
 
53
51
  rake_tasks do