stimulus_plumbers 0.3.3 → 0.4.2

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -0
  3. data/README.md +11 -4
  4. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +386 -0
  5. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +756 -304
  6. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
  7. data/app/assets/stylesheets/stimulus_plumbers/tokens.css +66 -26
  8. data/config/locales/en.yml +31 -0
  9. data/lib/stimulus_plumbers/components/avatar.rb +22 -13
  10. data/lib/stimulus_plumbers/components/button/group.rb +9 -4
  11. data/lib/stimulus_plumbers/components/button/slots.rb +11 -0
  12. data/lib/stimulus_plumbers/components/button.rb +30 -34
  13. data/lib/stimulus_plumbers/components/calendar/turbo/days_of_month.rb +191 -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 +137 -0
  16. data/lib/stimulus_plumbers/components/calendar/turbo/years_of_decade.rb +114 -0
  17. data/lib/stimulus_plumbers/components/calendar/turbo.rb +112 -0
  18. data/lib/stimulus_plumbers/components/calendar.rb +57 -32
  19. data/lib/stimulus_plumbers/components/card/slots.rb +26 -0
  20. data/lib/stimulus_plumbers/components/card.rb +53 -13
  21. data/lib/stimulus_plumbers/components/combobox/builder.rb +45 -0
  22. data/lib/stimulus_plumbers/components/combobox/date/navigation.rb +80 -0
  23. data/lib/stimulus_plumbers/components/combobox/date/navigator.rb +27 -0
  24. data/lib/stimulus_plumbers/components/combobox/date.rb +55 -28
  25. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +27 -24
  26. data/lib/stimulus_plumbers/components/combobox/options/option.rb +1 -1
  27. data/lib/stimulus_plumbers/components/combobox/options/option_group.rb +1 -1
  28. data/lib/stimulus_plumbers/components/combobox/time.rb +83 -78
  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/input_group.rb +2 -2
  35. data/lib/stimulus_plumbers/components/link/slots.rb +11 -0
  36. data/lib/stimulus_plumbers/components/link.rb +63 -0
  37. data/lib/stimulus_plumbers/components/list/item/slots.rb +13 -0
  38. data/lib/stimulus_plumbers/components/list/item.rb +83 -0
  39. data/lib/stimulus_plumbers/components/list/section.rb +73 -0
  40. data/lib/stimulus_plumbers/components/list.rb +31 -0
  41. data/lib/stimulus_plumbers/components/popover/panel.rb +32 -0
  42. data/lib/stimulus_plumbers/components/popover/trigger.rb +27 -0
  43. data/lib/stimulus_plumbers/components/popover.rb +44 -18
  44. data/lib/stimulus_plumbers/components/timeline/event/slots.rb +18 -0
  45. data/lib/stimulus_plumbers/components/timeline/event.rb +189 -0
  46. data/lib/stimulus_plumbers/components/timeline/group.rb +42 -0
  47. data/lib/stimulus_plumbers/components/timeline.rb +40 -0
  48. data/lib/stimulus_plumbers/engine.rb +1 -0
  49. data/lib/stimulus_plumbers/form/base.rb +103 -0
  50. data/lib/stimulus_plumbers/form/builder.rb +78 -23
  51. data/lib/stimulus_plumbers/form/field.rb +55 -88
  52. data/lib/stimulus_plumbers/form/fields/error.rb +1 -1
  53. data/lib/stimulus_plumbers/form/fields/fieldset.rb +11 -8
  54. data/lib/stimulus_plumbers/form/fields/hint.rb +1 -1
  55. data/lib/stimulus_plumbers/form/fields/inputs/checkbox.rb +115 -0
  56. data/lib/stimulus_plumbers/form/fields/inputs/combobox.rb +24 -0
  57. data/lib/stimulus_plumbers/form/fields/inputs/datetime.rb +52 -58
  58. data/lib/stimulus_plumbers/form/fields/inputs/file.rb +15 -9
  59. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +46 -28
  60. data/lib/stimulus_plumbers/form/fields/inputs/radio.rb +72 -0
  61. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +73 -65
  62. data/lib/stimulus_plumbers/form/fields/inputs/select/grouped.rb +22 -33
  63. data/lib/stimulus_plumbers/form/fields/inputs/select/timezone.rb +3 -46
  64. data/lib/stimulus_plumbers/form/fields/inputs/select/weekday.rb +3 -26
  65. data/lib/stimulus_plumbers/form/fields/inputs/select.rb +64 -61
  66. data/lib/stimulus_plumbers/form/fields/inputs/submit.rb +10 -7
  67. data/lib/stimulus_plumbers/form/fields/inputs/text.rb +34 -22
  68. data/lib/stimulus_plumbers/form/fields/inputs/text_area.rb +15 -9
  69. data/lib/stimulus_plumbers/form/fields/label/floating.rb +41 -0
  70. data/lib/stimulus_plumbers/form/fields/label.rb +9 -3
  71. data/lib/stimulus_plumbers/form/fields/renderer.rb +39 -0
  72. data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -1
  73. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -2
  74. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +76 -4
  75. data/lib/stimulus_plumbers/helpers/card_helper.rb +1 -11
  76. data/lib/stimulus_plumbers/helpers/combobox_helper.rb +27 -60
  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/timeline_helper.rb +15 -0
  82. data/lib/stimulus_plumbers/helpers.rb +8 -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 +6 -7
  96. data/lib/stimulus_plumbers/themes/schema/avatar/ranges.rb +13 -0
  97. data/lib/stimulus_plumbers/themes/schema/button/ranges.rb +16 -0
  98. data/lib/stimulus_plumbers/themes/schema/card/ranges.rb +13 -0
  99. data/lib/stimulus_plumbers/themes/schema/form/checkbox/ranges.rb +16 -0
  100. data/lib/stimulus_plumbers/themes/schema/form/floating/ranges.rb +15 -0
  101. data/lib/stimulus_plumbers/themes/schema/form/radio/ranges.rb +16 -0
  102. data/lib/stimulus_plumbers/themes/schema/form/ranges.rb +1 -2
  103. data/lib/stimulus_plumbers/themes/schema/link/ranges.rb +14 -0
  104. data/lib/stimulus_plumbers/themes/schema/ranges.rb +1 -5
  105. data/lib/stimulus_plumbers/themes/schema.rb +169 -51
  106. data/lib/stimulus_plumbers/version.rb +1 -1
  107. data/lib/stimulus_plumbers.rb +24 -16
  108. metadata +48 -16
  109. data/lib/stimulus_plumbers/components/action_list/item.rb +0 -30
  110. data/lib/stimulus_plumbers/components/action_list/section.rb +0 -28
  111. data/lib/stimulus_plumbers/components/action_list.rb +0 -29
  112. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +0 -149
  113. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +0 -43
  114. data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +0 -59
  115. data/lib/stimulus_plumbers/components/card/section.rb +0 -31
  116. data/lib/stimulus_plumbers/components/combobox/popover.rb +0 -47
  117. data/lib/stimulus_plumbers/components/combobox/time/drum.rb +0 -43
  118. data/lib/stimulus_plumbers/components/date_picker/navigation.rb +0 -41
  119. data/lib/stimulus_plumbers/components/date_picker/navigator.rb +0 -23
  120. data/lib/stimulus_plumbers/components/popover/builder.rb +0 -25
  121. data/lib/stimulus_plumbers/form/fields/inputs/choice.rb +0 -69
  122. data/lib/stimulus_plumbers/helpers/action_list_helper.rb +0 -25
  123. data/lib/stimulus_plumbers/plumber/html_options.rb +0 -52
