stimulus_plumbers 0.2.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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +75 -0
  3. data/app/assets/javascripts/stimulus-plumbers/.keep +0 -0
  4. data/app/assets/stylesheets/stimulus_plumbers/tokens.css +83 -0
  5. data/lib/stimulus_plumbers/components/action_list/renderer.rb +47 -0
  6. data/lib/stimulus_plumbers/components/avatar/renderer.rb +74 -0
  7. data/lib/stimulus_plumbers/components/button/renderer.rb +33 -0
  8. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +124 -0
  9. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +36 -0
  10. data/lib/stimulus_plumbers/components/calendar/month/turbo/renderer.rb +57 -0
  11. data/lib/stimulus_plumbers/components/calendar/renderer.rb +35 -0
  12. data/lib/stimulus_plumbers/components/card/renderer.rb +41 -0
  13. data/lib/stimulus_plumbers/components/date_picker/navigation.rb +49 -0
  14. data/lib/stimulus_plumbers/components/date_picker/navigator.rb +31 -0
  15. data/lib/stimulus_plumbers/components/date_picker/renderer.rb +82 -0
  16. data/lib/stimulus_plumbers/components/icon/renderer.rb +51 -0
  17. data/lib/stimulus_plumbers/components/plumber/base.rb +22 -0
  18. data/lib/stimulus_plumbers/components/plumber/dispatcher.rb +113 -0
  19. data/lib/stimulus_plumbers/components/plumber/html_options.rb +34 -0
  20. data/lib/stimulus_plumbers/components/plumber/renderer.rb +91 -0
  21. data/lib/stimulus_plumbers/components/popover/renderer.rb +46 -0
  22. data/lib/stimulus_plumbers/configuration.rb +39 -0
  23. data/lib/stimulus_plumbers/engine.rb +21 -0
  24. data/lib/stimulus_plumbers/form/builder.rb +67 -0
  25. data/lib/stimulus_plumbers/form/field_component.rb +80 -0
  26. data/lib/stimulus_plumbers/form/fields/choice.rb +25 -0
  27. data/lib/stimulus_plumbers/form/fields/file.rb +16 -0
  28. data/lib/stimulus_plumbers/form/fields/renderer.rb +57 -0
  29. data/lib/stimulus_plumbers/form/fields/select.rb +27 -0
  30. data/lib/stimulus_plumbers/form/fields/text.rb +25 -0
  31. data/lib/stimulus_plumbers/form/fields/text_area.rb +16 -0
  32. data/lib/stimulus_plumbers/helpers/action_list_helper.rb +25 -0
  33. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +17 -0
  34. data/lib/stimulus_plumbers/helpers/button_helper.rb +25 -0
  35. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +26 -0
  36. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +31 -0
  37. data/lib/stimulus_plumbers/helpers/card_helper.rb +21 -0
  38. data/lib/stimulus_plumbers/helpers/date_picker_helper.rb +17 -0
  39. data/lib/stimulus_plumbers/helpers/plumber_helper.rb +15 -0
  40. data/lib/stimulus_plumbers/helpers/popover_helper.rb +17 -0
  41. data/lib/stimulus_plumbers/helpers.rb +25 -0
  42. data/lib/stimulus_plumbers/logger.rb +20 -0
  43. data/lib/stimulus_plumbers/themes/action_list.rb +14 -0
  44. data/lib/stimulus_plumbers/themes/avatar.rb +14 -0
  45. data/lib/stimulus_plumbers/themes/base.rb +73 -0
  46. data/lib/stimulus_plumbers/themes/button.rb +18 -0
  47. data/lib/stimulus_plumbers/themes/calendar.rb +15 -0
  48. data/lib/stimulus_plumbers/themes/card.rb +12 -0
  49. data/lib/stimulus_plumbers/themes/form.rb +30 -0
  50. data/lib/stimulus_plumbers/themes/layout.rb +12 -0
  51. data/lib/stimulus_plumbers/themes/schema/ranges.rb +15 -0
  52. data/lib/stimulus_plumbers/themes/tailwind/action_list.rb +33 -0
  53. data/lib/stimulus_plumbers/themes/tailwind/avatar.rb +52 -0
  54. data/lib/stimulus_plumbers/themes/tailwind/button.rb +89 -0
  55. data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +34 -0
  56. data/lib/stimulus_plumbers/themes/tailwind/card.rb +24 -0
  57. data/lib/stimulus_plumbers/themes/tailwind/form.rb +104 -0
  58. data/lib/stimulus_plumbers/themes/tailwind/layout.rb +25 -0
  59. data/lib/stimulus_plumbers/themes/tailwind_theme.rb +29 -0
  60. data/lib/stimulus_plumbers/version.rb +5 -0
  61. data/lib/stimulus_plumbers.rb +48 -0
  62. metadata +129 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1abfd2ca5a0c575652d0d6c650acbde79e656b9b806a76b8f922e682cf2f7526
