stimulus_plumbers 0.3.2 → 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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +3 -1
  4. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +273 -0
  5. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +269 -160
  6. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
  7. data/app/assets/stylesheets/stimulus_plumbers/tokens.css +56 -13
  8. data/config/locales/en.yml +10 -0
  9. data/lib/stimulus_plumbers/components/avatar.rb +24 -17
  10. data/lib/stimulus_plumbers/components/button/group.rb +15 -4
  11. data/lib/stimulus_plumbers/components/button/slots.rb +11 -0
  12. data/lib/stimulus_plumbers/components/button.rb +45 -11
  13. data/lib/stimulus_plumbers/components/calendar/turbo/days_of_month.rb +151 -0
  14. data/lib/stimulus_plumbers/components/calendar/turbo/days_of_week.rb +62 -0
  15. data/lib/stimulus_plumbers/components/calendar/turbo/months_of_year.rb +99 -0
  16. data/lib/stimulus_plumbers/components/calendar/turbo/years_of_decade.rb +86 -0
  17. data/lib/stimulus_plumbers/components/calendar/turbo.rb +65 -0
  18. data/lib/stimulus_plumbers/components/calendar.rb +70 -26
  19. data/lib/stimulus_plumbers/components/card/slots.rb +26 -0
  20. data/lib/stimulus_plumbers/components/card.rb +56 -10
  21. data/lib/stimulus_plumbers/components/combobox/builder.rb +45 -0
  22. data/lib/stimulus_plumbers/components/combobox/date/navigation.rb +72 -0
  23. data/lib/stimulus_plumbers/components/combobox/date/navigator.rb +25 -0
  24. data/lib/stimulus_plumbers/components/combobox/date.rb +37 -23
  25. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +30 -21
  26. data/lib/stimulus_plumbers/components/combobox/options/option.rb +8 -2
  27. data/lib/stimulus_plumbers/components/combobox/options/option_group.rb +8 -2
  28. data/lib/stimulus_plumbers/components/combobox/options.rb +9 -5
  29. data/lib/stimulus_plumbers/components/combobox/time/drum.rb +8 -2
  30. data/lib/stimulus_plumbers/components/combobox/time.rb +50 -47
  31. data/lib/stimulus_plumbers/components/combobox/trigger.rb +62 -14
  32. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +96 -0
  33. data/lib/stimulus_plumbers/components/combobox.rb +62 -38
  34. data/lib/stimulus_plumbers/components/divider.rb +25 -4
  35. data/lib/stimulus_plumbers/components/icon.rb +11 -17
  36. data/lib/stimulus_plumbers/components/input_group.rb +29 -0
  37. data/lib/stimulus_plumbers/components/link/slots.rb +11 -0
  38. data/lib/stimulus_plumbers/components/link.rb +63 -0
  39. data/lib/stimulus_plumbers/components/list/item/slots.rb +13 -0
  40. data/lib/stimulus_plumbers/components/list/item.rb +83 -0
  41. data/lib/stimulus_plumbers/components/list/section.rb +73 -0
  42. data/lib/stimulus_plumbers/components/list.rb +31 -0
  43. data/lib/stimulus_plumbers/components/popover/panel.rb +32 -0
  44. data/lib/stimulus_plumbers/components/popover/trigger.rb +27 -0
  45. data/lib/stimulus_plumbers/components/popover.rb +44 -14
  46. data/lib/stimulus_plumbers/engine.rb +1 -0
  47. data/lib/stimulus_plumbers/form/base.rb +103 -0
  48. data/lib/stimulus_plumbers/form/builder.rb +71 -24
  49. data/lib/stimulus_plumbers/form/field.rb +56 -88
  50. data/lib/stimulus_plumbers/form/fields/error.rb +1 -1
  51. data/lib/stimulus_plumbers/form/fields/fieldset.rb +11 -8
  52. data/lib/stimulus_plumbers/form/fields/hint.rb +1 -1
  53. data/lib/stimulus_plumbers/form/fields/inputs/checkbox.rb +115 -0
  54. data/lib/stimulus_plumbers/form/fields/inputs/combobox.rb +24 -0
  55. data/lib/stimulus_plumbers/form/fields/inputs/datetime.rb +42 -48
  56. data/lib/stimulus_plumbers/form/fields/inputs/file.rb +9 -8
  57. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +32 -25
  58. data/lib/stimulus_plumbers/form/fields/inputs/radio.rb +60 -0
  59. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +34 -57
  60. data/lib/stimulus_plumbers/form/fields/inputs/select/grouped.rb +22 -29
  61. data/lib/stimulus_plumbers/form/fields/inputs/select/timezone.rb +3 -44
  62. data/lib/stimulus_plumbers/form/fields/inputs/select/weekday.rb +3 -28
  63. data/lib/stimulus_plumbers/form/fields/inputs/select.rb +62 -49
  64. data/lib/stimulus_plumbers/form/fields/inputs/submit.rb +10 -7
  65. data/lib/stimulus_plumbers/form/fields/inputs/text.rb +29 -22
  66. data/lib/stimulus_plumbers/form/fields/inputs/text_area.rb +9 -8
  67. data/lib/stimulus_plumbers/form/fields/label/floating.rb +41 -0
  68. data/lib/stimulus_plumbers/form/fields/label.rb +9 -3
  69. data/lib/stimulus_plumbers/form/fields/renderer.rb +39 -0
  70. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +2 -2
  71. data/lib/stimulus_plumbers/helpers/button_helper.rb +4 -8
  72. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +14 -11
  73. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +49 -11
  74. data/lib/stimulus_plumbers/helpers/card_helper.rb +2 -12
  75. data/lib/stimulus_plumbers/helpers/combobox_helper.rb +27 -47
  76. data/lib/stimulus_plumbers/helpers/divider_helper.rb +2 -2
  77. data/lib/stimulus_plumbers/helpers/icon_helper.rb +11 -0
  78. data/lib/stimulus_plumbers/helpers/link_helper.rb +11 -0
  79. data/lib/stimulus_plumbers/helpers/list_helper.rb +11 -0
  80. data/lib/stimulus_plumbers/helpers/plumber_helper.rb +3 -6
  81. data/lib/stimulus_plumbers/helpers/popover_helper.rb +2 -2
  82. data/lib/stimulus_plumbers/helpers.rb +6 -2
  83. data/lib/stimulus_plumbers/logger.rb +4 -3
  84. data/lib/stimulus_plumbers/plumber/base.rb +6 -1
  85. data/lib/stimulus_plumbers/plumber/dispatcher/klass_proxy.rb +4 -3
  86. data/lib/stimulus_plumbers/plumber/dispatcher/method_call.rb +4 -3
  87. data/lib/stimulus_plumbers/plumber/dispatcher.rb +4 -4
  88. data/lib/stimulus_plumbers/plumber/options/aria.rb +17 -0
  89. data/lib/stimulus_plumbers/plumber/options/html.rb +29 -0
  90. data/lib/stimulus_plumbers/plumber/options/stimulus.rb +29 -0
  91. data/lib/stimulus_plumbers/plumber/options/theme.rb +19 -0
  92. data/lib/stimulus_plumbers/plumber/options/token_list.rb +29 -0
  93. data/lib/stimulus_plumbers/plumber/renderer.rb +136 -41
  94. data/lib/stimulus_plumbers/plumber/slots.rb +74 -0
  95. data/lib/stimulus_plumbers/themes/base.rb +20 -23
  96. data/lib/stimulus_plumbers/themes/icons/external.rb +60 -0
  97. data/lib/stimulus_plumbers/themes/icons/registry.rb +36 -0
  98. data/lib/stimulus_plumbers/themes/schema/avatar/ranges.rb +13 -0
  99. data/lib/stimulus_plumbers/themes/schema/button/ranges.rb +16 -0
  100. data/lib/stimulus_plumbers/themes/schema/card/ranges.rb +13 -0
  101. data/lib/stimulus_plumbers/themes/schema/form/checkbox/ranges.rb +16 -0
  102. data/lib/stimulus_plumbers/themes/schema/form/radio/ranges.rb +16 -0
  103. data/lib/stimulus_plumbers/themes/schema/form/ranges.rb +1 -2
  104. data/lib/stimulus_plumbers/themes/schema/icon.rb +57 -15
  105. data/lib/stimulus_plumbers/themes/schema/link/ranges.rb +14 -0
  106. data/lib/stimulus_plumbers/themes/schema/ranges.rb +1 -5
  107. data/lib/stimulus_plumbers/themes/schema.rb +142 -67
  108. data/lib/stimulus_plumbers/version.rb +1 -1
  109. data/lib/stimulus_plumbers.rb +22 -17
  110. metadata +46 -17
  111. data/lib/stimulus_plumbers/components/action_list/item.rb +0 -27
  112. data/lib/stimulus_plumbers/components/action_list/section.rb +0 -22
  113. data/lib/stimulus_plumbers/components/action_list.rb +0 -23
  114. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +0 -145
  115. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +0 -39
  116. data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +0 -55
  117. data/lib/stimulus_plumbers/components/card/section.rb +0 -25
  118. data/lib/stimulus_plumbers/components/combobox/autocomplete.rb +0 -47
  119. data/lib/stimulus_plumbers/components/combobox/popover.rb +0 -24
  120. data/lib/stimulus_plumbers/components/date_picker/navigation.rb +0 -41
  121. data/lib/stimulus_plumbers/components/date_picker/navigator.rb +0 -31
  122. data/lib/stimulus_plumbers/components/popover/builder.rb +0 -25
  123. data/lib/stimulus_plumbers/form/fields/input_group.rb +0 -25
  124. data/lib/stimulus_plumbers/form/fields/inputs/choice.rb +0 -69
  125. data/lib/stimulus_plumbers/helpers/action_list_helper.rb +0 -25
  126. data/lib/stimulus_plumbers/plumber/html_options.rb +0 -52
