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
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Combobox
6
+ # Yielded to `Combobox#render`: selects a variant renderer, then exposes its
7
+ # `metadata` (trigger/wrapper wiring) and renders its panel body.
8
+ class Builder < Plumber::Slots
9
+ def dropdown(**options) = select(Dropdown, options)
10
+ def typeahead(**options) = select(Typeahead, options)
11
+ def date(**options) = select(Date, options)
12
+ def time(**options) = select(Time, options)
13
+
14
+ def selected? = @slots.key?(:variant)
15
+ def renderer = selection&.fetch(:renderer)
16
+ def options = selection ? selection[:options] : {}
17
+ def metadata = renderer ? renderer::Metadata : DefaultMetadata
18
+
19
+ def render_panel(template, panel_attrs:)
20
+ renderer&.new(template)&.render(panel_attrs: panel_attrs, **options)
21
+ end
22
+
23
+ # Metadata used when no variant is selected.
24
+ module DefaultMetadata
25
+ module_function
26
+
27
+ def haspopup = "dialog"
28
+ def popup_id_for(panel_id) = panel_id
29
+ def trigger_icon = nil
30
+ def trigger_options = {}
31
+ def stimulus_data(_panel_id, _options) = {}
32
+ end
33
+
34
+ private
35
+
36
+ def select(renderer, options)
37
+ set_slot(:variant, { renderer: renderer, options: options })
38
+ nil
39
+ end
40
+
41
+ def selection = resolve(:variant)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Combobox
6
+ class Date
7
+ class Navigation < Plumber::Base
8
+ def render(step:, stimulus_controller:, view: "month", date: ::Date.today, **kwargs)
9
+ html_options = merge_html_options(
10
+ theme.resolve(:combobox_date_navigation),
11
+ kwargs,
12
+ { aria: { label: "Date picker navigation" } }
13
+ )
14
+
15
+ template.content_tag(:nav, **html_options) do
16
+ template.safe_join(navigators(stimulus_controller, step, view, date))
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def navigators(stimulus_controller, step, view, date)
23
+ [
24
+ navigator(stimulus_controller, target: "previous", icon: "arrow-left", label: prev_label(step)),
25
+ view_title_navigator(stimulus_controller, view, date),
26
+ navigator(stimulus_controller, target: "next", icon: "arrow-right", label: next_label(step))
27
+ ]
28
+ end
29
+
30
+ def navigator(stimulus_controller, target:, label:, icon: nil)
31
+ opts = {
32
+ aria: { label: label },
33
+ data: { "#{stimulus_controller}-target" => target }
34
+ }
35
+ opts[:icon] = icon if icon
36
+ Navigator.new(template).render(**opts)
37
+ end
38
+
39
+ def view_title_navigator(stimulus_controller, view, date)
40
+ Navigator.new(template).render(
41
+ data: {
42
+ "#{stimulus_controller}-target" => "viewTitle",
43
+ action: "click->#{stimulus_controller}#zoomOut"
44
+ }
45
+ ) { view_title_label(view, date) }
46
+ end
47
+
48
+ def view_title_label(view, date)
49
+ case view
50
+ when "year" then date.year.to_s
51
+ when "decade" then decade_label(date)
52
+ else I18n.l(date, format: "%B %Y")
53
+ end
54
+ end
55
+
56
+ def decade_label(date)
57
+ start = (date.year / 10) * 10
58
+ "#{start}–#{start + 9}"
59
+ end
60
+
61
+ def prev_label(step)
62
+ "Previous #{step.to_s.titleize}"
63
+ end
64
+
65
+ def next_label(step)
66
+ "Next #{step.to_s.titleize}"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Combobox
6
+ class Date
7
+ class Navigator < Plumber::Base
8
+ def render(...)
9
+ render_navigator(...)
10
+ end
11
+
12
+ private
13
+
14
+ def render_navigator(icon: nil, **kwargs, &block)
15
+ html_options = merge_html_options(
16
+ theme.resolve(:combobox_date_navigation_navigator),
17
+ kwargs
18
+ )
19
+ Components::Button.new(template).render(variant: :ghost, size: nil, icon_leading: icon, **html_options, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -5,48 +5,62 @@ module StimulusPlumbers
5
5
  class Combobox
6
6
  class Date < Plumber::Base
7
7
  STIMULUS_CONTROLLER = "combobox-date"
8
- CALENDAR_OUTLET = "#{STIMULUS_CONTROLLER}-calendar-month-outlet".freeze
8
+ CALENDAR_OUTLET = "#{STIMULUS_CONTROLLER}-calendar-month-outlet".freeze
9
+ STIMULUS_ACTION = [
10
+ "calendar-observer:selected->#{STIMULUS_CONTROLLER}#onSelect",
11
+ "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect",
12
+ "#{STIMULUS_CONTROLLER}:selected->#{Components::Popover::STIMULUS_CONTROLLER}#closeOnSelect"
13
+ ].join(" ").freeze
9
14
 
10
- def self.default_opts
11
- {
12
- input: { data: { combobox_date_date_value: nil } },
13
- popover: { label: "Picker", role: "dialog", tag: :div }
14
- }
15
+ def self.calendar_id_for(panel_id)
16
+ [panel_id, "calendar"].compact.join("_")
15
17
  end
16
18
 
17
- def self.calendar_id_for(popover_id)
18
- [popover_id, "calendar"].compact.join("_")
19
+ module Metadata
20
+ module_function
21
+
22
+ def haspopup = "dialog"
23
+ def popup_id_for(panel_id) = panel_id
24
+ def trigger_icon = "calendar"
25
+ def trigger_options = {}
26
+ def stimulus_data(_panel_id, _options) = { input_formatter_format_value: "date" }
19
27
  end
20
28
 
21
- def render(value: nil, popover_id: nil)
22
- calendar_id = self.class.calendar_id_for(popover_id)
29
+ def render(...) = render_date(...)
30
+
31
+ private
32
+
33
+ def render_date(panel_attrs: {}, value: nil, label: "Picker", labelledby: nil)
34
+ calendar_id = self.class.calendar_id_for(panel_attrs[:id])
35
+
36
+ template.content_tag(
37
+ :div,
38
+ **merge_html_options(panel_attrs, dialog_attrs(value, calendar_id, label, labelledby))
39
+ ) do
40
+ template.safe_join([navigation, calendar(id: calendar_id)])
41
+ end
42
+ end
23
43
 
44
+ def dialog_attrs(value, calendar_id, label, labelledby)
24
45
  data = {
25
- controller: STIMULUS_CONTROLLER,
46
+ controller: STIMULUS_CONTROLLER,
26
47
  CALENDAR_OUTLET => "##{calendar_id}",
27
- action: [
28
- "calendar-month-observer:selected->#{STIMULUS_CONTROLLER}#onSelect",
29
- "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect"
30
- ].join(" "),
48
+ action: STIMULUS_ACTION,
31
49
  "#{STIMULUS_CONTROLLER}-date-value" => value
32
50
  }.compact
33
51
 
34
- template.content_tag(:div, data: data) do
35
- template.safe_join([navigation, calendar_month(id: calendar_id)])
36
- end
52
+ { role: "dialog", aria: labelled_aria(label, labelledby: labelledby), data: data }
37
53
  end
38
54
 
39
- private
40
-
41
55
  def navigation
42
- DatePicker::Navigation.new(template).render(
56
+ Navigation.new(template).render(
43
57
  step: "month",
44
58
  stimulus_controller: STIMULUS_CONTROLLER
45
59
  )
46
60
  end
47
61
 
48
- def calendar_month(**kwargs)
49
- Calendar.new(template).month(**kwargs)
62
+ def calendar(**kwargs)
63
+ Calendar.new(template).render(**kwargs)
50
64
  end
51
65
  end
52
66
  end
@@ -8,33 +8,42 @@ module StimulusPlumbers
8
8
  STIMULUS_ACTION = [
9
9
  "click->#{STIMULUS_CONTROLLER}#select",
10
10
  "keydown->#{STIMULUS_CONTROLLER}#onNavigate",
11
- "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect"
11
+ "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect",
12
+ "#{STIMULUS_CONTROLLER}:selected->#{Components::Popover::STIMULUS_CONTROLLER}#closeOnSelect"
12
13
  ].join(" ").freeze
13
14
 
14
- def self.default_opts
15
- {
16
- popover: {
17
- tag: :div,
18
- haspopup: "listbox",
19
- data: { controller: STIMULUS_CONTROLLER, action: STIMULUS_ACTION }
20
- }
21
- }
15
+ module Metadata
16
+ module_function
17
+
18
+ def haspopup = "listbox"
19
+ def popup_id_for(panel_id) = panel_id
20
+ def trigger_icon = "chevron-down"
21
+ def trigger_options = {}
22
+ def stimulus_data(_panel_id, _options) = {}
22
23
  end
23
24
 
24
- def render(options: [], value: nil, label: nil, labelledby: nil)
25
- listbox_attrs = merge_html_options(
26
- { classes: theme.resolve(:combobox_listbox).fetch(:classes, "") },
27
- { role: "listbox", data: { "#{STIMULUS_CONTROLLER}_target": "listbox" } }
25
+ def render(...) = render_dropdown(...)
26
+
27
+ private
28
+
29
+ def render_dropdown(panel_attrs: {}, options: [], value: nil, label: nil, labelledby: nil)
30
+ template.content_tag(
31
+ :ul,
32
+ Options.new(template).render(options, value: value),
33
+ **listbox_attrs(panel_attrs: panel_attrs, label: label, labelledby: labelledby)
28
34
  )
29
- if labelledby
30
- listbox_attrs[:aria] = { labelledby: labelledby }
31
- elsif label
32
- listbox_attrs[:aria] = { label: label }
33
- end
35
+ end
34
36
 
35
- template.content_tag(:ul, **listbox_attrs) do
36
- Options.new(template).render(options, value: value)
37
- end
37
+ def listbox_attrs(panel_attrs: {}, label: nil, labelledby: nil)
38
+ merge_html_options(
39
+ panel_attrs,
40
+ theme.resolve(:combobox_listbox),
41
+ {
42
+ role: "listbox",
43
+ aria: labelled_aria(label, labelledby: labelledby),
44
+ data: { controller: STIMULUS_CONTROLLER, action: STIMULUS_ACTION, combobox_dropdown_target: "listbox" }
45
+ }
46
+ )
38
47
  end
39
48
  end
40
49
  end
@@ -5,12 +5,18 @@ module StimulusPlumbers
5
5
  class Combobox
6
6
  class Options
7
7
  class Option < Plumber::Base
8
- def render(label:, value:, description: nil, disabled: false, selected: false)
8
+ def render(...)
9
+ render_option(...)
10
+ end
11
+
12
+ private
13
+
14
+ def render_option(label:, value:, description: nil, disabled: false, selected: false)
9
15
  aria = { selected: selected ? "true" : "false" }
10
16
  aria[:disabled] = "true" if disabled
11
17
 
12
18
  attrs = merge_html_options(
13
- { classes: theme.resolve(:combobox_option, selected: selected, disabled: disabled).fetch(:classes, "") },
19
+ theme.resolve(:combobox_option, selected: selected, disabled: disabled),
14
20
  { role: "option", aria: aria, data: { value: value } }
15
21
  )
16
22
 
@@ -5,9 +5,15 @@ module StimulusPlumbers
5
5
  class Combobox
6
6
  class Options
7
7
  class OptionGroup < Plumber::Base
8
- def render(label:, options:, value: nil)
8
+ def render(...)
9
+ render_option_group(...)
10
+ end
11
+
12
+ private
13
+
14
+ def render_option_group(label:, options:, value: nil)
9
15
  attrs = merge_html_options(
10
- { classes: theme.resolve(:combobox_option_group).fetch(:classes, "") },
16
+ theme.resolve(:combobox_option_group),
11
17
  { role: "group", aria: { label: label } }
12
18
  )
13
19
 
@@ -4,21 +4,25 @@ module StimulusPlumbers
4
4
  module Components
5
5
  class Combobox
6
6
  class Options < Plumber::Base
7
- def render(items, value: nil, &block)
8
- @selected_value = value.to_s
9
- template.safe_join(items.filter_map { |item| render_item(item, &block) })
7
+ def render(...)
8
+ render_options(...)
10
9
  end
11
10
 
12
11
  private
13
12
 
13
+ def render_options(items, value: nil, &block)
14
+ @selected_value = value.to_s
15
+ template.safe_join(items.filter_map { |item| render_item(item, &block) })
16
+ end
17
+
14
18
  def render_item(item, &block)
15
19
  attrs = normalize_item(item)
16
20
  return nil if attrs.nil?
17
21
 
18
22
  if attrs.key?(:optgroup)
19
- block ? block.call(attrs) : OptionGroup.new(template).render(**attrs[:optgroup], value: @selected_value)
23
+ block ? template.capture(attrs, &block) : OptionGroup.new(template).render(**attrs[:optgroup], value: @selected_value)
20
24
  else
21
- block ? block.call(attrs) : Option.new(template).render(**attrs)
25
+ block ? template.capture(attrs, &block) : Option.new(template).render(**attrs)
22
26
  end
23
27
  end
24
28
 
@@ -5,11 +5,17 @@ module StimulusPlumbers
5
5
  class Combobox
6
6
  class Time
7
7
  class Drum < Plumber::Base
8
- def render(stimulus_controller:, target:, label:, items:, selected: nil)
8
+ def render(...)
9
+ render_drum(...)
10
+ end
11
+
12
+ private
13
+
14
+ def render_drum(stimulus_controller:, target:, label:, items:, selected: nil)
9
15
  template.content_tag(
10
16
  :ul,
11
17
  **merge_html_options(
12
- { classes: theme.resolve(:combobox_listbox).fetch(:classes, "") },
18
+ theme.resolve(:combobox_listbox),
13
19
  {
14
20
  role: "listbox",
15
21
  tabindex: "0",
@@ -5,33 +5,51 @@ module StimulusPlumbers
5
5
  class Combobox
6
6
  class Time < Plumber::Base
7
7
  STIMULUS_CONTROLLER = "combobox-time"
8
-
9
- def self.default_opts
10
- {
11
- popover: { label: "Picker", role: "dialog", tag: :div }
12
- }
8
+ STIMULUS_ACTION = [
9
+ "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect",
10
+ "#{STIMULUS_CONTROLLER}:selected->#{Components::Popover::STIMULUS_CONTROLLER}#closeOnSelect"
11
+ ].join(" ").freeze
12
+
13
+ module Metadata
14
+ module_function
15
+
16
+ def haspopup = "dialog"
17
+ def popup_id_for(panel_id) = panel_id
18
+ def trigger_icon = "clock"
19
+ def trigger_options = {}
20
+
21
+ def stimulus_data(_panel_id, options)
22
+ {
23
+ input_formatter_format_value: "time",
24
+ input_formatter_options_value: { format: options.fetch(:format, :h12) }.to_json
25
+ }
26
+ end
13
27
  end
14
28
 
15
- def render(format: :h12, step: 1, value: nil)
29
+ def render(...) = render_time(...)
30
+
31
+ private
32
+
33
+ def render_time(panel_attrs: {}, format: :h12, step: 1, value: nil, label: "Picker", labelledby: nil)
16
34
  @format = format
17
35
  @step = [1, step.to_i].max
18
36
  @time = parse_time(value)
19
37
 
20
- template.content_tag(
21
- :div,
22
- **merge_html_options(
23
- { classes: theme.resolve(:combobox_time).fetch(:classes, "") },
24
- { data: { controller: STIMULUS_CONTROLLER,
25
- action: "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect"
26
- }
27
- }
28
- )
29
- ) do
30
- template.safe_join(drums)
31
- end
38
+ attrs = merge_html_options(
39
+ panel_attrs,
40
+ theme.resolve(:combobox_time),
41
+ dialog_attrs(label, labelledby)
42
+ )
43
+ template.content_tag(:div, **attrs) { template.safe_join(drums) }
32
44
  end
33
45
 
34
- private
46
+ def dialog_attrs(label, labelledby)
47
+ {
48
+ role: "dialog",
49
+ aria: labelled_aria(label, labelledby: labelledby),
50
+ data: { controller: STIMULUS_CONTROLLER, action: STIMULUS_ACTION }
51
+ }
52
+ end
35
53
 
36
54
  def drums
37
55
  cols = [hour_drum, minute_drum]
@@ -39,50 +57,35 @@ module StimulusPlumbers
39
57
  cols
40
58
  end
41
59
 
42
- def hour_drum
60
+ def render_drum(target, label, items, selected)
43
61
  drum.render(
44
62
  stimulus_controller: STIMULUS_CONTROLLER,
45
- target: "hour",
46
- label: "Hour",
47
- items: hour_items,
48
- selected: current_hour
63
+ target: target,
64
+ label: label,
65
+ items: items,
66
+ selected: selected
49
67
  )
50
68
  end
51
69
 
70
+ def hour_drum
71
+ render_drum("hour", "Hour", hour_items, current_hour)
72
+ end
73
+
52
74
  def minute_drum
53
- items = (0...60).step(@step).map do |m|
54
- s = m.to_s.rjust(2, "0")
55
- [s, s]
56
- end
75
+ items = (0...60).step(@step).map { |m| [m.to_s.rjust(2, "0")] * 2 }
57
76
  selected = @time ? snap_minute(@time.min).to_s.rjust(2, "0") : nil
58
- drum.render(
59
- stimulus_controller: STIMULUS_CONTROLLER,
60
- target: "minute",
61
- label: "Minute",
62
- items: items,
63
- selected: selected
64
- )
77
+ render_drum("minute", "Minute", items, selected)
65
78
  end
66
79
 
67
80
  def period_drum
68
- selected = @time && (@time.hour < 12 ? "AM" : "PM")
69
- drum.render(
70
- stimulus_controller: STIMULUS_CONTROLLER,
71
- target: "period",
72
- label: "Period",
73
- items: [%w[AM AM], %w[PM PM]],
74
- selected: selected
75
- )
81
+ render_drum("period", "Period", [%w[AM AM], %w[PM PM]], @time && (@time.hour < 12 ? "AM" : "PM"))
76
82
  end
77
83
 
78
84
  def hour_items
79
85
  if @format == :h12
80
86
  (1..12).map { |h| [h.to_s, h.to_s] }
81
87
  else
82
- (0..23).map do |h|
83
- s = h.to_s.rjust(2, "0")
84
- [s, s]
85
- end
88
+ (0..23).map { |h| [h.to_s.rjust(2, "0")] * 2 }
86
89
  end
87
90
  end
88
91
 
@@ -4,43 +4,91 @@ module StimulusPlumbers
4
4
  module Components
5
5
  class Combobox
6
6
  class Trigger < Plumber::Base
7
- def render(
7
+ STIMULUS_ACTION = [
8
+ "focus->#{Components::Popover::STIMULUS_CONTROLLER}#open",
9
+ "keydown.esc->#{Components::Popover::STIMULUS_CONTROLLER}#close"
10
+ ].join(" ").freeze
11
+
12
+ def render(...)
13
+ render_trigger(...)
14
+ end
15
+
16
+ private
17
+
18
+ def render_trigger(
8
19
  stimulus_controller:,
9
- popover_id:,
10
- haspopup:,
20
+ popover:,
11
21
  readonly: true,
12
- aria_autocomplete: nil,
13
- aria_label: nil,
14
22
  aria: {},
15
23
  id: nil,
16
24
  data: {},
25
+ icon_leading: nil,
26
+ icon_trailing: nil,
27
+ **kwargs
28
+ )
29
+ input_html = render_input(
30
+ stimulus_controller: stimulus_controller,
31
+ popover: popover,
32
+ readonly: readonly,
33
+ aria: aria,
34
+ id: id,
35
+ data: data,
36
+ **kwargs
37
+ )
38
+
39
+ return input_html unless icon_leading || icon_trailing
40
+
41
+ render_trigger_group(icon_leading, icon_trailing) { input_html }
42
+ end
43
+
44
+ def render_input(
45
+ stimulus_controller:,
46
+ popover:,
47
+ readonly:,
48
+ aria:,
49
+ id:,
50
+ data:,
17
51
  **kwargs
18
52
  )
19
53
  stimulus_data = {
54
+ popover_target: popover.dig(:data, :popover_target),
20
55
  "#{stimulus_controller}_target": "trigger",
21
- input_format_target: "input",
22
- action: "focus->#{stimulus_controller}#open keydown.esc->#{stimulus_controller}#close"
56
+ input_formatter_target: "input",
57
+ action: STIMULUS_ACTION
23
58
  }
24
59
 
25
- trigger_aria = { haspopup: haspopup, expanded: "false", controls: popover_id }
26
- trigger_aria[:autocomplete] = aria_autocomplete if aria_autocomplete
27
- trigger_aria[:label] = aria_label if aria_label
28
-
29
60
  template.tag.input(
30
61
  **merge_html_options(
31
- { classes: theme.resolve(:combobox_trigger).fetch(:classes, "") },
62
+ theme.resolve(:combobox_trigger),
32
63
  {
33
64
  id: id,
34
65
  type: "text",
35
66
  readonly: (readonly ? true : nil),
36
67
  role: "combobox",
37
- aria: trigger_aria.deep_merge(aria),
38
- data: merge_data_options(stimulus_data, data)
68
+ aria: popover.fetch(:aria, {}).deep_merge(aria),
69
+ data: stimulus_data
39
70
  },
71
+ { data: data },
40
72
  kwargs
41
73
  )
42
74
  )
43
75
  end
76
+
77
+ def render_trigger_group(icon_leading, icon_trailing, &block)
78
+ InputGroup.new(template).render(
79
+ leading: icon_leading ? -> { render_trigger_icon(icon_leading) } : nil,
80
+ trailing: icon_trailing ? -> { render_trigger_icon(icon_trailing) } : nil,
81
+ **theme.resolve(:combobox_trigger_group)
82
+ ) { template.capture(&block) }
83
+ end
84
+
85
+ def render_trigger_icon(name)
86
+ Components::Icon.new(template).render(
87
+ name: name,
88
+ classes: theme.resolve(:combobox_trigger_icon).fetch(:classes, ""),
89
+ aria: { hidden: "true" }
90
+ )
91
+ end
44
92
  end
45
93
  end
46
94
  end