@@ -3,55 +3,80 @@
3
3
  module StimulusPlumbers
4
4
  module Components
5
5
  class Calendar < Plumber::Base
6
- STIMULUS_CONTROLLER = "calendar-month"
7
- OBSERVER_STIMULUS_CONTROLLER = "calendar-month-observer"
8
- STIMULUS_DATA = {
9
- controller: "#{STIMULUS_CONTROLLER} #{OBSERVER_STIMULUS_CONTROLLER}",
10
- action: [
11
- "click->#{OBSERVER_STIMULUS_CONTROLLER}#onSelect",
12
- "#{OBSERVER_STIMULUS_CONTROLLER}:selected->#{STIMULUS_CONTROLLER}#onSelect"
13
- ].join(" ")
14
- }.freeze
6
+ MONTH_STIMULUS_CONTROLLER = "calendar-month"
7
+ YEAR_STIMULUS_CONTROLLER = "calendar-year"
8
+ DECADE_STIMULUS_CONTROLLER = "calendar-decade"
15
9
 
16
10
  def month(**kwargs)
17
- template.content_tag(:div, role: "grid", **month_html_options(kwargs)) do
18
- template.safe_join([template.tag.div(**dow_options), template.tag.div(**dom_options)])
11
+ html_options = merge_html_options(
12
+ theme.resolve(:calendar),
13
+ kwargs,
14
+ { data: { controller: MONTH_STIMULUS_CONTROLLER } },
15
+ { data: stimulus_theme_options }
16
+ )
17
+ template.content_tag(:div, **html_options, role: "grid") do
18
+ template.safe_join([days_of_week, days_of_month])
19
19
  end