4
+ data.tar.gz: 621aa551f6fb7b9f3a90c3313e4dedb6c901f475cb96f6631a9fc54edccf5bac
5
+ SHA512:
6
+ metadata.gz: e45e2a0ea994b0e8d22edc2098974d6f15c43bb552a92dc7c5baf2dc0700b9a8f22ef570ffa913c0cae84d6bc64ecbe6b6e09613e4b73ccaccc1613d93d898d3
7
+ data.tar.gz: 1be1501a2b77cad35780730e84803bdb5506e6def24699c46cc1797196780aec94dfe7a451a0b4774b748258132a7b0eb3d6bae16c71c22bc7c733bf24c45f1c
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Stimulus Plumbers
2
+
3
+ A library of semantically correct, accessible UI components for Rails 8.0+ using ViewComponent and Stimulus.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'stimulus_plumbers'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Stimulus Plumbers provides ready-to-use ViewComponent components that render semantically correct, accessible HTML. Each component is designed with accessibility as a core requirement, not an afterthought.
22
+
23
+ ### Basic Example
24
+
25
+ ```erb
26
+ <%# In your Rails view %>
27
+ <%= render ButtonComponent.new(url: root_path) do %>
28
+ Click me
29
+ <% end %>
30
+ ```
31
+
32
+ ### Available Components
33
+
34
+ - **ActionList**: Accessible lists with items and sections
35
+ - **Avatar**: User avatar component
36
+ - **Button**: Semantic button with optional prefix/suffix
37
+ - **Card**: Card component with sections
38
+ - **Calendar**: Date calendar with navigation
39
+ - **Container**: Layout container
40
+ - **Divider**: Semantic divider/separator
41
+ - **Dropdown**: Accessible dropdown menus
42
+ - **Navigation**: Navigation bars, tabs, and lists
43
+ - **Popover**: Accessible popover component
44
+
45
+ ## Component Philosophy
46
+
47
+ All components in this library follow these principles:
48
+
49
+ 1. **Accessibility First**: WCAG 2.1 Level AA minimum
50
+ 2. **Semantic HTML**: Use native elements before ARIA
51
+ 3. **Keyboard Navigation**: Full keyboard support
52
+ 4. **Screen Reader Friendly**: Proper announcements and labels
53
+ 5. **Focus Management**: Visible focus indicators and logical tab order
54
+
55
+ ## Development
56
+
57
+ After checking out the repo, run:
58
+
59
+ ```bash
60
+ bundle install
61
+ ```
62
+
63
+ Run the test suite with:
64
+
65
+ ```bash
66
+ bundle exec rspec
67
+ ```
68
+
69
+ ## Contributing
70
+
71
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ryancyq/stimulus-plumbers.
72
+
73
+ ## License
74
+
75
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
File without changes
@@ -0,0 +1,83 @@
1
+ /*
2
+ * Stimulus Plumbers – Design Tokens
3
+ *
4
+ * CSS custom properties for all presentational values.
5
+ * Structural layout utilities stay in component templates.
6
+ * Override any token in your own :root block to retheme the library.
7
+ */
8
+
9
+ :root {
10
+ /* ── Semantic colors ─────────────────────────────────────────────────── */
11
+ --sp-color-primary: oklch(55% 0.2 250);
12
+ --sp-color-primary-fg: oklch(98% 0.01 250);
13
+
14
+ --sp-color-secondary: oklch(55% 0.05 250);
15
+ --sp-color-secondary-fg: oklch(98% 0.01 250);
16
+
17
+ --sp-color-destructive: oklch(55% 0.22 25);
18
+ --sp-color-destructive-fg: oklch(98% 0.01 25);
19
+
20
+ --sp-color-muted: oklch(96% 0.02 250);
21
+ --sp-color-muted-fg: oklch(45% 0.05 250);
22
+
23
+ --sp-color-border: oklch(90% 0.02 250);
24
+ --sp-color-bg: oklch(100% 0 0);
25
+ --sp-color-fg: oklch(10% 0.02 250);
26
+
27
+
28
+ /* ── Focus ring ──────────────────────────────────────────────────────── */
29
+ --sp-focus-ring-color: var(--sp-color-primary);
30
+ --sp-focus-ring-width: 2px;
31
+ --sp-focus-ring-offset: 2px;
32
+
33
+
34
+ /* ── Avatar palette (10 slots, index chosen by name hash) ────────────── */
35
+ --sp-avatar-color-1: #b45309;
36
+ --sp-avatar-color-2: #4d7c0f;
37
+ --sp-avatar-color-3: #0369a1;
38
+ --sp-avatar-color-4: #0e7490;
39
+ --sp-avatar-color-5: #0f766e;
40
+ --sp-avatar-color-6: #047857;
41
+ --sp-avatar-color-7: #4338ca;
42
+ --sp-avatar-color-8: #a21caf;
43
+ --sp-avatar-color-9: #be123c;
44
+ --sp-avatar-color-10: #be185d;
45
+ --sp-avatar-fg: #ffffff;
46
+
47
+
48
+ /* ── Spacing ─────────────────────────────────────────────────────────── */
49
+ --sp-space-1: 0.25rem; /* 4px */
50
+ --sp-space-2: 0.5rem; /* 8px */
51
+ --sp-space-3: 0.75rem; /* 12px */
52
+ --sp-space-4: 1rem; /* 16px */
53
+ --sp-space-6: 1.5rem; /* 24px */
54
+
55
+
56
+ /* ── Border radius ───────────────────────────────────────────────────── */
57
+ --sp-radius-sm: 0.25rem;
58
+ --sp-radius-md: 0.375rem;
59
+ --sp-radius-lg: 0.5rem;
60
+ --sp-radius-full: 9999px;
61
+
62
+
63
+ /* ── Shadow ──────────────────────────────────────────────────────────── */
64
+ --sp-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05);
65
+ --sp-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
66
+ 0 2px 4px -2px rgb(0 0 0 / 0.1);
67
+
68
+
69
+ /* ── Typography ──────────────────────────────────────────────────────── */
70
+ --sp-text-sm: 0.875rem;
71
+ --sp-text-base: 1rem;
72
+ --sp-text-lg: 1.125rem;
73
+
74
+
75
+ /* ── Sizing ──────────────────────────────────────────────────────────── */
76
+ --sp-avatar-size: 2rem; /* 32px */
77
+ --sp-icon-size: 1.5rem; /* 24px */
78
+ --sp-calendar-day-size: 2rem; /* 32px */
79
+
80
+
81
+ /* ── Z-index ─────────────────────────────────────────────────────────── */
82
+ --sp-z-popover: 50;
83
+ }
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module ActionList
6
+ class Renderer < Plumber::Base
7
+ def list(**kwargs, &block)
8
+ html_options = merge_html_options(
9
+ { classes: theme.resolve(:action_list).fetch(:classes, "") },
10
+ kwargs
11
+ )
12
+ template.content_tag(:div, template.capture(&block), **html_options)
13
+ end
14
+
15
+ def section(title: nil, **kwargs, &block)
16
+ html_options = merge_html_options(kwargs)
17
+ template.content_tag(:div, **html_options) do
18
+ template.safe_join(
19
+ [
20
+ (template.content_tag(:p, title) if title.present?),
21
+ template.content_tag(:ul, template.capture(&block))
22
+ ].compact
23
+ )
24
+ end
25
+ end
26
+
27
+ def item(content = nil, url: nil, external: false, active: false, **kwargs, &block)
28
+ content = template.capture(&block) if block_given?
29
+ html_options = merge_html_options(
30
+ { classes: theme.resolve(:action_list_item, active: active).fetch(:classes, "") },
31
+ kwargs
32
+ )
33
+
34
+ inner = if url
35
+ html_options[:target] = "_blank" if external
36
+ template.content_tag(:a, content, href: url, **html_options)
37
+ else
38
+ html_options[:type] ||= "button"
39
+ template.content_tag(:button, content, **html_options)
40
+ end
41
+
42
+ template.content_tag(:li, inner)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Avatar
6
+ class Renderer < Plumber::Base
7
+ def avatar(name: nil, initials: nil, url: nil, color: nil, size: :md, **kwargs, &block)
8
+ color_css = resolve_color(color, name, initials) unless url || block_given?
9
+
10
+ html_options = merge_html_options(
11
+ {
12
+ classes: [theme.resolve(:avatar, size: size).fetch(:classes, ""), color_css],
13
+ "aria-label": name,
14
+ role: "img"
15
+ },
16
+ kwargs
17
+ )
18
+
19
+ template.content_tag(:span, inner(name, initials, url, &block), **html_options)
20
+ end
21
+
22
+ private
23
+
24
+ def inner(name, initials, url, &block)
25
+ if block_given?
26
+ template.capture(&block)
27
+ elsif url
28
+ template.tag.img(src: url, alt: name.present? ? "#{name}'s avatar" : "", onerror: "this.src=''")
29
+ elsif initials
30
+ initials_svg(initials)
31
+ else
32
+ fallback_svg
33
+ end
34
+ end
35
+
36
+ def resolve_color(color, name, initials)
37
+ if color
38
+ theme.avatar_colors.fetch(color, nil)
39
+ elsif (seed = name || initials)
40
+ theme.avatar_color_range[seed.bytes.reduce(:^) % theme.avatar_color_range.length]
41
+ else
42
+ theme.avatar_color_range.first
43
+ end
44
+ end
45
+
46
+ def initials_svg(initials)
47
+ template.content_tag(:svg, viewBox: "0 0 40 40") do
48
+ template.content_tag(
49
+ :text,
50
+ initials.upcase,
51
+ x: "50%",
52
+ y: "50%",
53
+ dy: "0.35em",
54
+ fill: "currentColor",
55
+ "font-size": "20",
56
+ "text-anchor": "middle"
57
+ )
58
+ end
59
+ end
60
+
61
+ def fallback_svg
62
+ template.content_tag(:svg, viewBox: "0 0 40 40") do
63
+ template.tag.path(
64
+ fill: "currentColor",
65
+ d: "M8.28 27.5A14.95 14.95 0 0120 21.8c4.76 0 8.97 2.24 11.72 5.7a14.02 " \
66
+ "14.02 0 01-8.25 5.91 14.82 14.82 0 01-6.94 0 14.02 14.02 0 01-8.25-5.9z" \
67
+ "M13.99 12.78a6.02 6.02 0 1112.03 0 6.02 6.02 0 01-12.03 0z"
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Button
6
+ class Renderer < Plumber::Base
7
+ def button(content = nil, url: nil, external: false, variant: :primary, size: :md, **kwargs, &block)
8
+ content = template.capture(&block) if block_given?
9
+ html_options = merge_html_options(
10
+ { classes: theme.resolve(:button, variant: variant, size: size).fetch(:classes, "") },
11
+ kwargs
12
+ )
13
+
14
+ if url
15
+ html_options[:target] = "_blank" if external
16
+ template.content_tag(:a, content, href: url, **html_options)
17
+ else
18
+ html_options[:type] ||= "button"
19
+ template.content_tag(:button, content, **html_options)
20
+ end
21
+ end
22
+
23
+ def group(alignment: :left, direction: :row, **kwargs, &block)
24
+ html_options = merge_html_options(
25
+ { classes: theme.resolve(:button_group, alignment: alignment, direction: direction).fetch(:classes, "") },
26
+ kwargs
27
+ )
28
+ template.content_tag(:div, template.capture(&block), **html_options)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Calendar
6
+ module Month
7
+ module Turbo
8
+ class DaysOfMonth < Plumber::Base
9
+ DAYS_IN_WEEK = 7
10
+
11
+ attr_reader :date, :today, :selectable, :selected_date, :show_other_months
12
+
13
+ def initialize(
14
+ template,
15
+ date: Date.today,
16
+ today: Date.today,
17
+ selectable: false,
18
+ selected_date: nil,
19
+ show_other_months: false
20
+ )
21
+ super(template)
22
+ @date = date
23
+ @today = today
24
+ @selectable = selectable
25
+ @selected_date = selected_date
26
+ @show_other_months = show_other_months
27
+ end
28
+
29
+ def render(**kwargs)
30
+ html_options = merge_html_options(
31
+ { classes: theme.resolve(:calendar_days_of_month).fetch(:classes, "") },
32
+ kwargs
33
+ )
34
+
35
+ template.content_tag(:div, **html_options, role: "rowgroup") do
36
+ template.safe_join(
37
+ build_days.each_slice(DAYS_IN_WEEK).map do |week|
38
+ template.content_tag(:div, role: "row") do
39
+ days_in_week(week)
40
+ end
41
+ end
42
+ )
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
49
+ def focus_day
50
+ @focus_day ||= if selected_date&.month == date.month && selected_date&.year == date.year
51
+ selected_date
52
+ elsif today.month == date.month && today.year == date.year
53
+ today
54
+ else
55
+ date.beginning_of_month
56
+ end
57
+ end
58
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
59
+
60
+ def build_days
61
+ first = date.beginning_of_month
62
+ last = date.end_of_month
63
+
64
+ prev_days = (first - first.wday).upto(first - 1).to_a
65
+ current_days = first.upto(last).to_a
66
+ total = prev_days.length + current_days.length
67
+ next_count = (DAYS_IN_WEEK - (total % DAYS_IN_WEEK)) % DAYS_IN_WEEK
68
+ next_days = next_count.positive? ? (last + 1).upto(last + next_count).to_a : []
69
+
70
+ prev_days + current_days + next_days
71
+ end
72
+
73
+ def days_in_week(week)
74
+ template.safe_join(
75
+ week.map do |day|
76
+ if day.month == date.month
77
+ current_month_day_cell(day)
78
+ elsif show_other_months
79
+ other_month_day_cell(day)
80
+ else
81
+ hidden_day_cell(day)
82
+ end
83
+ end
84
+ )
85
+ end
86
+
87
+ def hidden_day_cell(date)
88
+ template.content_tag(:span, role: "gridcell", tabindex: -1, aria: { hidden: "true" }) do
89
+ template.content_tag(:time, nil, datetime: date.iso8601)
90
+ end
91
+ end
92
+
93
+ def current_month_day_cell(date)
94
+ tag = selectable ? :button : :span
95
+ selected = selected_date && date == selected_date ? "true" : "false"
96
+ template.content_tag(
97
+ tag,
98
+ role: "gridcell",
99
+ tabindex: date == focus_day ? 0 : -1,
100
+ aria: {
101
+ current: date == today ? "date" : nil,
102
+ selected: selectable ? selected : nil
103
+ }
104
+ ) do
105
+ template.content_tag(:time, date.day.to_s, datetime: date.iso8601)
106
+ end
107
+ end
108
+
109
+ def other_month_day_cell(date)
110
+ template.content_tag(
111
+ :span,
112
+ role: "gridcell",
113
+ tabindex: -1,
114
+ aria: { disabled: "true", selected: "false" }
115
+ ) do
116
+ template.content_tag(:time, date.day.to_s, datetime: date.iso8601)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Calendar
6
+ module Month
7
+ module Turbo
8
+ class DaysOfWeek < Plumber::Base
9
+ def render(**kwargs)
10
+ html_options = merge_html_options(
11
+ { classes: theme.resolve(:calendar_days_of_week).fetch(:classes, "") },
12
+ kwargs
13
+ )
14
+
15
+ template.content_tag(:div, **html_options) do
16
+ template.content_tag(:div, role: "row") do
17
+ template.safe_join(
18
+ day_names.map do |abbr, full|
19
+ template.content_tag(:span, abbr, role: "columnheader", abbr: full)
20
+ end
21
+ )
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def day_names
29
+ I18n.t("date.abbr_day_names").zip(I18n.t("date.day_names"))
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Calendar
6
+ module Month
7
+ module Turbo
8
+ class Renderer < Plumber::Base
9
+ STIMULUS_CONTROLLER = "calendar-month-observer"
10
+
11
+ def render(
12
+ date: Date.today,
13
+ today: Date.today,
14
+ selectable: false,
15
+ selected_date: nil,
16
+ show_other_months: false,
17
+ **kwargs
18
+ )
19
+ html_options = merge_html_options(
20
+ {
21
+ classes: theme.resolve(:calendar).fetch(:classes, ""),
22
+ data: { controller: STIMULUS_CONTROLLER, action: "click->#{STIMULUS_CONTROLLER}#select" }
23
+ },
24
+ kwargs
25
+ )
26
+
27
+ template.content_tag(:div, role: "grid", **html_options) do
28
+ template.safe_join(
29
+ [
30
+ days_of_week,
31
+ days_of_month(
32
+ date: date,
33
+ today: today,
34
+ selectable: selectable,
35
+ selected_date: selected_date,
36
+ show_other_months: show_other_months
37
+ )
38
+ ]
39
+ )
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def days_of_week(**kwargs)
46
+ DaysOfWeek.new(template).render(**kwargs)
47
+ end
48
+
49
+ def days_of_month(**kwargs)
50
+ DaysOfMonth.new(template, **kwargs).render
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Calendar
6
+ class Renderer < Plumber::Base
7
+ STIMULUS_CONTROLLER = "calendar-month"
8
+ OBSERVER_STIMULUS_CONTROLLER = "calendar-month-observer"
9
+ STIMULUS_DATA = {
10
+ controller: "#{STIMULUS_CONTROLLER} #{OBSERVER_STIMULUS_CONTROLLER}",
11
+ action: "click->#{OBSERVER_STIMULUS_CONTROLLER}#select"
12
+ }.freeze
13
+
14
+ def month(**kwargs)
15
+ html_options = merge_html_options(
16
+ { classes: theme.resolve(:calendar).fetch(:classes, ""), data: STIMULUS_DATA },
17
+ kwargs
18
+ )
19
+
20
+ template.content_tag(:div, role: "grid", **html_options) do
21
+ template.safe_join(
22
+ [
23
+ template.tag.div(data: { "#{STIMULUS_CONTROLLER}-target" => "daysOfWeek" }),
24
+ template.tag.div(
25
+ role: "rowgroup",
26
+ data: { "#{STIMULUS_CONTROLLER}-target" => "daysOfMonth" }
27
+ )
28
+ ]
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Card
6
+ class Renderer < Plumber::Base
7
+ def card(title: nil, **kwargs, &block)
8
+ html_options = merge_html_options(
9
+ { classes: theme.resolve(:card).fetch(:classes, "") },
10
+ kwargs
11
+ )
12
+
13
+ template.content_tag(:div, **html_options) do
14
+ template.safe_join(
15
+ [
16
+ (template.content_tag(:h2, title) if title.present?),
17
+ template.capture(&block)
18
+ ].compact
19
+ )
20
+ end
21
+ end
22
+
23
+ def section(title: nil, **kwargs, &block)
24
+ html_options = merge_html_options(
25
+ { classes: theme.resolve(:card_section).fetch(:classes, "") },
26
+ kwargs
27
+ )
28
+
29
+ template.content_tag(:div, **html_options) do
30
+ template.safe_join(
31
+ [
32
+ (template.content_tag(:h3, title) if title.present?),
33
+ template.capture(&block)
34
+ ].compact
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module DatePicker
6
+ class Navigation < Plumber::Base
7
+ def render(stimulus_controller:, step:, **kwargs)
8
+ html_options = merge_html_options(
9
+ { classes: theme.resolve(:calendar_navigation).fetch(:classes, ""), aria: { label: "DatePicker Navigation" } },
10
+ kwargs
11
+ )
12
+
13
+ template.content_tag(:nav, **html_options) do
14
+ template.safe_join(navigators(stimulus_controller, step))
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def navigators(stimulus_controller, step)
21
+ [
22
+ Navigator.new(template).render(
23
+ icon_options: { name: "arrow-left" },
24
+ aria: { label: ["previous", step].join(" ").titleize },
25
+ data: { "#{stimulus_controller}-target" => "previous" }
26
+ ),
27
+ Navigator.new(template).render(
28
+ aria: { label: "Day" },
29
+ data: { "#{stimulus_controller}-target" => "day" }
30
+ ),
31
+ Navigator.new(template).render(
32
+ aria: { label: "Month" },
33
+ data: { "#{stimulus_controller}-target" => "month" }
34
+ ),
35
+ Navigator.new(template).render(
36
+ aria: { label: "Year" },
37
+ data: { "#{stimulus_controller}-target" => "year" }
38
+ ),
39
+ Navigator.new(template).render(
40
+ icon_options: { name: "arrow-right" },
41
+ aria: { label: ["next", step].join(" ").titleize },
42
+ data: { "#{stimulus_controller}-target" => "next" }
43
+ )
44
+ ]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end