@@ -3,27 +3,57 @@
3
3
  module StimulusPlumbers
4
4
  module Components
5
5
  class Popover < Plumber::Base
6
- def render(interactive: true, **kwargs, &block)
7
- html_options = merge_html_options(
8
- { classes: theme.resolve(:popover).fetch(:classes, "") },
9
- kwargs
10
- )
6
+ STIMULUS_CONTROLLER = "popover"
11
7
 
12
- builder = Popover::Builder.new(template)
13
- template.capture(builder, &block)
8
+ def self.panel_id_for(trigger_id)
9
+ [trigger_id, "popover"].compact.join("_")
10
+ end
14
11
 
15
- template.content_tag(:div, **html_options) do
16
- template.safe_join([builder.activator_html, wrap_content(interactive, builder)])
12
+ def render(...) = render_popover(...)
13
+
14
+ def build(panel_id: nil, &block)
15
+ @panel_id = panel_id || self.class.panel_id_for(template.sp_dom_id)
16
+ @trigger_html = nil
17
+ @panel_html = nil
18
+ yield self
19
+ template.safe_join([@trigger_html, @panel_html].compact)
20
+ end
21
+
22
+ def trigger(haspopup: "dialog", controls: @panel_id, **kwargs, &block)
23
+ if block_given? && block.arity == 1
24
+ attrs = {
25
+ panel_id: @panel_id,
26
+ aria: { haspopup: haspopup, expanded: "false", controls: controls },
27
+ data: { popover_target: "trigger", action: Popover::Trigger::STIMULUS_ACTION }
28
+ }
29
+ @trigger_html = template.capture(attrs, &block)
30
+ else
31
+ @trigger_html = Popover::Trigger.new(template).render(
32
+ panel_id: @panel_id, haspopup: haspopup, **kwargs, &block
33
+ )
17
34
  end
