stimulus_plumbers 0.3.3 → 0.4.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +273 -0
  4. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +228 -145
  5. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
  6. data/app/assets/stylesheets/stimulus_plumbers/tokens.css +43 -7
  7. data/config/locales/en.yml +10 -0
  8. data/lib/stimulus_plumbers/components/avatar.rb +14 -13
  9. data/lib/stimulus_plumbers/components/button/group.rb +9 -4
  10. data/lib/stimulus_plumbers/components/button/slots.rb +11 -0
  11. data/lib/stimulus_plumbers/components/button.rb +30 -34
  12. data/lib/stimulus_plumbers/components/calendar/turbo/days_of_month.rb +151 -0
  13. data/lib/stimulus_plumbers/components/calendar/turbo/days_of_week.rb +62 -0
  14. data/lib/stimulus_plumbers/components/calendar/turbo/months_of_year.rb +99 -0
  15. data/lib/stimulus_plumbers/components/calendar/turbo/years_of_decade.rb +86 -0
  16. data/lib/stimulus_plumbers/components/calendar/turbo.rb +65 -0
  17. data/lib/stimulus_plumbers/components/calendar.rb +70 -29
  18. data/lib/stimulus_plumbers/components/card/slots.rb +26 -0
  19. data/lib/stimulus_plumbers/components/card.rb +54 -14
  20. data/lib/stimulus_plumbers/components/combobox/builder.rb +45 -0
  21. data/lib/stimulus_plumbers/components/combobox/date/navigation.rb +72 -0
  22. data/lib/stimulus_plumbers/components/combobox/date/navigator.rb +25 -0
  23. data/lib/stimulus_plumbers/components/combobox/date.rb +34 -24
  24. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +27 -24
  25. data/lib/stimulus_plumbers/components/combobox/options/option.rb +1 -1
  26. data/lib/stimulus_plumbers/components/combobox/options/option_group.rb +1 -1
  27. data/lib/stimulus_plumbers/components/combobox/time/drum.rb +1 -1
  28. data/lib/stimulus_plumbers/components/combobox/time.rb +48 -49
  29. data/lib/stimulus_plumbers/components/combobox/trigger.rb +17 -12
  30. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +63 -16
  31. data/lib/stimulus_plumbers/components/combobox.rb +58 -38
  32. data/lib/stimulus_plumbers/components/divider.rb +9 -8
  33. data/lib/stimulus_plumbers/components/icon.rb +5 -1
  34. data/lib/stimulus_plumbers/components/link/slots.rb +11 -0
  35. data/lib/stimulus_plumbers/components/link.rb +63 -0
  36. data/lib/stimulus_plumbers/components/list/item/slots.rb +13 -0
  37. data/lib/stimulus_plumbers/components/list/item.rb +83 -0
  38. data/lib/stimulus_plumbers/components/list/section.rb +73 -0
  39. data/lib/stimulus_plumbers/components/list.rb +31 -0
  40. data/lib/stimulus_plumbers/components/popover/panel.rb +32 -0
  41. data/lib/stimulus_plumbers/components/popover/trigger.rb +27 -0
  42. data/lib/stimulus_plumbers/components/popover.rb +44 -18
  43. data/lib/stimulus_plumbers/engine.rb +1 -0
  44. data/lib/stimulus_plumbers/form/base.rb +103 -0
  45. data/lib/stimulus_plumbers/form/builder.rb +71 -24
  46. data/lib/stimulus_plumbers/form/field.rb +56 -88
  47. data/lib/stimulus_plumbers/form/fields/error.rb +1 -1
  48. data/lib/stimulus_plumbers/form/fields/fieldset.rb +11 -8
  49. data/lib/stimulus_plumbers/form/fields/hint.rb +1 -1
  50. data/lib/stimulus_plumbers/form/fields/inputs/checkbox.rb +115 -0
  51. data/lib/stimulus_plumbers/form/fields/inputs/combobox.rb +24 -0
  52. data/lib/stimulus_plumbers/form/fields/inputs/datetime.rb +40 -58
  53. data/lib/stimulus_plumbers/form/fields/inputs/file.rb +9 -8
  54. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +30 -23
  55. data/lib/stimulus_plumbers/form/fields/inputs/radio.rb +60 -0
  56. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +31 -54
  57. data/lib/stimulus_plumbers/form/fields/inputs/select/grouped.rb +22 -33
  58. data/lib/stimulus_plumbers/form/fields/inputs/select/timezone.rb +3 -46
  59. data/lib/stimulus_plumbers/form/fields/inputs/select/weekday.rb +3 -26
  60. data/lib/stimulus_plumbers/form/fields/inputs/select.rb +62 -61
  61. data/lib/stimulus_plumbers/form/fields/inputs/submit.rb +10 -7
  62. data/lib/stimulus_plumbers/form/fields/inputs/text.rb +29 -22
  63. data/lib/stimulus_plumbers/form/fields/inputs/text_area.rb +9 -8
  64. data/lib/stimulus_plumbers/form/fields/label/floating.rb +41 -0
  65. data/lib/stimulus_plumbers/form/fields/label.rb +9 -3
  66. data/lib/stimulus_plumbers/form/fields/renderer.rb +39 -0
  67. data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -1
  68. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -2
  69. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +56 -4
  70. data/lib/stimulus_plumbers/helpers/card_helper.rb +1 -11
  71. data/lib/stimulus_plumbers/helpers/combobox_helper.rb +27 -60
  72. data/lib/stimulus_plumbers/helpers/icon_helper.rb +11 -0
  73. data/lib/stimulus_plumbers/helpers/link_helper.rb +11 -0
  74. data/lib/stimulus_plumbers/helpers/list_helper.rb +11 -0
  75. data/lib/stimulus_plumbers/helpers/plumber_helper.rb +3 -6
  76. data/lib/stimulus_plumbers/helpers.rb +6 -2
  77. data/lib/stimulus_plumbers/logger.rb +4 -3
  78. data/lib/stimulus_plumbers/plumber/base.rb +6 -1
  79. data/lib/stimulus_plumbers/plumber/dispatcher/klass_proxy.rb +4 -3
  80. data/lib/stimulus_plumbers/plumber/dispatcher/method_call.rb +4 -3
  81. data/lib/stimulus_plumbers/plumber/dispatcher.rb +4 -4
  82. data/lib/stimulus_plumbers/plumber/options/aria.rb +17 -0
  83. data/lib/stimulus_plumbers/plumber/options/html.rb +29 -0
  84. data/lib/stimulus_plumbers/plumber/options/stimulus.rb +29 -0
  85. data/lib/stimulus_plumbers/plumber/options/theme.rb +19 -0
  86. data/lib/stimulus_plumbers/plumber/options/token_list.rb +29 -0
  87. data/lib/stimulus_plumbers/plumber/renderer.rb +136 -41
  88. data/lib/stimulus_plumbers/plumber/slots.rb +74 -0
  89. data/lib/stimulus_plumbers/themes/base.rb +5 -7
  90. data/lib/stimulus_plumbers/themes/schema/avatar/ranges.rb +13 -0
  91. data/lib/stimulus_plumbers/themes/schema/button/ranges.rb +16 -0
  92. data/lib/stimulus_plumbers/themes/schema/card/ranges.rb +13 -0
  93. data/lib/stimulus_plumbers/themes/schema/form/checkbox/ranges.rb +16 -0
  94. data/lib/stimulus_plumbers/themes/schema/form/radio/ranges.rb +16 -0
  95. data/lib/stimulus_plumbers/themes/schema/form/ranges.rb +1 -2
  96. data/lib/stimulus_plumbers/themes/schema/link/ranges.rb +14 -0
  97. data/lib/stimulus_plumbers/themes/schema/ranges.rb +1 -5
  98. data/lib/stimulus_plumbers/themes/schema.rb +119 -48
  99. data/lib/stimulus_plumbers/version.rb +1 -1
  100. data/lib/stimulus_plumbers.rb +20 -15
  101. metadata +42 -15
  102. data/lib/stimulus_plumbers/components/action_list/item.rb +0 -30
  103. data/lib/stimulus_plumbers/components/action_list/section.rb +0 -28
  104. data/lib/stimulus_plumbers/components/action_list.rb +0 -29
  105. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +0 -149
  106. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +0 -43
  107. data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +0 -59
  108. data/lib/stimulus_plumbers/components/card/section.rb +0 -31
  109. data/lib/stimulus_plumbers/components/combobox/popover.rb +0 -47
  110. data/lib/stimulus_plumbers/components/date_picker/navigation.rb +0 -41
  111. data/lib/stimulus_plumbers/components/date_picker/navigator.rb +0 -23
  112. data/lib/stimulus_plumbers/components/popover/builder.rb +0 -25
  113. data/lib/stimulus_plumbers/form/fields/inputs/choice.rb +0 -69
  114. data/lib/stimulus_plumbers/helpers/action_list_helper.rb +0 -25
  115. data/lib/stimulus_plumbers/plumber/html_options.rb +0 -52
