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
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Calendar
6
+ class Turbo
7
+ class DaysOfMonth < Plumber::Base
8
+ DAYS_PER_ROW = 7
9
+
10
+ attr_reader :date, :today, :selectable, :selected_date, :show_other_months, :since, :till
11
+
12
+ def initialize(
13
+ template,
14
+ date: Date.today,
15
+ today: Date.today,
16
+ selectable: false,
17
+ selected_date: nil,
18
+ show_other_months: false,
19
+ since: nil,
20
+ till: nil
21
+ )
22
+ super(template)
23
+ @date = date
24
+ @today = today
25
+ @selectable = selectable
26
+ @selected_date = selected_date
27
+ @show_other_months = show_other_months
28
+ @since = since
29
+ @till = till
30
+ end
31
+
32
+ def render(...)
33
+ render_days_of_month(...)
34
+ end
35
+
36
+ private
37
+
38
+ def render_days_of_month(**html_options)
39
+ html_options = merge_html_options(
40
+ theme.resolve(:calendar_days_of_month),
41
+ html_options
42
+ )
43
+ template.content_tag(:div, **html_options, role: "rowgroup") { weeks_of_month }
44
+ end
45
+
46
+ def weeks_of_month
47
+ week_options = merge_html_options(
48
+ theme.resolve(:calendar_row),
49
+ { role: "row" }
50
+ )
51
+ template.safe_join(
52
+ build_days.each_slice(DAYS_PER_ROW).map do |days|
53
+ template.content_tag(:div, **week_options) { days_in_row(days) }
54
+ end
55
+ )
56
+ end
57
+
58
+ def focus_day
59
+ @focus_day ||= if selected_date_in_current_month?
60
+ selected_date
61
+ elsif today_in_current_month?
62
+ today
63
+ else
64
+ date.beginning_of_month
65
+ end
66
+ end
67
+
68
+ def build_days
69
+ first = date.beginning_of_month
70
+ last = date.end_of_month
71
+ current_days = first.upto(last).to_a
72
+ prev_filler_days(first) + current_days + next_filler_days(last, current_days.length)
73
+ end
74
+
75
+ def prev_filler_days(first_day_of_month)
76
+ week_start = first_day_of_month - first_day_of_month.wday
77
+ week_start.upto(first_day_of_month - 1).to_a
78
+ end
79
+
80
+ def next_filler_days(last_day_of_month, days_in_month)
81
+ week_start_offset = last_day_of_month.beginning_of_month.wday
82
+ total = week_start_offset + days_in_month
83
+ next_count = (DAYS_PER_ROW - (total % DAYS_PER_ROW)) % DAYS_PER_ROW
84
+ next_count.positive? ? (last_day_of_month + 1).upto(last_day_of_month + next_count).to_a : []
85
+ end
86
+
87
+ def selected_date_in_current_month?
88
+ selected_date&.month == date.month && selected_date&.year == date.year
89
+ end
90
+
91
+ def today_in_current_month?
92
+ today.month == date.month && today.year == date.year
93
+ end
94
+
95
+ def days_in_row(days)
96
+ template.safe_join(
97
+ days.map do |day|
98
+ if day.month == date.month
99
+ current_month_day_cell(day)
100
+ elsif show_other_months
101
+ other_month_day_cell(day)
102
+ else
103
+ hidden_day_cell(day)
104
+ end
105
+ end
106
+ )
107
+ end
108
+
109
+ def hidden_day_cell(date)
110
+ html_options = merge_html_options(
111
+ theme.resolve(:calendar_day),
112
+ { role: "gridcell", tabindex: -1, aria: { hidden: "true" } }
113
+ )
114
+ template.content_tag(:span, **html_options) do
115
+ template.content_tag(:time, nil, datetime: date.iso8601)
116
+ end
117
+ end
118
+
119
+ def current_month_day_cell(date)
120
+ if in_range?(date)
121
+ tag = selectable ? :button : :span
122
+ template.content_tag(tag, **day_cell_html_options(date)) do
123
+ template.content_tag(:time, date.day.to_s, datetime: date.iso8601)
124
+ end
125
+ else
126
+ disabled_current_month_day_cell(date)
127
+ end
128
+ end
129
+
130
+ def disabled_current_month_day_cell(date)
131
+ html_options = merge_html_options(
132
+ theme.resolve(:calendar_day),
133
+ { role: "gridcell", tabindex: -1, aria: { disabled: "true" } }
134
+ )
135
+ template.content_tag(:span, **html_options) do
136
+ template.content_tag(:time, date.day.to_s, datetime: date.iso8601)
137
+ end
138
+ end
139
+
140
+ def day_cell_html_options(date)
141
+ is_today = date == today
142
+ is_selected = selected_date && date == selected_date
143
+ merge_html_options(
144
+ theme.resolve(:calendar_day),
145
+ {
146
+ role: "gridcell",
147
+ tabindex: date == focus_day ? 0 : -1,
148
+ aria: {
149
+ current: is_today ? "date" : nil,
150
+ selected: if selectable then is_selected ? "true" : "false" end
151
+ }
152
+ }
153
+ )
154
+ end
155
+
156
+ def other_month_day_cell(date)
157
+ if selectable && in_range?(date)
158
+ navigable_other_month_day_cell(date)
159
+ else
160
+ disabled_other_month_day_cell(date)
161
+ end
162
+ end
163
+
164
+ def navigable_other_month_day_cell(date)
165
+ options = merge_html_options(
166
+ theme.resolve(:calendar_day, outside: true),
167
+ { role: "gridcell", tabindex: -1, aria: { selected: "false" } }
168
+ )
169
+ template.content_tag(:button, **options, type: "button") do
170
+ template.content_tag(:time, date.day.to_s, datetime: date.iso8601)
171
+ end
172
+ end
173
+
174
+ def disabled_other_month_day_cell(date)
175
+ options = merge_html_options(
176
+ theme.resolve(:calendar_day, outside: true),
177
+ { role: "gridcell", tabindex: -1, aria: { disabled: "true", selected: "false" } }
178
+ )
179
+ template.content_tag(:span, **options) do
180
+ template.content_tag(:time, date.day.to_s, datetime: date.iso8601)
181
+ end
182
+ end
183
+
184
+ def in_range?(date)
185
+ (since.nil? || date >= since) && (till.nil? || date <= till)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Calendar
6
+ class Turbo
7
+ class DaysOfWeek < Plumber::Base
8
+ WEEKDAY_FORMATS = %i[narrow short long].freeze
9
+
10
+ attr_reader :format
11
+
12
+ def initialize(template, format: :short)
13
+ super(template)
14
+ @format = format
15
+ end
16
+
17
+ def render(...)
18
+ render_days_of_week(...)
19
+ end
20
+
21
+ private
22
+
23
+ def render_days_of_week(**kwargs)
24
+ html_options = merge_html_options(
25
+ theme.resolve(:calendar_days_of_week),
26
+ kwargs
27
+ )
28
+ template.content_tag(:div, **html_options) { days_of_week }
29
+ end
30
+
31
+ def days_of_week
32
+ week_options = merge_html_options(
33
+ theme.resolve(:calendar_row),
34
+ { role: "row" }
35
+ )
36
+ template.content_tag(:div, **week_options) do
37
+ template.safe_join(
38
+ day_names.map do |abbr, full|
39
+ options = { role: "columnheader" }
40
+ options[:aria] = { label: abbr } if format == :narrow
41
+ template.content_tag(:span, display_name(abbr, full), **options)
42
+ end
43
+ )
44
+ end
45
+ end
46
+
47
+ def display_name(abbr, full)
48
+ case format
49
+ when :narrow then abbr[0, 1]
50
+ when :long then full
51
+ else abbr
52
+ end
53
+ end
54
+
55
+ def day_names
56
+ I18n.t("date.abbr_day_names").zip(I18n.t("date.day_names"))
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Calendar
6
+ class Turbo
7
+ class MonthsOfYear < Plumber::Base
8
+ YEAR_SIZE = 12
9
+ MONTHS_PER_ROW = 4
10
+ MONTH_FORMATS = %i[narrow short long].freeze
11
+
12
+ attr_reader :date, :today, :selected_date, :format, :since, :till, :disabled_months
13
+
14
+ def initialize(
15
+ template,
16
+ date: Date.today,
17
+ today: Date.today,
18
+ selected_date: nil,
19
+ format: :short,
20
+ since: nil,
21
+ till: nil,
22
+ disabled_months: []
23
+ )
24
+ super(template)
25
+ @date = date
26
+ @today = today
27
+ @selected_date = selected_date
28
+ @format = format
29
+ @since = since
30
+ @till = till
31
+ @disabled_months = disabled_months
32
+ end
33
+
34
+ def render(...)
35
+ render_months_of_year(...)
36
+ end
37
+
38
+ private
39
+
40
+ def render_months_of_year(**html_options)
41
+ html_options = merge_html_options(
42
+ theme.resolve(:calendar_months_of_year),
43
+ html_options
44
+ )
45
+ template.content_tag(:div, role: "rowgroup", **html_options) { months_of_year }
46
+ end
47
+
48
+ def months_of_year
49
+ row_options = merge_html_options(
50
+ theme.resolve(:calendar_row),
51
+ { role: "row" }
52
+ )
53
+ template.safe_join(
54
+ month_names.each_slice(MONTHS_PER_ROW).map do |months|
55
+ template.content_tag(:div, **row_options) { months_in_row(months) }
56
+ end
57
+ )
58
+ end
59
+
60
+ def month_names
61
+ I18n.t("date.abbr_month_names").compact.first(YEAR_SIZE)
62
+ .zip(I18n.t("date.month_names").compact.first(YEAR_SIZE))
63
+ .each_with_index.map do |(abbr, full), i|
64
+ month_num = i + 1
65
+ [month_num, abbr, full, month_disabled?(month_num, abbr, full)]
66
+ end
67
+ end
68
+
69
+ def months_in_row(months)
70
+ template.safe_join(months.map { |number, abbr, full, disabled| month_cell(number, abbr, full, disabled) })
71
+ end
72
+
73
+ def month_cell(month_number, abbr, full, disabled)
74
+ options = month_cell_html_options(month_number, disabled)
75
+ options[:aria][:label] = abbr if format == :narrow
76
+ template.content_tag(:button, display_name(abbr, full), **options)
77
+ end
78
+
79
+ def display_name(abbr, full)
80
+ case format
81
+ when :narrow then abbr[0, 1]
82
+ when :long then full
83
+ else abbr
84
+ end
85
+ end
86
+
87
+ def month_cell_html_options(month_number, disabled)
88
+ merge_html_options(
89
+ theme.resolve(:calendar_month),
90
+ {
91
+ role: "gridcell",
92
+ tabindex: focused_month?(month_number, disabled) ? 0 : -1,
93
+ data: { month: month_number },
94
+ aria: {
95
+ current: current_month?(month_number) ? "month" : nil,
96
+ selected: selected_date_in_month?(month_number) ? "true" : "false",
97
+ disabled: disabled ? "true" : nil
98
+ }
99
+ }
100
+ )
101
+ end
102
+
103
+ def month_disabled?(month_num, abbr, full)
104
+ month_start = Date.new(date.year, month_num, 1)
105
+ month_end = Date.new(date.year, month_num, -1)
106
+ (since && month_end < since) ||
107
+ (till && month_start > till) ||
108
+ month_in_disabled_list?(month_num, abbr, full)
109
+ end
110
+
111
+ def month_in_disabled_list?(month_num, abbr, full)
112
+ disabled_months.any? { |v| v.to_s == month_num.to_s } ||
113
+ disabled_months.include?(abbr) ||
114
+ disabled_months.include?(full)
115
+ end
116
+
117
+ def current_month?(month_number)
118
+ month_number == today.month && date.year == today.year
119
+ end
120
+
121
+ def focused_month?(month_number, disabled)
122
+ !disabled && (selected_date_in_month?(month_number) ||
123
+ (current_month?(month_number) && !selected_date_in_current_year?))
124
+ end
125
+
126
+ def selected_date_in_current_year?
127
+ selected_date && selected_date.year == date.year
128
+ end
129
+
130
+ def selected_date_in_month?(month)
131
+ selected_date && month == selected_date.month && date.year == selected_date.year
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Calendar
6
+ class Turbo
7
+ class YearsOfDecade < Plumber::Base
8
+ YEARS_PER_ROW = 4
9
+ DECADE_SIZE = 10
10
+
11
+ attr_reader :date, :today, :selected_date, :since, :till, :disabled_years
12
+
13
+ def initialize(
14
+ template,
15
+ date: Date.today,
16
+ today: Date.today,
17
+ selected_date: nil,
18
+ since: nil,
19
+ till: nil,
20
+ disabled_years: []
21
+ )
22
+ super(template)
23
+ @date = date
24
+ @today = today
25
+ @selected_date = selected_date
26
+ @since = since
27
+ @till = till
28
+ @disabled_years = disabled_years
29
+ end
30
+
31
+ def render(...)
32
+ render_years_of_decade(...)
33
+ end
34
+
35
+ private
36
+
37
+ def render_years_of_decade(**html_options)
38
+ html_options = merge_html_options(
39
+ theme.resolve(:calendar_years_of_decade),
40
+ html_options
41
+ )
42
+ template.content_tag(:div, role: "rowgroup", **html_options) { years_of_decade }
43
+ end
44
+
45
+ def years_of_decade
46
+ row_options = merge_html_options(
47
+ theme.resolve(:calendar_row),
48
+ { role: "row" }
49
+ )
50
+ template.safe_join(
51
+ year_names.each_slice(YEARS_PER_ROW).map do |years|
52
+ template.content_tag(:div, **row_options) { years_in_row(years) }
53
+ end
54
+ )
55
+ end
56
+
57
+ def year_names
58
+ decade_start = (date.year / DECADE_SIZE) * DECADE_SIZE
59
+ ((decade_start - 1)..(decade_start + DECADE_SIZE)).map do |y|
60
+ [y, year_disabled?(y, decade_start)]
61
+ end
62
+ end
63
+
64
+ def years_in_row(years)
65
+ template.safe_join(years.map { |year, disabled| year_cell(year, disabled) })
66
+ end
67
+
68
+ def year_cell(year, disabled)
69
+ template.content_tag(:button, year.to_s, **year_cell_html_options(year, disabled))
70
+ end
71
+
72
+ def year_cell_html_options(year, disabled)
73
+ merge_html_options(
74
+ theme.resolve(:calendar_year),
75
+ {
76
+ role: "gridcell",
77
+ tabindex: focused_year?(year, disabled) ? 0 : -1,
78
+ data: { year: year },
79
+ aria: {
80
+ current: current_year?(year) ? "year" : nil,
81
+ selected: selected_date_in_year?(year) ? "true" : "false",
82
+ disabled: disabled ? "true" : nil
83
+ }
84
+ }
85
+ )
86
+ end
87
+
88
+ def year_disabled?(year, decade_start)
89
+ outside_decade?(year, decade_start) ||
90
+ (since && year < since.year) ||
91
+ (till && year > till.year) ||
92
+ disabled_years.any? { |v| v.to_s == year.to_s }
93
+ end
94
+
95
+ def outside_decade?(year, decade_start)
96
+ year < decade_start || year > (decade_start + DECADE_SIZE - 1)
97
+ end
98
+
99
+ def current_year?(year)
100
+ year == today.year
101
+ end
102
+
103
+ def focused_year?(year, disabled)
104
+ !disabled && (selected_date_in_year?(year) || (current_year?(year) && !selected_date))
105
+ end
106
+
107
+ def selected_date_in_year?(year)
108
+ selected_date && year == selected_date.year
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class Calendar
6
+ class Turbo < Plumber::Base
7
+ MONTH_SELECTOR_CONTROLLER = "calendar-month-selector"
8
+ YEAR_SELECTOR_CONTROLLER = "calendar-year-selector"
9
+ DECADE_SELECTOR_CONTROLLER = "calendar-decade-selector"
10
+
11
+ def month(
12
+ date: Date.today,
13
+ today: Date.today,
14
+ show_other_months: false,
15
+ weekday_format: :short,
16
+ selectable: false,
17
+ selected_date: nil,
18
+ since: nil,
19
+ till: nil,
20
+ **kwargs
21
+ )
22
+ html_options = merge_html_options(
23
+ theme.resolve(:calendar),
24
+ kwargs,
25
+ { data: { controller: MONTH_SELECTOR_CONTROLLER } }
26
+ )
27
+ template.content_tag(:div, role: "grid", **html_options) do
28
+ template.safe_join(
29
+ [
30
+ Turbo::DaysOfWeek.new(template, format: weekday_format).render,
31
+ Turbo::DaysOfMonth.new(
32
+ template,
33
+ date: date,
34
+ today: today,
35
+ selectable: selectable,
36
+ selected_date: selected_date,
37
+ show_other_months: show_other_months,
38
+ since: since,
39
+ till: till
40
+ ).render
41
+ ]
42
+ )
43
+ end
44
+ end
45
+
46
+ def year(
47
+ date: Date.today,
48
+ today: Date.today,
49
+ selected_date: nil,
50
+ month_format: :short,
51
+ since: nil,
52
+ till: nil,
53
+ disabled_months: [],
54
+ **kwargs
55
+ )
56
+ html_options = merge_html_options(
57
+ theme.resolve(:calendar_quarter_grid),
58
+ kwargs,
59
+ {
60
+ role: "grid",
61
+ aria: { label: I18n.t("stimulus_plumbers.calendar.year_view") },
62
+ data: { controller: YEAR_SELECTOR_CONTROLLER }
63
+ }
64
+ )
65
+ template.content_tag(:div, **html_options) do
66
+ Turbo::MonthsOfYear.new(
67
+ template,
68
+ date: date,
69
+ today: today,
70
+ selected_date: selected_date,
71
+ format: month_format,
72
+ since: since,
73
+ till: till,
74
+ disabled_months: disabled_months
75
+ ).render
76
+ end
77
+ end
78
+
79
+ def decade(
80
+ date: Date.today,
81
+ today: Date.today,
82
+ selected_date: nil,
83
+ since: nil,
84
+ till: nil,
85
+ disabled_years: [],
86
+ **kwargs
87
+ )
88
+ html_options = merge_html_options(
89
+ theme.resolve(:calendar_quarter_grid),
90
+ kwargs,
91
+ {
92
+ role: "grid",
93
+ aria: { label: I18n.t("stimulus_plumbers.calendar.decade_view") },
94
+ data: { controller: DECADE_SELECTOR_CONTROLLER }
95
+ }
96
+ )
97
+ template.content_tag(:div, **html_options) do
98
+ Turbo::YearsOfDecade.new(
99
+ template,
100
+ date: date,
101
+ today: today,
102
+ selected_date: selected_date,
103
+ since: since,
104
+ till: till,
105
+ disabled_years: disabled_years
106
+ ).render
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end