18
35
  end
19
36
 
37
+ def panel(**kwargs, &block)
38
+ @panel_html = Popover::Panel.new(template).render(panel_id: @panel_id, **kwargs, &block)
39
+ end
40
+
41
+ def build_panel(**kwargs, &block)
42
+ @panel_html = Popover::Panel.new(template).build(panel_id: @panel_id, **kwargs, &block)
43
+ end
44
+
20
45
  private
21
46
 
22
- def wrap_content(interactive, builder)
23
- if interactive
24
- template.content_tag(:template, builder.content_html)
25
- else
26
- builder.content_html
47
+ def render_popover(panel_id: nil, close_on_select: nil, **kwargs, &block)
48
+ data = { controller: STIMULUS_CONTROLLER }
49
+ data[:popover_close_on_select_value] = close_on_select unless close_on_select.nil?
50
+ html_options = merge_html_options(
51
+ theme.resolve(:popover_wrapper),
52
+ kwargs,
53
+ { data: data }
54
+ )
55
+ template.content_tag(:div, **html_options) do
56
+ build(panel_id: panel_id, &block)
27
57
  end
28
58
  end
29
59
  end
@@ -7,6 +7,7 @@ module StimulusPlumbers
7
7
  isolate_namespace StimulusPlumbers
8
8
 