@@ -2,10 +2,17 @@
2
2
 
3
3
  require "action_view/version"
4
4
 
5
+ require_relative "../plumber/options/aria"
6
+ require_relative "../plumber/options/html"
7
+
8
+ require_relative "base"
5
9
  require_relative "field"
10
+ require_relative "fields/renderer"
6
11
  require_relative "fields/fieldset"
7
- require_relative "fields/inputs/choice"
12
+ require_relative "fields/inputs/checkbox"
13
+ require_relative "fields/inputs/combobox"
8
14
  require_relative "fields/inputs/datetime"
15
+ require_relative "fields/inputs/radio"
9
16
  require_relative "fields/inputs/file"
10
17
  require_relative "fields/inputs/password"
11
18
  require_relative "fields/inputs/search"
@@ -16,14 +23,15 @@ require_relative "fields/inputs/select/weekday"
16
23
  require_relative "fields/inputs/submit"
17
24
  require_relative "fields/inputs/text"
18
25
  require_relative "fields/inputs/text_area"
19
- require_relative "../plumber/html_options"
20
26
 
21
27
  module StimulusPlumbers
22
28
  module Form
23
29
  class Builder < ActionView::Helpers::FormBuilder
24
- include Plumber::HtmlOptions
25
- include Fields::Inputs::Choice
30
+ include Plumber::Options::Html
31
+ include Plumber::Options::Aria
32
+ include Fields::Inputs::Checkbox
26
33
  include Fields::Inputs::Datetime
