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
@@ -5,13 +5,12 @@ module StimulusPlumbers
5
5
  class Divider < Plumber::Base
6
6
  def render(label = nil, **kwargs)
7
7
  divider_opts = merge_html_options(
8
- { classes: theme.resolve(:divider).fetch(:classes, "") },
8
+ theme.resolve(:divider),
9
9
  kwargs
10
10
  )
11
11
  template.content_tag(:div, role: "separator", **divider_opts) do
12
12
  if label.blank?
13
- hr_classes = theme.resolve(:divider_separator).fetch(:classes, "")
14
- template.tag.hr(class: hr_classes)
13
+ template.tag.hr(**divider_separator_opts)
15
14
  else
16
15
  render_with(label)
17
16
  end
@@ -21,16 +20,18 @@ module StimulusPlumbers
21
20
  private
22
21
 
23
22
  def render_with(label)
24
- hr_classes = theme.resolve(:divider_separator).fetch(:classes, "")
25
- label_classes = theme.resolve(:divider_label).fetch(:classes, "")
26
23
  template.safe_join(
27
24
  [
28
- template.tag.hr(class: hr_classes),
29
- template.content_tag(:span, label, class: label_classes),
30
- template.tag.hr(class: hr_classes)
25
+ template.tag.hr(**divider_separator_opts),
26
+ template.content_tag(:span, label, **merge_html_options(theme.resolve(:divider_label))),
27
+ template.tag.hr(**divider_separator_opts)
31
28
  ]
32
29
  )
33
30
  end
31
+
32
+ def divider_separator_opts
33
+ merge_html_options(theme.resolve(:divider_separator))
34
+ end
34
35
  end
35
36
  end
36
37
  end
@@ -3,9 +3,13 @@
3
3
  module StimulusPlumbers
4
4
  module Components
5
5
  class Icon < Plumber::Base
6
+ def self.icon_name?(value)
7
+ value.is_a?(Symbol) || (value.is_a?(String) && !value.html_safe?)
8
+ end
9
+
6
10
  def render(name:, **kwargs)
7
11
  html_options = merge_html_options(
8
- { classes: theme.resolve(:icon).fetch(:classes, "") },
12
+ theme.resolve(:icon),
9
13
  kwargs
10
14
  )