20
20
  end
21
21
 
22
- private
22
+ def year(**kwargs)
23
+ html_options = merge_html_options(
24
+ {
25
+ hidden: true,
26
+ role: "grid",
27
+ aria: { label: I18n.t("stimulus_plumbers.calendar.year_view") },
28
+ data: { controller: YEAR_STIMULUS_CONTROLLER }
29
+ },
30
+ kwargs
31
+ )
32
+ template.content_tag(:div, **html_options) do
33
+ template.tag.div(data: { "#{YEAR_STIMULUS_CONTROLLER}-target": "grid" }, role: "rowgroup")
34
+ end
35
+ end
23
36
 
24
- def month_html_options(kwargs)
25
- merge_html_options(
37
+ def decade(**kwargs)
38
+ html_options = merge_html_options(
26
39
  {
27
- classes: theme.resolve(:calendar).fetch(:classes, ""),
28
- data: month_stimulus_data
40
+ hidden: true,
41
+ role: "grid",
42
+ aria: { label: I18n.t("stimulus_plumbers.calendar.decade_view") },
43
+ data: { controller: DECADE_STIMULUS_CONTROLLER }
29
44
  },
30
45
  kwargs
31
46
  )
47
+ template.content_tag(:div, **html_options) do
48
+ template.tag.div(data: { "#{DECADE_STIMULUS_CONTROLLER}-target": "grid" }, role: "rowgroup")
49
+ end
32
50
  end
33
51
 
34
- def month_stimulus_data
35
- STIMULUS_DATA.merge(
36
- calendar_month_week_class: theme.resolve(:calendar_week).fetch(:classes, ""),
37
- calendar_month_day_of_month_class: theme.resolve(:calendar_day).fetch(:classes, "")
38
- ).compact_blank
52
+ private
53
+
54
+ def stimulus_theme_options
55
+ {
56
+ "#{MONTH_STIMULUS_CONTROLLER}-row-class": theme.resolve(:calendar_row).fetch(:classes, ""),
57
+ "#{MONTH_STIMULUS_CONTROLLER}-day-of-month-class": theme.resolve(:calendar_day).fetch(:classes, ""),
58
+ "#{MONTH_STIMULUS_CONTROLLER}-day-of-other-month-class": theme.resolve(:calendar_day, outside: true).fetch(:classes, "")
59
+ }
39
60
  end
40
61
 
41
- def dow_options
42
- merge_html_options(
43
- { classes: theme.resolve(:calendar_days_of_week).fetch(:classes, "") },
44
- { data: { "#{STIMULUS_CONTROLLER}-target": "daysOfWeek" } }
62
+ def days_of_week
63
+ template.tag.div(
64
+ **merge_html_options(
65
+ theme.resolve(:calendar_days_of_week),
66
+ { data: { "#{MONTH_STIMULUS_CONTROLLER}-target": "daysOfWeek" } }
67
+ )
45
68
  )
46
69
  end
47
70
 
48
- def dom_options
49
- merge_html_options(
50
- { classes: theme.resolve(:calendar_days_of_month).fetch(:classes, "") },
51
- {
52
- role: "rowgroup",
53
- data: { "#{STIMULUS_CONTROLLER}-target": "daysOfMonth" }
54
- }
71
+ def days_of_month
72
+ template.tag.div(
73
+ **merge_html_options(
74
+ theme.resolve(:calendar_days_of_month),
75
+ {
76
+ role: "rowgroup",
77
+ data: { "#{MONTH_STIMULUS_CONTROLLER}-target": "daysOfMonth" }
78
+ }
79
+ )
55
80
  )
56
81
  end
57
82
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Card
6
+ class Slots < Plumber::Slots
7
+ slot :icon, :title
8
+
9
+ def with_body(&block)
10
+ raise ArgumentError, "card.body requires a block" unless block
11
+
12
+ set_slot(:body, block)
13
+ nil
14
+ end
15
+
16
+ # Defined manually (not via `slot` DSL) because it requires a named `url:` keyword with validation.
17
+ def with_action(value = nil, url: nil, &block)
18
+ raise ArgumentError, "card.action requires content (string or block) when url: is given" if url && value.nil? && !block
19
+
20
+ set_slot(:action, block || value, url ? { url: url } : {})
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -3,31 +3,71 @@
3
3
  module StimulusPlumbers
4
4
  module Components
5
5
  class Card < Plumber::Base
6
- def render(...)
7
- render_card(...)
8
- end
6
+ def render(variant: :tertiary, title_tag: :h2, **kwargs, &block)
7
+ slots = Card::Slots.new
8
+ yield slots if block_given?
9
9
 
10
- def section(...)
11
- Card::Section.new(template).render(...)
10
+ html_options = merge_html_options(theme.resolve(:card, variant: variant), kwargs)
11
+ template.content_tag(:div, **html_options) do
12
+ template.safe_join(
13
+ [
14
+ render_header(slots, title_tag),
15
+ render_body(slots),
16
+ render_action(slots)
17
+ ]
18
+ )
19
+ end
12
20
  end
13
21
 
14
22
  private
15
23
 
16
- def render_card(title: nil, title_tag: :h2, **kwargs, &block)
17
- html_options = merge_html_options(
18
- { classes: theme.resolve(:card).fetch(:classes, "") },
19
- kwargs
20
- )
24
+ def render_header(slots, title_tag)
25
+ icon = slots.resolve(:icon) { |value| render_icon_slot(value) }
26
+ title = slots.resolve(:title)
27
+ return unless icon || title
21
28
 
22
- template.content_tag(:div, **html_options) do
29
+ template.content_tag(:div, **merge_html_options(theme.resolve(:card_header))) do
23
30
  template.safe_join(
24
31
  [
25
- (template.content_tag(title_tag, title) if title.present?),
26
- template.capture(&block)
32
+ icon,
33
+ (template.content_tag(title_tag, title, **merge_html_options(theme.resolve(:card_title))) if title)
27
34
  ]
28
35
  )
29
36
  end
30
37
  end
38
+
39
+ def render_body(slots)
40
+ content = slots.resolve(:body)
41
+ return unless content
42
+
43
+ template.content_tag(:div, **merge_html_options(theme.resolve(:card_body))) do
44
+ content
45
+ end
46
+ end
47
+
48
+ def render_icon_slot(value)
49
+ return value unless value.is_a?(Symbol) || (value.is_a?(String) && !value.html_safe?)
50
+
51
+ Components::Icon.new(template).render(
52
+ name: value,
53
+ classes: theme.resolve(:card_icon).fetch(:classes, ""),
54
+ aria: { hidden: "true" }
55
+ )
56
+ end
57
+
58
+ def render_action(slots)
59
+ content = slots.resolve(:action)
60
+ return unless content
61
+
62
+ url = slots.options_for(:action)[:url]
63
+ Components::Button.new(template).build(type: :ghost, variant: :tertiary) do |attrs|
64
+ element = url.present? ? :a : :button
65
+ extra = url.present? ? { href: url } : { type: "button" }
66
+ template.content_tag(element, **merge_html_options(attrs, extra, theme.resolve(:card_action))) do
67
+ template.content_tag(:span, content)
68
+ end
69
+ end
70
+ end
31
71
  end
32
72
  end
33
73
  end
@@ -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,80 @@
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: t("navigation_label") } }
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(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(stimulus_controller, view, date)
40
+ html_options = merge_html_options(
41
+ theme.resolve(:combobox_date_navigation_title),
42
+ data: {
43
+ "#{stimulus_controller}-target" => "viewTitle",
44
+ action: "click->#{stimulus_controller}#zoomOut"
45
+ }
46
+ )
47
+ Components::Button.new(template).render(
48
+ type: :ghost, variant: :tertiary, size: :sm, **html_options
49
+ ) { view_label(view, date) }
50
+ end
51
+
52
+ def view_label(view, date)
53
+ case view
54
+ when "year" then date.year.to_s
55
+ when "decade" then decade_label(date)
56
+ else I18n.l(date, format: "%B %Y")
57
+ end
58
+ end
59
+
60
+ def decade_label(date)
61
+ start = (date.year / 10) * 10
62
+ "#{start}–#{start + 9}"
63
+ end
64
+
65
+ def prev_label(step)
66
+ t("previous_#{step}")
67
+ end
68
+
69
+ def next_label(step)
70
+ t("next_#{step}")
71
+ end
72
+
73
+ def t(key)
74
+ I18n.t("stimulus_plumbers.combobox.date.#{key}")
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,27 @@
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(
20
+ type: :ghost, variant: :tertiary, size: :sm, icon_leading: icon, **html_options, &block
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -4,53 +4,80 @@ module StimulusPlumbers
4
4
  module Components
5
5
  class Combobox
6
6
  class Date < Plumber::Base
7
- STIMULUS_CONTROLLER = "combobox-date"
8
- CALENDAR_OUTLET = "#{STIMULUS_CONTROLLER}-calendar-month-outlet".freeze
9
-
10
- def self.default_opts
11
- {
12
- input: { data: { combobox_date_date_value: nil } },
13
- popover: { label: "Picker", role: "dialog", tag: :div }
14
- }
15
- end
7
+ STIMULUS_CONTROLLER = "combobox-date"
8
+ CALENDAR_MONTH_OUTLET = "#{STIMULUS_CONTROLLER}-calendar-month-outlet".freeze
9
+ CALENDAR_YEAR_OUTLET = "#{STIMULUS_CONTROLLER}-calendar-year-outlet".freeze
10
+ CALENDAR_DECADE_OUTLET = "#{STIMULUS_CONTROLLER}-calendar-decade-outlet".freeze
11
+ STIMULUS_ACTION = [
12
+ "calendar-month:selected->#{STIMULUS_CONTROLLER}#onDaySelect",
13
+ "calendar-year:selected->#{STIMULUS_CONTROLLER}#onMonthSelect",
14
+ "calendar-decade:selected->#{STIMULUS_CONTROLLER}#onYearSelect",
15
+ "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect",
16
+ "#{STIMULUS_CONTROLLER}:selected->#{Components::Popover::STIMULUS_CONTROLLER}#closeOnSelect"
17
+ ].join(" ").freeze
16
18
 
17
- def self.calendar_id_for(popover_id)
18
- [popover_id, "calendar"].compact.join("_")
19
- end
19
+ def self.month_id_for(panel_id) = [panel_id, "calendar_month"].compact.join("_")
20
+ def self.year_id_for(panel_id) = [panel_id, "calendar_year"].compact.join("_")
21
+ def self.decade_id_for(panel_id) = [panel_id, "calendar_decade"].compact.join("_")
22
+
23
+ module Metadata
24
+ module_function
20
25
 
21
- def render(...)
22
- render_date(...)
26
+ def haspopup = "dialog"
27
+ def popup_id_for(panel_id) = panel_id
28
+ def trigger_icon = "calendar"
29
+ def trigger_options = {}
30
+ def stimulus_data(_panel_id, _options) = { input_formatter_format_value: "date" }
23
31
  end
24
32
 
33
+ def render(...) = render_date(...)
34
+
25
35
  private
26
36
 
27
- def render_date(value: nil, popover_id: nil)
28
- calendar_id = self.class.calendar_id_for(popover_id)
37
+ def render_date(panel_attrs: {}, value: nil, label: nil, labelledby: nil)
38
+ panel_id = panel_attrs[:id]
39
+ month_id = self.class.month_id_for(panel_id)
40
+ year_id = self.class.year_id_for(panel_id)
41
+ decade_id = self.class.decade_id_for(panel_id)
29
42
 
43
+ template.content_tag(
44
+ :div,
45
+ **merge_html_options(panel_attrs, dialog_attrs(value, month_id, year_id, decade_id, label, labelledby))
46
+ ) do
47
+ template.safe_join([navigation, calendar(month_id: month_id, year_id: year_id, decade_id: decade_id)])
48
+ end
49
+ end
50
+
51
+ def dialog_attrs(value, month_id, year_id, decade_id, label, labelledby)
30
52
  data = {
31
- controller: STIMULUS_CONTROLLER,
32
- CALENDAR_OUTLET => "##{calendar_id}",
33
- action: [
34
- "calendar-month-observer:selected->#{STIMULUS_CONTROLLER}#onSelect",
35
- "#{STIMULUS_CONTROLLER}:selected->#{Combobox::STIMULUS_CONTROLLER}#onSelect"
36
- ].join(" "),
53
+ controller: STIMULUS_CONTROLLER,
54
+ CALENDAR_MONTH_OUTLET => "##{month_id}",
55
+ CALENDAR_YEAR_OUTLET => "##{year_id}",
56
+ CALENDAR_DECADE_OUTLET => "##{decade_id}",
57
+ action: STIMULUS_ACTION,
37
58
  "#{STIMULUS_CONTROLLER}-date-value" => value
38
59
  }.compact
39
60
 
40
- template.content_tag(:div, data: data) do
41
- template.safe_join([navigation, calendar_month(id: calendar_id)])
42
- end
61
+ resolved_label = label || I18n.t("stimulus_plumbers.combobox.date.dialog_label")
62
+ { role: "dialog", aria: labelled_aria(resolved_label, labelledby: labelledby), data: data }
43
63
  end
44
64
 
45
65
  def navigation
46
- DatePicker::Navigation.new(template).render(
66
+ Navigation.new(template).render(
47
67
  step: "month",
48
68
  stimulus_controller: STIMULUS_CONTROLLER
49
69
  )
50
70
  end
51
71
 
52
- def calendar_month(**kwargs)
53
- Calendar.new(template).month(**kwargs)
72
+ def calendar(month_id:, year_id:, decade_id:)
73
+ cal = Calendar.new(template)
74
+ template.safe_join(
75
+ [
76
+ cal.month(id: month_id),
77
+ cal.year(id: year_id),
78
+ cal.decade(id: decade_id)
79
+ ]
80
+ )
54
81
  end
55
82
  end
56
83
  end
@@ -8,39 +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
- }
22
- end
15
+ module Metadata
16
+ module_function
23
17
 
24
- def render(...)
25
- render_dropdown(...)
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) = {}
26
23
  end
27
24
 
25
+ def render(...) = render_dropdown(...)
26
+
28
27
  private
29
28
 
30
- def render_dropdown(options: [], value: nil, label: nil, labelledby: nil)
31
- listbox_attrs = merge_html_options(
32
- { classes: theme.resolve(:combobox_listbox).fetch(:classes, "") },
33
- { role: "listbox", data: { "#{STIMULUS_CONTROLLER}_target": "listbox" } }
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)
34
34
  )
35
- if labelledby
36
- listbox_attrs[:aria] = { labelledby: labelledby }
37
- elsif label
38
- listbox_attrs[:aria] = { label: label }
39
- end
35
+ end
40
36
 
41
- template.content_tag(:ul, **listbox_attrs) do
42
- Options.new(template).render(options, value: value)
43
- 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
+ )
44
47
  end
45
48
  end
46
49
  end
@@ -16,7 +16,7 @@ module StimulusPlumbers
16
16
  aria[:disabled] = "true" if disabled
17
17
 
18
18
  attrs = merge_html_options(
19
- { classes: theme.resolve(:combobox_option, selected: selected, disabled: disabled).fetch(:classes, "") },
19
+ theme.resolve(:combobox_option, selected: selected, disabled: disabled),
20
20
  { role: "option", aria: aria, data: { value: value } }
21
21
  )
22
22
 
@@ -13,7 +13,7 @@ module StimulusPlumbers
13
13
 
14
14
  def render_option_group(label:, options:, value: nil)
15
15
  attrs = merge_html_options(
16
- { classes: theme.resolve(:combobox_option_group).fetch(:classes, "") },
16
+ theme.resolve(:combobox_option_group),
17
17
  { role: "group", aria: { label: label } }
18
18
  )
19
19