34
+ include Fields::Inputs::Radio
27
35
  include Fields::Inputs::File
28
36
  include Fields::Inputs::Password
29
37
  include Fields::Inputs::Search
@@ -35,35 +43,75 @@ module StimulusPlumbers
35
43
  include Fields::Inputs::Text
36
44
  include Fields::Inputs::TextArea
37
45
 
46
+ def field(attribute, as:, **options)
47
+ field_opts = options.slice(*Field::OPTIONS)
48
+ input_opts = options.except(*Field::OPTIONS)
49
+ render_field(as, attribute, field_opts, input_opts)
50
+ end
51
+
52
+ def collection_field(attribute, as:, collection:, value_method:, text_method:, **options)
53
+ field_opts = options.slice(*Field::OPTIONS)
54
+ input_opts = options.except(*Field::OPTIONS)
55
+ render_collection_field(as, attribute, field_opts, collection, value_method, text_method, input_opts)
56
+ end
57
+
58
+ def choice(attribute, as:, collection: nil, value_method: nil, text_method: nil, **options)
59
+ field_opts = options.slice(*Field::OPTIONS)
60
+ input_opts = options.except(*Field::OPTIONS)
61
+ render_choice_field(as, attribute, field_opts, collection, value_method, text_method, input_opts)
62
+ end
63
+
38
64
  private
39
65
 
40
- def render_fieldset(attribute, field, &block)
41
- Fields::Fieldset.new(@template).render(object, attribute, field_id(attribute), field, &block)
66
+ def theme
67
+ StimulusPlumbers.config.theme.current
42
68
  end
43
69
 
44
- def render_input_group(error:, leading: nil, trailing: nil, **wrapper_opts, &block)
45
- Components::InputGroup.new(@template).render(leading: leading, trailing: trailing, error: error, **wrapper_opts, &block)
70
+ def render_field(as, attribute, field_opts, input_opts)
71
+ raise ArgumentError, "unknown field type: #{as.inspect}" unless Fields::Renderer::FIELD.key?(as)
72
+
73
+ field = Field.new(@template, **field_opts)
74
+ field.render(object, attribute, input_id: field_id(attribute)) do |html_opts, opts, error|
75
+ Plumber::Dispatcher.build(
76
+ Fields::Renderer::FIELD.fetch(as), attribute, html_opts, opts, error, **input_opts
77
+ ).call(self)
78
+ end
46
79
  end
47
80
 