11
15
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Link
6
+ class Slots < Plumber::Slots
7
+ slot :icon_leading, :icon_trailing
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Link < Plumber::Base
6
+ def render(content = nil, url:, icon_leading: nil, icon_trailing: nil, **kwargs, &block)
7
+ icon_trailing ||= "external-link" if kwargs[:target] == "_blank"
8
+
9
+ slots = Link::Slots.new
10
+ slots.with_icon_leading(icon_leading) if icon_leading
11
+ slots.with_icon_trailing(icon_trailing) if icon_trailing
12
+
13
+ render_link(url: url, **kwargs) do
14
+ build_layout(slots) do
15
+ build_content(content, &block)
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def render_link(url:, target: nil, type: :default, variant: :default, **kwargs, &block)
23
+ html_options = merge_html_options(
24
+ theme.resolve(:link, type: type, variant: variant),
25
+ kwargs
26
+ )
27
+ template.content_tag(:a, href: url, target: target, **html_options) do
28
+ template.capture(&block)
29
+ end
30
+ end
31
+
32
+ def build_layout(slots, &block)
33
+ template.safe_join(
34
+ [
35
+ render_icon_slot(slots, :icon_leading),
36
+ template.capture(&block),
37
+ render_icon_slot(slots, :icon_trailing)
38
+ ]
39
+ )
40
+ end
41
+
42
+ def render_icon_slot(slots, name)
43
+ slots.resolve(name) do |value|
44
+ next value unless Components::Icon.icon_name?(value)
45
+
46
+ Components::Icon.new(template).render(
47
+ name: value,
48
+ classes: theme.resolve(:link_icon).fetch(:classes, ""),
49
+ aria: { hidden: "true" }
50
+ )
51
+ end
52
+ end
53
+
54
+ def build_content(content, &block)
55
+ if block_given?
56
+ template.content_tag(:span, template.capture(&block))
57
+ elsif content
58
+ template.content_tag(:span, content)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class List
6
+ class Item
7
+ class Slots < Plumber::Slots
8
+ slot :icon_leading, :title, :description, :icon_trailing
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class List
6
+ class Item < Plumber::Base
7
+ def render(content = nil, **kwargs, &block)
8
+ slots = List::Item::Slots.new
9
+ slots.with_title(content) if content
10
+ slots.with_icon_trailing("external-link") if kwargs[:url].present? && kwargs[:target] == "_blank"
11
+ yield slots if block_given?
12
+
13
+ template.content_tag(:li) do
14
+ build(**kwargs) do |attrs|
15
+ render_link_or_button(**attrs) { render_item_slots(slots) }
16
+ end
17
+ end
18
+ end
19
+
20
+ def build(**kwargs, &block)
21
+ html_options = merge_html_options(theme.resolve(:list_item), kwargs)
22
+ template.capture(html_options, &block)
23
+ end
24
+
25
+ private
26
+
27
+ def render_link_or_button(url: nil, target: nil, active: false, **html_options, &block)
28
+ if url.present?
29
+ aria = active ? { aria: { current: "page" } } : {}
30
+ template.content_tag(:a, href: url, target: target, **merge_html_options(html_options, aria)) do
31
+ template.capture(&block)
32
+ end
33
+ else
34
+ aria = active ? { aria: { current: true } } : {}
35
+ template.content_tag(:button, type: "button", **merge_html_options(html_options, aria)) do
36
+ template.capture(&block)
37
+ end
38
+ end
39
+ end
40
+
41
+ def render_icon_slot(slots, name)
42
+ slots.resolve(name) do |value|
43
+ next value unless Components::Icon.icon_name?(value)
44
+
45
+ Components::Icon.new(template).render(
46
+ name: value,
47
+ classes: theme.resolve(:list_item_icon).fetch(:classes, ""),
48
+ aria: { hidden: "true" }
49
+ )
50
+ end
51
+ end
52
+
53
+ def render_title_slot(slots)
54
+ slots.resolve(:title) { |v| template.content_tag(:span, v, **merge_html_options(theme.resolve(:list_item_title))) }
55
+ end
56
+
57
+ def render_description_slot(slots)
58
+ slots.resolve(:description) do |v|
59
+ template.content_tag(:span, v, **merge_html_options(theme.resolve(:list_item_description)))
60
+ end
61
+ end
62
+
63
+ def render_content_slot(slots)
64
+ title = render_title_slot(slots)
65
+ description = render_description_slot(slots)
66
+ return unless title || description
67
+
68
+ template.content_tag(:span, **merge_html_options(theme.resolve(:list_item_content))) do
69
+ template.safe_join([title, description].compact)
70
+ end
71
+ end
72
+
73
+ def render_item_slots(slots)
74
+ icon_leading = render_icon_slot(slots, :icon_leading)
75
+ icon_trailing = render_icon_slot(slots, :icon_trailing)
76
+ content = render_content_slot(slots)
77
+
78
+ template.safe_join([icon_leading, content, icon_trailing].compact)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class List
6
+ class Section < Plumber::Base
7
+ def initialize(template, heading_level: nil)
8
+ super(template)
9
+ @heading_level = heading_level
10
+ end
11
+
12
+ def render(title: nil, description: nil, **kwargs, &block)
13
+ html_options = merge_html_options(theme.resolve(:list_section), kwargs)
14
+ template.content_tag(:li, **html_options) do
15
+ template.safe_join(
16
+ [
17
+ render_section_header(title, description),
18
+ render_section_body(title, &block)
19
+ ]
20
+ )
21
+ end
22
+ end
23
+
24
+ def section(...)
25
+ List::Section.new(template, heading_level: (@heading_level || 0) + 1).render(...)
26
+ end
27
+
28
+ def item(content = nil, **kwargs, &block)
29
+ List::Item.new(template).render(content, **kwargs, &block)
30
+ end
31
+
32
+ private
33
+
34
+ def render_section_header(title, description)
35
+ return unless title.present? || description.present?
36
+
37
+ template.safe_join(
38
+ [
39
+ render_section_title(title),
40
+ (if description.present?
41
+ template.content_tag(
42
+ :span,
43
+ description,
44
+ **merge_html_options(theme.resolve(:list_section_description))
45
+ )
46
+ end)
47
+ ].compact
48
+ )
49
+ end
50
+
51
+ def render_section_title(title)
52
+ return unless title.present?
53
+
54
+ if @heading_level
55
+ tag = :"h#{[@heading_level, 6].min}"
56
+ template.content_tag(tag, title, **merge_html_options(theme.resolve(:list_section_title)))
57
+ else
58
+ template.content_tag(
59
+ :span,
60
+ title,
61
+ **merge_html_options(theme.resolve(:list_section_title), { aria: { hidden: "true" } })
62
+ )
63
+ end
64
+ end
65
+
66
+ def render_section_body(title, &block)
67
+ opts = title.present? ? { aria: { label: title } } : {}
68
+ template.content_tag(:ul, template.capture(self, &block), opts)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class List < Plumber::Base
6
+ def render(...)
7
+ render_list(...)
8
+ end
9
+
10
+ def section(...)
11
+ List::Section.new(template, heading_level: @heading_level).render(...)
12
+ end
13
+
14
+ def item(content = nil, **kwargs, &block)
15
+ List::Item.new(template).render(content, **kwargs, &block)
16
+ end
17
+
18
+ private
19
+
20
+ def render_list(role: "list", heading_level: nil, **kwargs, &block)
21
+ @heading_level = heading_level
22
+ html_options = merge_html_options(
23
+ theme.resolve(:list),
24
+ kwargs,
25
+ { role: role }
26
+ )
27
+ template.content_tag(:ul, template.capture(self, &block), **html_options)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Popover
6
+ class Panel < Plumber::Base
7
+ def render(panel_id:, tag: :div, **kwargs, &block)
8
+ template.content_tag(
9
+ tag,
10
+ block_given? ? template.capture(panel_id, &block) : nil,
11
+ **panel_attrs(panel_id, **kwargs)
12
+ )
13
+ end
14
+
15
+ def build(panel_id:, **kwargs, &block)
16
+ template.capture(panel_attrs(panel_id, **kwargs), &block)
17
+ end
18
+
19
+ private
20
+
21
+ def panel_attrs(panel_id, **kwargs)
22
+ merge_html_options(
23
+ { id: panel_id, hidden: "" },
24
+ theme.resolve(:popover),
25
+ { data: { popover_target: "panel" } },
26
+ kwargs
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Popover
6
+ class Trigger < Plumber::Base
7
+ STIMULUS_ACTION = [
8
+ "click->#{STIMULUS_CONTROLLER}#toggle",
9
+ "keydown.esc->#{STIMULUS_CONTROLLER}#close"
10
+ ].join(" ").freeze
11
+
12
+ def render(panel_id:, haspopup: "dialog", **kwargs, &block)
13
+ html_options = merge_html_options(
14
+ theme.resolve(:popover_trigger),
15
+ {
16
+ type: "button",
17
+ aria: { haspopup: haspopup, expanded: "false", controls: panel_id },
18
+ data: { popover_target: "trigger", action: STIMULUS_ACTION }
19
+ },
20
+ kwargs
21
+ )
22
+ template.content_tag(:button, block_given? ? template.capture(&block) : nil, **html_options)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,31 +3,57 @@
3
3
  module StimulusPlumbers
4
4
  module Components
5
5
  class Popover < Plumber::Base
6
- def render(...)
7
- render_popover(...)
8
- end
6
+ STIMULUS_CONTROLLER = "popover"
9
7
 
10
- private
8
+ def self.panel_id_for(trigger_id)
9
+ [trigger_id, "popover"].compact.join("_")
10
+ end
11
11
 
12
- def render_popover(interactive: true, **kwargs, &block)
13
- html_options = merge_html_options(
14
- { classes: theme.resolve(:popover).fetch(:classes, "") },
15
- kwargs
16
- )
12
+ def render(...) = render_popover(...)
17
13
 
18
- builder = Popover::Builder.new(template)
19
- template.capture(builder, &block)
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
20
21
 
21
- template.content_tag(:div, **html_options) do
22
- template.safe_join([builder.activator_html, wrap_content(interactive, builder)])
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
+ )
23
34
  end
24
35
  end
25
36
 
26
- def wrap_content(interactive, builder)
27
- if interactive
28
- template.content_tag(:template, builder.content_html)
29
- else
30
- builder.content_html
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
+
45
+ private
46
+
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)
31
57
  end
32
58
  end
33
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