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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/app/helpers/shoelace/components/error_wrappable.rb +21 -0
- data/app/helpers/shoelace/components/sl_checkbox.rb +40 -0
- data/app/helpers/shoelace/components/sl_collection_radio_buttons.rb +47 -0
- data/app/helpers/shoelace/components/sl_color_picker.rb +30 -0
- data/app/helpers/shoelace/components/sl_input.rb +33 -0
- data/app/helpers/shoelace/components/sl_radio_button.rb +20 -0
- data/app/helpers/shoelace/components/sl_range.rb +22 -0
- data/app/helpers/shoelace/components/sl_select.rb +77 -0
- data/app/helpers/shoelace/components/sl_switch.rb +23 -0
- data/app/helpers/shoelace/components/sl_textarea.rb +20 -0
- data/app/helpers/shoelace/form_builder.rb +8 -0
- data/app/helpers/shoelace/sl_form_builder.rb +111 -0
- data/app/helpers/shoelace/sl_form_helper.rb +139 -0
- data/lib/shoelace/rails/version.rb +1 -1
- data/lib/shoelace/railtie.rb +2 -4
- data/test/helpers/form_builder/sl_checkbox_test.rb +45 -0
- data/test/helpers/form_builder/sl_color_picker_test.rb +26 -0
- data/test/helpers/form_builder/sl_input_test.rb +126 -0
- data/test/helpers/form_builder/sl_radio_group_test.rb +63 -0
- data/test/helpers/form_builder/sl_range_test.rb +34 -0
- data/test/helpers/form_builder/sl_select_test.rb +320 -0
- data/test/helpers/form_builder/sl_switch_test.rb +26 -0
- data/test/helpers/form_builder/sl_textarea_test.rb +72 -0
- data/test/helpers/form_builder_test.rb +16 -0
- data/test/helpers/form_helper_test.rb +3 -442
- data/test/helpers/translation_test.rb +2 -4
- data/test/test_helper.rb +22 -0
- metadata +25 -4
- 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
|