48
- def render_combobox(attribute, input_id:, opts:, err:, **wrapper_opts, &block)
49
- combobox_opts = opts.deep_merge(
50
- input: { name: field_name(attribute) },
51
- trigger: { id: input_id }
52
- )
53
-
54
- Components::Combobox.new(@template).render(
55
- **combobox_opts,
56
- **merge_html_options(wrapper_opts, field_theme(:form_combobox, error: err)),
57
- &block
58
- )
81
+ def render_collection_field(as, attribute, field_opts, collection, value_method, text_method, input_opts)
82
+ raise ArgumentError, "unknown collection field type: #{as.inspect}" unless Fields::Renderer::COLLECTION.key?(as)
83
+
84
+ Plumber::Dispatcher.build(
85
+ Fields::Renderer::COLLECTION.fetch(as),
86
+ attribute,
87
+ collection,
88
+ value_method,
89
+ text_method,
90
+ field_opts,
91
+ **input_opts
92
+ ).call(self)
59
93
  end
60
94
 
61
- def field_theme(key, **variants)
62
- { class: theme.resolve(key, **variants).fetch(:classes, "") }
95
+ def render_choice_field(as, attribute, field_opts, collection, value_method, text_method, input_opts)
96
+ raise ArgumentError, "unknown choice type: #{as.inspect}" unless Fields::Renderer::CHOICE.key?(as)
97
+
98
+ Plumber::Dispatcher.build(
99
+ Fields::Renderer::CHOICE.fetch(as),
100
+ attribute,
101
+ collection,
102
+ value_method,
103
+ text_method,
104
+ field_opts,
105
+ **input_opts
106
+ ).call(self)
63
107
  end
64
108
 
65
- def theme
66
- StimulusPlumbers.config.theme.current
109
+ def render_fieldset(attribute, field, &block)
110
+ Fields::Fieldset.new(@template).render(object, attribute, field_id(attribute), field, &block)
111
+ end
112
+
113
+ def render_input_group(error:, leading: nil, trailing: nil, **wrapper_opts, &block)
114
+ Components::InputGroup.new(@template).render(leading: leading, trailing: trailing, error: error, **wrapper_opts, &block)
67
115
  end
68
116
 
69
117
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
@@ -94,7 +142,6 @@ module StimulusPlumbers
94
142
 
95
143
  names = method_names.map! { |name| "[#{name}]" }.join
96
144
 
97
- # a little duplication to construct fewer strings
98
145
  if object_name.blank?
99
146
  "#{method_name}#{names}#{"[]" if multiple}"
100
147
  elsif index
@@ -1,56 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "base"
4
+
3
5
  module StimulusPlumbers
4
6
  module Form
5
- class Field
6
- attr_reader :label, :required, :layout
7
+ class Field < Base
8
+ TYPES = %i[
9
+ text email number url tel color month week range datetime_local
10
+ text_area file password date time select search
11
+ ].freeze
12
+ COLLECTION_TYPES = %i[radio check_box collection_select grouped_collection_select].freeze
13
+ FLOATING_TYPES = %i[standard filled outlined].freeze
14
+ OPTIONS = (Base::OPTIONS + %i[hide_label]).freeze
15
+
16
+ attr_reader :hide_label
7
17
 
8
18
  def self.label_id(input_id)
9
19
  [input_id, "label"].compact.join("_")
10
20
  end
11
21
 
12
- def initialize(
13
- template,
14
- label: nil,
15
- hint: nil,
16
- error: nil,
17
- required: false,
18
- hide_label: false,
19
- layout: :stacked,
20
- **kwargs
21
- )
22
- @template = template
23
- @label = label
24
- @hint = hint
25
- @error_override = error
26
- @required = required
27
- @hide_label = hide_label
28
- @layout = layout.to_sym
29
- @kwargs = kwargs
22
+ def initialize(template, hide_label: false, **kwargs)
23
+ super(template, **kwargs)
24
+ @hide_label = hide_label
30
25
  end
31
26
 
32
27
  def label_hidden?
33
28
  @hide_label
34
29
  end
35
30
 
36
- def error?(object, attribute)
37
- build_errors(object, attribute).any?
31
+ def render(object, attribute, input_id:, &block)
32
+ @label ||= attribute.to_s.humanize
33
+ case @floating
34
+ when *FLOATING_TYPES
35
+ render_floating_field(object, attribute, input_id, &block)
36
+ else
37
+ render_default_field(object, attribute, input_id, &block)
38
+ end
38
39
  end