9
9
  config.autoload_paths << File.expand_path("../stimulus-plumbers", __dir__)
10
+ config.i18n.load_path += Dir[File.expand_path("../../config/locales/*.{rb,yml}", __dir__)]
10
11
 
11
12
  initializer "stimulus_plumbers.assets", after: :set_default_precompile do |app|
12
13
  app.config.assets.precompile += %w[stimulus_plumbers/tokens.css] if app.config.respond_to?(:assets)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ class Base
6
+ OPTIONS = %i[label hint error required layout floating].freeze
7
+
8
+ attr_reader(*OPTIONS)
9
+
10
+ def initialize(
11
+ template,
12
+ label: nil,
13
+ hint: nil,
14
+ error: nil,
15
+ required: false,
16
+ layout: :stacked,
17
+ floating: nil,
18
+ **kwargs
19
+ )
20
+ @template = template
21
+ @label = label
22
+ @hint = hint
23
+ @error = error
24
+ @required = required
25
+ @layout = layout.to_sym
26
+ @floating = floating
27
+ @kwargs = kwargs
28
+ end
29
+
30
+ def theme
31
+ StimulusPlumbers.config.theme.current
32
+ end
33
+
34
+ def error?(object, attribute)
35
+ build_errors(object, attribute).any?
36
+ end
37
+
38
+ def described_by(object, attribute, input_id)
39
+ ids = []
40
+ ids << hint_id(input_id) if @hint.present?
41
+ ids.concat(build_error_ids(object, attribute, input_id))
42
+ ids.join(" ").presence
43
+ end
44
+
45
+ def render_hint(input_id)
46
+ Fields::Hint.new(@template).render(text: @hint, id: hint_id(input_id)) if @hint.present?
47
+ end
48
+
49
+ def render_errors(object, attribute, input_id)
50
+ errs = build_errors(object, attribute)
51
+ return if errs.none?
52
+
53
+ @template.safe_join(
54
+ errs.map.with_index do |message, i|
55
+ Fields::Error.new(@template).render(message: message, id: build_error_ids(object, attribute, input_id)[i])
56
+ end
57
+ )
58
+ end
59
+
60
+ private
61
+
62
+ def build_errors(object, attribute)
63
+ if error
64
+ Array(error)
65
+ elsif object.respond_to?(:errors)
66
+ object.errors[attribute]
67
+ else
68
+ []
69
+ end
70
+ end
71
+
72
+ def build_aria(object, attribute, input_id)
73
+ aria = {}
74
+ aria[:describedby] = described_by(object, attribute, input_id)
75
+ aria[:invalid] = "true" if error?(object, attribute)
76
+ aria[:required] = "true" if @required
77
+ aria.compact
78
+ end
79
+
80
+ def build_html_options(input_id, aria)
81
+ attrs = { id: input_id, aria: aria }
82
+ attrs[:required] = true if @required
83
+ attrs
84
+ end
85
+
86
+ def hint_id(input_id)
87
+ [input_id, "hint"].compact.join("_")
88
+ end
89
+
90
+ def error_id(input_id)
91
+ [input_id, "error"].compact.join("_")
92
+ end
93
+
94
+ def build_error_ids(object, attribute, input_id)
95
+ errs = build_errors(object, attribute)
96
+ return [] if errs.none?
97
+ return [error_id(input_id)] if errs.one?
98
+
99
+ errs.each_index.map { |i| [error_id(input_id), i + 1].compact.join("_") }
100
+ end
101
+ end
102
+ end
103
+ end
@@ -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
- Fields::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