39
40
 
40
- def described_by(object, attribute, input_id)
41
- ids = []
42
- ids << hint_id(input_id) if @hint.present?
43
- ids.concat(build_error_ids(object, attribute, input_id))
44
- ids.join(" ").presence
45
- end
41
+ private
46
42
 
47
- def render(object, attribute, input_id:, &block)
48
- @label ||= attribute.to_s.humanize
49
- error = error?(object, attribute)
43
+ def render_default_field(object, attribute, input_id, &block)
44
+ error_override = error?(object, attribute)
50
45
  aria = build_aria(object, attribute, input_id)
51
- generated_opts = build_html_options(input_id, aria)
52
- field_html = @template.capture(generated_opts, @kwargs, error, &block)
53
- Fields::Group.new(@template).render(layout: @layout, error: error) do
46
+ field_opts = build_html_options(input_id, aria)
47
+ field_html = @template.capture(field_opts, @kwargs, error_override, &block)
48
+ Fields::Group.new(@template).render(layout: @layout, error: error_override) do
54
49
  @template.safe_join(
55
50
  [
56
51
  field_label(input_id),
@@ -62,70 +57,43 @@ module StimulusPlumbers
62
57
  end
63
58
  end
64
59
 
65
- def render_hint(input_id)
66
- Fields::Hint.new(@template).render(text: @hint, id: hint_id(input_id)) if @hint.present?
67
- end
68
-
69
- def render_errors(object, attribute, input_id)
70
- errs = build_errors(object, attribute)
71
- return if errs.none?
72
-
73
- @template.safe_join(
74
- errs.map.with_index do |message, i|
75
- Fields::Error.new(@template).render(message: message, id: build_error_ids(object, attribute, input_id)[i])
76
- end
60
+ def field_label(input_id)
61
+ Fields::Label.new(@template).render(
62
+ text: @label,
63
+ for_id: input_id,
64
+ id: self.class.label_id(input_id),
65
+ required: @required,
66
+ hidden: @hide_label
77
67
  )
78
68
  end
79
69
 
80
- private
81
-
82
- def build_errors(object, attribute)
83
- if @error_override
84
- Array(@error_override)
85
- elsif object.respond_to?(:errors)
86
- object.errors[attribute]
87
- else
88
- []
70
+ def render_floating_field(object, attribute, input_id, &block)
71
+ error_override = error?(object, attribute)
72
+ aria = build_aria(object, attribute, input_id)
73
+ input_classes = theme.resolve(:form_field_floating, type: @floating, error: error_override)[:classes]
74
+ field_opts = build_html_options(input_id, aria).merge(class: input_classes, placeholder: " ")
75
+ Fields::Group.new(@template).render(layout: @layout, error: error_override) do
76
+ @template.safe_join(
77
+ [
78
+ floating_field_label(input_id, error: error_override) do
79
+ @template.capture(field_opts, @kwargs, error_override, &block)
80
+ end,
81
+ render_hint(input_id),
82
+ render_errors(object, attribute, input_id)
83
+ ]
84
+ )
89
85
  end
90
86
  end
91
87
 
92
- def build_aria(object, attribute, input_id)
93
- aria = {}
94
- aria[:describedby] = described_by(object, attribute, input_id)
95
- aria[:invalid] = "true" if error?(object, attribute)
96
- aria[:required] = "true" if @required
97
- aria.compact
98
- end
99
-
100
- def build_html_options(input_id, aria)
101
- attrs = { id: input_id, aria: aria }
102
- attrs[:required] = true if @required
103
- attrs
104
- end
105
-
106
- def hint_id(input_id)
107
- [input_id, "hint"].compact.join("_")
108
- end
109
-
110
- def error_id(input_id)
111
- [input_id, "error"].compact.join("_")
112
- end
113
-
114
- def build_error_ids(object, attribute, input_id)
115
- errs = build_errors(object, attribute)
116
- return [] if errs.none?
117
- return [error_id(input_id)] if errs.one?
118
-
119
- errs.each_index.map { |i| [error_id(input_id), i + 1].compact.join("_") }
120
- end
121
-
122
- def field_label(input_id)
123
- Fields::Label.new(@template).render(
88
+ def floating_field_label(input_id, error:, &block)
89
+ Fields::Label::Floating.new(@template).render(
124
90
  text: @label,
125
91
  for_id: input_id,
126
92
  id: self.class.label_id(input_id),
93
+ type: @floating,
127
94
  required: @required,
128
- hidden: @hide_label
95
+ error: error,
96
+ &block
129
97
  )
130
98
  end
131
99
  end
@@ -5,7 +5,7 @@ module StimulusPlumbers
5
5
  module Fields
6
6
  class Error < Plumber::Base
7
7
  def render(message:, id:)
8
- html_options = merge_html_options(theme.resolve(:form_error))
8
+ html_options = merge_html_options(theme.resolve(:form_field_error))
9
9
  template.content_tag(:p, message, id: id, role: "alert", **html_options)
10
10
  end
11
11
  end
@@ -7,7 +7,7 @@ module StimulusPlumbers
7
7
  def render(object, attribute, input_id, field, &block)
8
8
  error = field.error?(object, attribute)
9
9
  fieldset_opts = build_fieldset_aria(field, object, attribute, input_id, error)
10
- Group.new(template).render(layout: field.layout, error: error) do
10
+ Group.new(template).render(layout: :stacked, error: error) do
11
11
  template.safe_join(
12
12
  [
13
13
  build_fieldset(fieldset_opts, field, attribute, error, &block),
@@ -25,12 +25,17 @@ module StimulusPlumbers
25
25
  template.safe_join(
26
26
  [
27
27
  legend(field, attribute),
28
- template.capture(error, &block)
28
+ fields_wrapper(field.layout) { template.capture(error, &block) }
29
29
  ]
30
30
  )
31
31
  end
32
32
  end
33
33
 
34
+ def fields_wrapper(layout, &block)
35
+ html_options = merge_html_options(theme.resolve(:form_field_choice_items, layout: layout))
36
+ template.content_tag(:div, **html_options, &block)
37
+ end
38
+
34
39
  def legend(field, attribute)
35
40
  Label.new(template).render(
36
41
  text: field.label || attribute.to_s.humanize,
@@ -41,12 +46,10 @@ module StimulusPlumbers
41
46
  end
42
47
 
43
48
  def build_fieldset_aria(field, object, attribute, input_id, error)
44
- opts = {}
45
- db = field.described_by(object, attribute, input_id)
46
- opts[:"aria-describedby"] = db if db
47
- opts[:"aria-invalid"] = "true" if error
48
- opts[:"aria-required"] = "true" if field.required
49
- opts
49
+ aria = {}
50
+ aria[:describedby] = field.described_by(object, attribute, input_id)
51
+ aria[:invalid] = "true" if error
52
+ { aria: aria.compact }
50
53
  end
51
54
  end
52
55
  end
@@ -5,7 +5,7 @@ module StimulusPlumbers
5
5
  module Fields
6
6
  class Hint < Plumber::Base
7
7
  def render(text:, id:)
8
- html_options = merge_html_options(theme.resolve(:form_details))
8
+ html_options = merge_html_options(theme.resolve(:form_field_hint))
9
9
  template.content_tag(:p, text, id: id, **html_options)
10
10
  end
11
11
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module Checkbox
8
+ def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0")
9
+ html_options = merge_html_options(theme.resolve(:form_field_input_checkbox), options)
10
+ super(attribute, html_options, checked_value, unchecked_value)
11
+ end
12
+
13
+ def collection_check_boxes(
14
+ attribute,
15
+ collection,
16
+ value_method,
17
+ text_method,
18
+ options = {},
19
+ html_options = {},
20
+ &block
21
+ )
22
+ item_opts = merge_html_options(theme.resolve(:form_field_input_checkbox), html_options)
23
+ if block_given?
24
+ super(attribute, collection, value_method, text_method, options, item_opts, &block)
25
+ else
26
+ super(attribute, collection, value_method, text_method, options, item_opts) do |builder|
27
+ render_check_box_label(builder, theme.resolve(:form_field_checkbox_label))
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def render_check_box(attribute, collection, value_method, text_method, field_opts, **kwargs)
35
+ if collection
36
+ render_collection_check_box(attribute, collection, value_method, text_method, field_opts, **kwargs)
37
+ else
38
+ render_single_check_box(attribute, field_opts, **kwargs)
39
+ end
40
+ end
41
+
42
+ def render_collection_check_box(attribute, collection, value_method, text_method, field_opts, **kwargs)
43
+ type = kwargs.delete(:type) { :default }
44
+ variant = kwargs.delete(:variant) { :default }
45
+ field = Field.new(@template, **{ layout: :inline }.deep_merge(field_opts))
46
+ render_fieldset(attribute, field) do |error|
47
+ item_opts = merge_html_options(
48
+ theme.resolve(:form_field_input_checkbox, error: error, type: type, variant: variant),
49
+ kwargs,
50
+ field.required ? { aria: { required: "true" } } : {}
51
+ )
52
+ @template.collection_check_boxes(
53
+ @object_name, attribute, collection, value_method, text_method, {}, item_opts
54
+ ) do |builder|
55
+ render_check_box_label(builder, theme.resolve(:form_field_checkbox_label, type: type, variant: variant), type)
56
+ end
57
+ end
58
+ end
59
+
60
+ def render_check_box_label(builder, label_opts, type = :default)
61
+ html_options = merge_html_options(label_opts)
62
+ case type
63
+ when :card
64
+ builder.label(**html_options) { @template.safe_join([builder.text, builder.check_box]) }
65
+ else
66
+ builder.label(**html_options) { @template.safe_join([builder.check_box, builder.text]) }
67
+ end
68
+ end
69
+
70
+ def render_single_check_box(attribute, field_opts, checked_value: "1", unchecked_value: "0", **kwargs)
71
+ field = Field.new(@template, **field_opts)
72
+ input_id = field_id(attribute)
73
+ error = field.error?(object, attribute)
74
+
75
+ Fields::Group.new(@template).render(layout: :stacked, error: error) do
76
+ check_box_html = build_check_box_input(field, attribute, input_id, error, checked_value, unchecked_value, **kwargs)
77
+ @template.safe_join(
78
+ [
79
+ build_check_box_label(field, attribute, input_id, check_box_html),
80
+ field.render_hint(input_id),
81
+ field.render_errors(object, attribute, input_id)
82
+ ]
83
+ )
84
+ end
85
+ end
86
+
87
+ def build_check_box_input(field, attribute, input_id, error, checked_value, unchecked_value, **kwargs)
88
+ check_box_opts = merge_html_options(
89
+ theme.resolve(:form_checkbox, error: error),
90
+ kwargs,
91
+ {
92
+ id: input_id,
93
+ aria: {
94
+ describedby: field.described_by(object, attribute, input_id),
95
+ invalid: error ? "true" : nil,
96
+ required: field.required ? "true" : nil
97
+ }.compact
98
+ },
99
+ field.required ? { required: true } : {}
100
+ )
101
+ @template.check_box(@object_name, attribute, objectify_options(check_box_opts), checked_value, unchecked_value)
102
+ end
103
+
104
+ def build_check_box_label(field, attribute, input_id, check_box_html)
105
+ label_opts = merge_html_options(theme.resolve(:form_field_checkbox_label))
106
+ label_text = field.label || attribute.to_s.humanize
107
+ @template.content_tag(:label, for: input_id, **label_opts) do
108
+ @template.safe_join([check_box_html, label_text])
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module Combobox
8
+ private
9
+
10
+ def render_combobox(attribute, input_id:, opts:, error:, **kwargs, &block)
11
+ combobox_opts = opts.deep_merge(input: { name: field_name(attribute) })
12
+
13
+ Components::Combobox.new(@template).render(
14
+ id: input_id,
15
+ **combobox_opts,
16
+ **merge_html_options(theme.resolve(:form_field_input_combobox, error: error), kwargs),
17
+ &block
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,90 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "combobox"
4
+
3
5
  module StimulusPlumbers
4
6
  module Form
5
7
  module Fields
6
8
  module Inputs
7
9
  module Datetime
10
+ include Combobox
11
+
8
12
  def date_field(attribute, options = {})
9
- html_native = options.delete(:html_native) { false }
10
- icon_leading = options.delete(:icon_leading)
11
- icon_trailing = options.delete(:icon_trailing) { "calendar" }
12
- Field.new(@template, **options).render(
13
- object,
14
- attribute,
15
- input_id: field_id(attribute)
16
- ) do |html_opts, opts, error|
17
- if html_native
18
- html_options = merge_html_options(opts, html_opts, field_theme(:form_input, error: error))
19
- super(attribute, html_options)
20
- else
21
- render_date_combobox(attribute, html_opts, error, icon_leading: icon_leading, icon_trailing: icon_trailing)
22
- end
23
- end
13
+ html_options = merge_html_options(theme.resolve(:form_field_input), options)
14
+ super(attribute, html_options)
24
15
  end
25
16
 
26
17
  def time_field(attribute, options = {})
27
- html_native = options.delete(:html_native) { false }
28
- format = options.delete(:format) { :h12 }
29
- step = options.delete(:step) { 1 }
30
- icon_leading = options.delete(:icon_leading)
31
- icon_trailing = options.delete(:icon_trailing) { "clock" }
32
- Field.new(@template, **options).render(
33
- object,
34
- attribute,
35
- input_id: field_id(attribute)
36
- ) do |html_opts, opts, error|
37
- if html_native
38
- html_options = merge_html_options(opts, html_opts, field_theme(:form_input, error: error))
39
- super(attribute, html_options)
40
- else
41
- render_time_combobox(
42
- attribute,
43
- html_opts,
44
- error,
45
- format: format,
46
- step: step,
47
- icon_leading: icon_leading,
48
- icon_trailing: icon_trailing
49
- )
50
- end
51
- end
18
+ html_options = merge_html_options(theme.resolve(:form_field_input), options)
19
+ super(attribute, html_options)
52
20
  end
53
21
 
54
22
  private
55
23
 
56
- def render_date_combobox(attribute, html_opts, error, icon_leading:, icon_trailing:)
24
+ def render_combobox_date(attribute, html_opts, opts, error, icon_leading: nil, icon_trailing: nil, **kwargs)
57
25
  current_value = object.respond_to?(attribute) ? object.public_send(attribute) : nil
58
- opts = Components::Combobox::Date.default_opts.deep_merge(
59
- input: { value: current_value, data: { combobox_date_date_value: current_value } },
26
+ labelledby = Field.label_id(html_opts[:id])
27
+ combobox_opts = {
28
+ input: { value: current_value },
60
29
  trigger: { aria: html_opts[:aria], icon_leading: icon_leading, icon_trailing: icon_trailing }.compact,
61
- popover: { labelledby: Field.label_id(html_opts[:id]) }
62
- )
30
+ **opts
31
+ }
63
32
  render_combobox(
64
33
  attribute,
65
34
  input_id: html_opts[:id],
66
- opts: opts,
67
- err: error,
68
- data: { input_formatter_format_value: "date" }
69
- ) do |popover_id|
70
- Components::Combobox::Date.new(@template).render(value: current_value, popover_id: popover_id)
35
+ opts: combobox_opts,
36
+ error: error,
37
+ **kwargs
38
+ ) do |c|
39
+ c.date(value: current_value, labelledby: labelledby)
71
40
  end
72
41
  end
73
42
 
74
- def render_time_combobox(attribute, html_opts, error, format:, step:, icon_leading:, icon_trailing:)
43
+ def render_combobox_time(
44
+ attribute,
45
+ html_opts,
46
+ opts,
47
+ error,
48
+ format: :h12,
49
+ step: 1,
50
+ icon_leading: nil,
51
+ icon_trailing: nil,
52
+ **kwargs
53
+ )
75
54
  current_value = object.respond_to?(attribute) ? object.public_send(attribute) : nil
76
- opts = Components::Combobox::Time.default_opts.deep_merge(
55
+ labelledby = Field.label_id(html_opts[:id])
56
+ combobox_opts = {
77
57
  input: { value: current_value },
78
58
  trigger: { aria: html_opts[:aria], icon_leading: icon_leading, icon_trailing: icon_trailing }.compact,
79
- popover: { labelledby: Field.label_id(html_opts[:id]) }
80
- )
59
+ **opts
60
+ }
81
61
  render_combobox(
82
62
  attribute,
83
63
  input_id: html_opts[:id],
84
- opts: opts,
85
- err: error,
86
- data: { input_formatter_format_value: "time", input_formatter_options_value: { format: format }.to_json }
87
- ) { Components::Combobox::Time.new(@template).render(format: format, step: step, value: current_value) }
64
+ opts: combobox_opts,
65
+ error: error,
66
+ **kwargs
67
+ ) do |c|
68
+ c.time(format: format, step: step, value: current_value, labelledby: labelledby)
69
+ end
88
70
  end
89
71
  end
90
72
  end