stimulus_plumbers 0.4.5 → 0.4.9

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +15 -1
  4. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +331 -20
  5. data/app/assets/javascripts/stimulus-plumbers/index.es.js +493 -226
  6. data/app/assets/javascripts/stimulus-plumbers/index.es.js.map +1 -1
  7. data/app/assets/javascripts/stimulus-plumbers/index.umd.js +1 -1
  8. data/app/assets/javascripts/stimulus-plumbers/index.umd.js.map +1 -1
  9. data/app/assets/stylesheets/stimulus_plumbers/tokens.css +58 -53
  10. data/docs/architecture.md +0 -2
  11. data/docs/component/avatar.md +1 -0
  12. data/docs/component/button.md +2 -1
  13. data/docs/component/calendar.md +18 -20
  14. data/docs/component/card.md +14 -12
  15. data/docs/component/checklist.md +153 -0
  16. data/docs/component/combobox.md +2 -2
  17. data/docs/component/form.md +32 -6
  18. data/docs/component/icon.md +7 -5
  19. data/docs/component/indicator.md +55 -0
  20. data/docs/component/list.md +4 -5
  21. data/docs/component/ordered_list.md +118 -0
  22. data/docs/component/plumber.md +43 -0
  23. data/docs/component/popover.md +1 -2
  24. data/docs/component/progress.md +65 -0
  25. data/docs/component/theme.md +15 -2
  26. data/docs/component/timeline.md +49 -14
  27. data/docs/guide.md +39 -0
  28. data/lib/generators/stimulus_plumbers/install/install_generator.rb +33 -0
  29. data/lib/stimulus_plumbers/components/button.rb +2 -1
  30. data/lib/stimulus_plumbers/components/card.rb +1 -1
  31. data/lib/stimulus_plumbers/components/checklist/item/slots.rb +13 -0
  32. data/lib/stimulus_plumbers/components/checklist/item.rb +64 -0
  33. data/lib/stimulus_plumbers/components/checklist.rb +50 -0
  34. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +1 -0
  35. data/lib/stimulus_plumbers/components/combobox.rb +1 -1
  36. data/lib/stimulus_plumbers/components/icon.rb +2 -2
  37. data/lib/stimulus_plumbers/components/indicator.rb +30 -0
  38. data/lib/stimulus_plumbers/components/link.rb +2 -1
  39. data/lib/stimulus_plumbers/components/list/item.rb +1 -1
  40. data/lib/stimulus_plumbers/components/ordered_list/item/slots.rb +13 -0
  41. data/lib/stimulus_plumbers/components/ordered_list/item.rb +129 -0
  42. data/lib/stimulus_plumbers/components/ordered_list.rb +24 -0
  43. data/lib/stimulus_plumbers/components/progress_bar.rb +48 -0
  44. data/lib/stimulus_plumbers/components/progress_meter.rb +37 -0
  45. data/lib/stimulus_plumbers/components/progress_ring.rb +40 -0
  46. data/lib/stimulus_plumbers/components/timeline/event.rb +3 -2
  47. data/lib/stimulus_plumbers/components/timeline/group.rb +5 -3
  48. data/lib/stimulus_plumbers/engine.rb +4 -0
  49. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +1 -0
  50. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -0
  51. data/lib/stimulus_plumbers/generators/css_entrypoint.rb +80 -0
  52. data/lib/stimulus_plumbers/generators/tokens_directive.rb +24 -0
  53. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +1 -7
  54. data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -7
  55. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -9
  56. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +3 -6
  57. data/lib/stimulus_plumbers/helpers/checklist_helper.rb +11 -0
  58. data/lib/stimulus_plumbers/helpers/indicator_helper.rb +11 -0
  59. data/lib/stimulus_plumbers/helpers/ordered_list_helper.rb +11 -0
  60. data/lib/stimulus_plumbers/helpers/popover_helper.rb +1 -7
  61. data/lib/stimulus_plumbers/helpers/progress_helper.rb +21 -0
  62. data/lib/stimulus_plumbers/helpers.rb +8 -0
  63. data/lib/stimulus_plumbers/plumber/slots.rb +10 -3
  64. data/lib/stimulus_plumbers/themes/base.rb +4 -0
  65. data/lib/stimulus_plumbers/themes/schema/icon.rb +21 -4
  66. data/lib/stimulus_plumbers/themes/schema/indicator/ranges.rb +14 -0
  67. data/lib/stimulus_plumbers/themes/schema.rb +39 -3
  68. data/lib/stimulus_plumbers/version.rb +1 -1
  69. data/lib/stimulus_plumbers.rb +10 -0
  70. data/lib/tasks/stimulus_plumbers.rake +18 -0
  71. data/vendor/ARIA.md +108 -0
  72. data/vendor/component/manifest.json +212 -0
  73. data/vendor/controller/docs/calendar.md +212 -0
  74. data/vendor/controller/docs/checklist.md +45 -0
  75. data/vendor/controller/docs/clipboard.md +61 -0
  76. data/vendor/controller/docs/combobox.md +212 -0
  77. data/vendor/controller/docs/dismisser.md +39 -0
  78. data/vendor/controller/docs/flipper.md +46 -0
  79. data/vendor/controller/docs/input-clearable.md +43 -0
  80. data/vendor/controller/docs/input-formatter.md +89 -0
  81. data/vendor/controller/docs/modal.md +57 -0
  82. data/vendor/controller/docs/panner.md +24 -0
  83. data/vendor/controller/docs/popover.md +111 -0
  84. data/vendor/controller/docs/progress.md +82 -0
  85. data/vendor/controller/docs/reorderable.md +82 -0
  86. data/vendor/controller/docs/timeline.md +84 -0
  87. data/vendor/controller/docs/visibility.md +31 -0
  88. data/vendor/controller/guide.md +16 -0
  89. data/vendor/{controllers.manifest.json → controller/manifest.json} +331 -20
  90. metadata +44 -2
@@ -6,7 +6,7 @@ module StimulusPlumbers
6
6
  def render(content = nil, url:, icon_leading: nil, icon_trailing: nil, **kwargs, &block)
7
7
  icon_trailing ||= "external-link" if kwargs[:target] == "_blank"
8
8
 
9
- slots = Link::Slots.new
9
+ slots = Link::Slots.new(template)
10
10
  slots.with_icon_leading(icon_leading) if icon_leading
11
11
  slots.with_icon_trailing(icon_trailing) if icon_trailing
12
12
 
@@ -45,6 +45,7 @@ module StimulusPlumbers
45
45
 
46
46
  Components::Icon.new(template).render(
47
47
  name: value,
48
+ size: :sm,
48
49
  classes: theme.resolve(:link_icon).fetch(:classes, ""),
49
50
  aria: { hidden: "true" }
50
51
  )
@@ -5,7 +5,7 @@ module StimulusPlumbers
5
5
  class List
6
6
  class Item < Plumber::Base
7
7
  def render(content = nil, **kwargs, &block)
8
- slots = List::Item::Slots.new
8
+ slots = List::Item::Slots.new(template)
9
9
  slots.with_title(content) if content
10
10
  slots.with_icon_trailing("external-link") if kwargs[:url].present? && kwargs[:target] == "_blank"
11
11
  yield slots if block_given?
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class OrderedList
6
+ class Item
7
+ class Slots < Plumber::Slots
8
+ slot :icon_leading, :title, :description, :icon_trailing
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class OrderedList
6
+ class Item < Plumber::Base
7
+ HANDLE_ACTION = "pointerdown->reorderable#onPointerDown pointermove->reorderable#onPointerMove " \
8
+ "pointerup->reorderable#onPointerUp"
9
+
10
+ def render(content = nil, id:, handle: :item, url: nil, target: nil, active: false, **html_options, &block)
11
+ validate_options!(id, url, html_options)
12
+
13
+ slots = build_slots(content, &block)
14
+
15
+ build(id: id, handle: handle) do |attrs|
16
+ template.content_tag(:li, **attrs) do
17
+ template.safe_join(
18
+ [
19
+ render_icon_position(slots, :leading),
20
+ render_body(slots, url: url, target: target, active: active, **html_options),
21
+ render_icon_position(slots, :trailing)
22
+ ]
23
+ )
24
+ end
25
+ end
26
+ end
27
+
28
+ def build(id:, handle: :item, &block)
29
+ @handle = handle
30
+ html_options = merge_html_options(theme.resolve(:ordered_list_item), { id: id }, item_target_attrs)
31
+ template.capture(html_options, &block)
32
+ end
33
+
34
+ private
35
+
36
+ def validate_options!(id, url, html_options)
37
+ raise ArgumentError, "id: is required so the item appears in reorderable:reordered's itemIds" if id.blank?
38
+ return unless url.blank? && html_options.present?
39
+
40
+ raise ArgumentError, "html_options are only applied to the <a> rendered when url: is set"
41
+ end
42
+
43
+ def build_slots(content, &block)
44
+ slots = OrderedList::Item::Slots.new(template)
45
+ slots.with_title(content) if content
46
+ yield slots if block_given?
47
+ slots
48
+ end
49
+
50
+ def item_target_attrs
51
+ if @handle == :item
52
+ { data: { "reorderable-target": "item handle", action: HANDLE_ACTION } }
53
+ else
54
+ { data: { "reorderable-target": "item" } }
55
+ end
56
+ end
57
+
58
+ def render_body(slots, url:, target:, active:, **html_options)
59
+ content = render_content_slot(slots)
60
+ return content unless url.present?
61
+
62
+ aria = active ? { aria: { current: "page" } } : {}
63
+ attrs = merge_html_options(html_options, { data: { "reorderable-target": "trigger" } }, aria)
64
+ template.content_tag(:a, content, href: url, target: target, **attrs)
65
+ end
66
+
67
+ def render_icon_position(slots, position)
68
+ slot_name = position == :leading ? :icon_leading : :icon_trailing
69
+ is_handle = @handle == position
70
+ custom_icon = render_icon_slot(slots, slot_name)
71
+ return unless custom_icon || is_handle
72
+
73
+ html_options = merge_html_options(theme.resolve(:ordered_list_item_handle), handle_target_attrs(is_handle))
74
+ template.content_tag(:span, custom_icon || default_handle_icon, **html_options)
75
+ end
76
+
77
+ def handle_target_attrs(is_handle)
78
+ return {} unless is_handle
79
+
80
+ { data: { "reorderable-target": "handle", action: HANDLE_ACTION } }
81
+ end
82
+
83
+ def render_icon_slot(slots, name)
84
+ slots.resolve(name) do |value|
85
+ next value unless Components::Icon.icon_name?(value)
86
+
87
+ Components::Icon.new(template).render(
88
+ name: value,
89
+ size: :sm,
90
+ classes: theme.resolve(:ordered_list_item_handle).fetch(:classes, ""),
91
+ aria: { hidden: "true" }
92
+ )
93
+ end
94
+ end
95
+
96
+ def default_handle_icon
97
+ Components::Icon.new(template).render(
98
+ name: "grip-vertical",
99
+ size: :sm,
100
+ classes: theme.resolve(:ordered_list_item_handle).fetch(:classes, ""),
101
+ aria: { hidden: "true" }
102
+ )
103
+ end
104
+
105
+ def render_content_slot(slots)
106
+ title = render_title_slot(slots)
107
+ description = render_description_slot(slots)
108
+ return unless title || description
109
+
110
+ template.content_tag(:span, **merge_html_options(theme.resolve(:ordered_list_item_content))) do
111
+ template.safe_join([title, description])
112
+ end
113
+ end
114
+
115
+ def render_title_slot(slots)
116
+ slots.resolve(:title) do |v|
117
+ template.content_tag(:span, v, **merge_html_options(theme.resolve(:ordered_list_item_title)))
118
+ end
119
+ end
120
+
121
+ def render_description_slot(slots)
122
+ slots.resolve(:description) do |v|
123
+ template.content_tag(:span, v, **merge_html_options(theme.resolve(:ordered_list_item_description)))
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class OrderedList < Plumber::Base
6
+ def render(move_key: "Alt", editing: false, orientation: nil, role: "list", **kwargs, &block)
7
+ stimulus = {
8
+ data: {
9
+ controller: "reorderable",
10
+ "reorderable-move-key-value": move_key,
11
+ "reorderable-editing-value": editing,
12
+ "reorderable-orientation-value": orientation
13
+ }.compact
14
+ }
15
+ html_options = merge_html_options(theme.resolve(:ordered_list), kwargs, stimulus)
16
+ template.content_tag(:ol, template.capture(self, &block), **html_options, role: role)
17
+ end
18
+
19
+ def item(content = nil, **kwargs, &block)
20
+ OrderedList::Item.new(template).render(content, **kwargs, &block)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class ProgressBar < Plumber::Base
6
+ def render(...) = render_bar(...)
7
+
8
+ private
9
+
10
+ def render_bar(value:, min: 0, max: 100, indeterminate: false, **kwargs)
11
+ html_options = merge_html_options(
12
+ theme.resolve(:progress_bar),
13
+ kwargs,
14
+ stimulus_data(value: value, min: min, max: max, indeterminate: indeterminate),
15
+ { role: "progressbar", aria: progress_aria(value: value, min: min, max: max, indeterminate: indeterminate) }
16
+ )
17
+ template.content_tag(:div, render_fill, **html_options)
18
+ end
19
+
20
+ def render_fill
21
+ template.content_tag(
22
+ :div,
23
+ nil,
24
+ **merge_html_options(theme.resolve(:progress_bar_fill), { data: { "progress-target": "fill" } })
25
+ )
26
+ end
27
+
28
+ def progress_aria(value:, min:, max:, indeterminate:)
29
+ aria = { valuemin: min, valuemax: max }
30
+ aria[:valuenow] = value unless indeterminate
31
+ aria
32
+ end
33
+
34
+ def stimulus_data(value:, min:, max:, indeterminate:)
35
+ {
36
+ data: {
37
+ controller: "progress",
38
+ "progress-variant-value": "bar",
39
+ "progress-current-value": value,
40
+ "progress-min-value": min,
41
+ "progress-max-value": max,
42
+ "progress-indeterminate-value": indeterminate
43
+ }
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class ProgressMeter < Plumber::Base
6
+ def render(...) = render_meter(...)
7
+
8
+ private
9
+
10
+ def render_meter(value:, min: 0, max: 100, low: nil, high: nil, optimum: nil, **kwargs)
11
+ attrs = { value: value, min: min, max: max, low: low, high: high, optimum: optimum }.compact
12
+ html_options = merge_html_options(
13
+ theme.resolve(:progress_meter),
14
+ kwargs,
15
+ stimulus_data(value: value, min: min, max: max, low: low, high: high, optimum: optimum),
16
+ attrs
17
+ )
18
+ template.content_tag(:meter, nil, **html_options)
19
+ end
20
+
21
+ def stimulus_data(value:, min:, max:, low:, high:, optimum:)
22
+ data = {
23
+ controller: "progress",
24
+ "progress-target": "meter",
25
+ "progress-variant-value": "meter",
26
+ "progress-current-value": value,
27
+ "progress-min-value": min,
28
+ "progress-max-value": max
29
+ }
30
+ data["progress-low-value"] = low if low
31
+ data["progress-high-value"] = high if high
32
+ data["progress-optimum-value"] = optimum if optimum
33
+ { data: data }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ class ProgressRing < Plumber::Base
6
+ def render(...) = render_ring(...)
7
+
8
+ private
9
+
10
+ def render_ring(value:, max: 100, min: 0, indeterminate: false, **kwargs)
11
+ icon_options = merge_html_options(
12
+ theme.resolve(:progress_ring),
13
+ kwargs,
14
+ stimulus_data(value: value, min: min, max: max, indeterminate: indeterminate),
15
+ { role: "progressbar", aria: progress_aria(value: value, min: min, max: max, indeterminate: indeterminate) }
16
+ )
17
+ Components::Icon.new(template).render(name: "progress-ring", **icon_options)
18
+ end
19
+
20
+ def progress_aria(value:, min:, max:, indeterminate:)
21
+ aria = { valuemin: min, valuemax: max }
22
+ aria[:valuenow] = value unless indeterminate
23
+ aria
24
+ end
25
+
26
+ def stimulus_data(value:, min:, max:, indeterminate:)
27
+ {
28
+ data: {
29
+ controller: "progress",
30
+ "progress-variant-value": "ring",
31
+ "progress-current-value": value,
32
+ "progress-min-value": min,
33
+ "progress-max-value": max,
34
+ "progress-indeterminate-value": indeterminate
35
+ }
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -13,7 +13,7 @@ module StimulusPlumbers
13
13
  def render(datetime: nil, id: nil, interactive: false, orientation: :vertical, **kwargs, &block)
14
14
  @detail_id = self.class.detail_id_for(id || template.sp_dom_id)
15
15
 
16
- slots = Timeline::Event::Slots.new
16
+ slots = Timeline::Event::Slots.new(template)
17
17
  yield slots if block_given?
18
18
 
19
19
  validate_slots!(slots, datetime: datetime)
@@ -107,11 +107,12 @@ module StimulusPlumbers
107
107
  if type == :icon && icon_name
108
108
  Components::Icon.new(template).render(
109
109
  name: icon_name,
110
+ size: :sm,
110
111
  classes: theme.resolve(:timeline_item_indicator_icon_slot).fetch(:classes, ""),
111
112
  aria: { hidden: "true" }
112
113
  )
113
114
  else
114
- template.content_tag(:span, nil, **merge_html_options(theme.resolve(:timeline_item_indicator_dot)))
115
+ Components::Indicator.new(template).render(type: :dot, variant: :primary)
115
116
  end
116
117
  end
117
118
 
@@ -4,7 +4,8 @@ module StimulusPlumbers
4
4
  module Components
5
5
  class Timeline
6
6
  class Group < Plumber::Base
7
- def render(**kwargs, &block)
7
+ def render(orientation: :vertical, **kwargs, &block)
8
+ @orientation = orientation
8
9
  content = template.capture(self, &block)
9
10
  html_options = merge_html_options(theme.resolve(:timeline_group), kwargs)
10
11
  template.content_tag(:div, content, **html_options)
@@ -12,7 +13,7 @@ module StimulusPlumbers
12
13
 
13
14
  def section(date:, datetime: nil, **kwargs, &block)
14
15
  inner_tl = Timeline.new(template)
15
- inner_tl.instance_variable_set(:@orientation, :vertical)
16
+ inner_tl.instance_variable_set(:@orientation, @orientation)
16
17
  inner_tl.instance_variable_set(:@interactive, false)
17
18
 
18
19
  events_html = template.capture(inner_tl, &block)
@@ -29,10 +30,11 @@ module StimulusPlumbers
29
30
  theme.resolve(:timeline_group_section_date),
30
31
  datetime ? { datetime: datetime } : {}
31
32
  )
33
+ list_attrs = merge_html_options(theme.resolve(:timeline_group_section_list, orientation: @orientation))
32
34
  template.safe_join(
33
35
  [
34
36
  template.content_tag(:time, date, **date_attrs),
35
- template.content_tag(:ol, events_html, **merge_html_options(theme.resolve(:timeline_group_section_list)))
37
+ template.content_tag(:ol, events_html, **list_attrs)
36
38
  ]
37
39
  )
38
40
  end
@@ -18,5 +18,9 @@ module StimulusPlumbers
18
18
  include StimulusPlumbers::Helpers
19
19
  end
20
20
  end
21
+
22
+ rake_tasks do
23
+ load File.join(__dir__, "../tasks/stimulus_plumbers.rake")
24
+ end
21
25
  end
22
26
  end
@@ -51,6 +51,7 @@ module StimulusPlumbers
51
51
  build_reveal_button do
52
52
  Components::Icon.new(@template).render(
53
53
  name: "reveal",
54
+ size: :sm,
54
55
  aria: { hidden: "true" },
55
56
  **theme.resolve(:button_icon)
56
57
  )
@@ -83,6 +83,7 @@ module StimulusPlumbers
83
83
  build_clear_button do
84
84
  Components::Icon.new(@template).render(
85
85
  name: "close",
86
+ size: :sm,
86
87
  aria: { hidden: "true" },
87
88
  **theme.resolve(:button_icon)
88
89
  )
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Generators
5
+ module CssEntrypoint
6
+ STIMULUS_PLUMBERS_CSS_ENTRY = "STIMULUS_PLUMBERS_CSS_ENTRY"
7
+
8
+ # Shared with stimulus_plumbers_tailwind's InstallGenerator — checked in order,
9
+ # each path is another gem/tool's own default entry file. Detection is theme-
10
+ # agnostic: any of these may be the CSS entry regardless of which theme is active.
11
+ ENTRY_CANDIDATES = [
12
+ "app/assets/stylesheets/application.tailwind.css", # tailwindcss-rails 2.x default
13
+ "app/assets/tailwind/application.css", # tailwindcss-rails 3.x+ default
14
+ "app/assets/stylesheets/application.css", # Rails/Propshaft default manifest
15
+ "app/javascript/entrypoints/application.css" # jsbundling-rails (esbuild/webpack) default
16
+ ].freeze
17
+
18
+ def entry_css_file(candidates:, env_var:)
19
+ if ENV[env_var]
20
+ path = File.expand_path(ENV.fetch(env_var, nil))
21
+ return path if File.exist?(path)
22
+ end
23
+
24
+ candidates
25
+ .map { |c| File.join(destination_root, c) }
26
+ .find { |f| File.exist?(f) }
27
+ end
28
+
29
+ def warn_entry_css_not_found(candidates:, env_var:, label:)
30
+ tried = candidates.map { |c| File.join(destination_root, c) }
31
+ tried.unshift(File.expand_path(ENV[env_var])) if ENV[env_var]
32
+ say "Could not find a #{label} entry file. Tried: #{tried.join(", ")}. " \
33
+ "Set #{env_var}=/path/to/entry.css and re-run.",
34
+ :red
35
+ end
36
+
37
+ def apply_edit(css_file, directive, stale_pattern:, anchor_pattern: nil)
38
+ content = File.read(css_file)
39
+ new_content, status = content_edit(content, directive, stale_pattern: stale_pattern, anchor_pattern: anchor_pattern)
40
+
41
+ if new_content
42
+ File.write(css_file, new_content)
43
+ say_status status, relative_to_destination(css_file), :green
44
+ else
45
+ say_status :identical, relative_to_destination(css_file)
46
+ end
47
+ rescue Errno::EROFS, Errno::EACCES => e
48
+ say "Could not update #{relative_to_destination(css_file)}: #{e.message}. Skipping.", :yellow
49
+ end
50
+
51
+ # `stale_pattern:` is required — every call site needs stale-import detection,
52
+ # so making it optional would leave an untested "anchor without stale" state.
53
+ def content_edit(content, directive, stale_pattern:, anchor_pattern: nil)
54
+ return nil if content.include?(directive)
55
+
56
+ if (existing = content.match(stale_pattern))
57
+ return [content.sub(existing[0], directive), :update]
58
+ end
59
+
60
+ anchor_pattern ? insert_near_anchor(content, directive, anchor_pattern) : prepend(content, directive)
61
+ end
62
+
63
+ def prepend(content, directive)
64
+ ["#{directive}\n#{content}", :prepend]
65
+ end
66
+
67
+ def insert_near_anchor(content, directive, anchor_pattern)
68
+ if (anchor = content.match(anchor_pattern))
69
+ [content.sub(anchor[0], "#{anchor[0]}\n#{directive}"), :insert]
70
+ else
71
+ ["#{content.rstrip}\n#{directive}\n", :append]
72
+ end
73
+ end
74
+
75
+ def relative_to_destination(path)
76
+ path.delete_prefix("#{destination_root}/")
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module StimulusPlumbers
6
+ module Generators
7
+ module TokensDirective
8
+ GEM_ROOT = File.expand_path("../../..", __dir__)
9
+ TOKENS_CSS_REL_PATH = "app/assets/stylesheets/stimulus_plumbers/tokens.css"
10
+
11
+ module_function
12
+
13
+ def directive(from:)
14
+ path = Pathname.new(GEM_ROOT).join(TOKENS_CSS_REL_PATH)
15
+ rel = path.relative_path_from(Pathname.new(from))
16
+ %(@import "#{rel}";)
17
+ end
18
+
19
+ def stale_pattern
20
+ %r{@import "[^"]*#{Regexp.escape(TOKENS_CSS_REL_PATH)}";}
21
+ end
22
+ end
23
+ end
24
+ end
@@ -4,13 +4,7 @@ module StimulusPlumbers
4
4
  module Helpers
5
5
  module AvatarHelper
6
6
  def sp_avatar(...)
7
- avatar_renderer.render(...)
8
- end
9
-
10
- private
11
-
12
- def avatar_renderer
13
- Components::Avatar.new(self)
7
+ Components::Avatar.new(self).render(...)
14
8
  end
15
9
  end
16
10
  end
@@ -4,18 +4,12 @@ module StimulusPlumbers
4
4
  module Helpers
5
5
  module ButtonHelper
6
6
  def sp_button(content = nil, **kwargs, &block)
7
- button_renderer.render(content, **kwargs, &block)
7
+ Components::Button.new(self).render(content, **kwargs, &block)
8
8
  end
9
9
 
10
10
  def sp_button_group(...)
11
11
  Components::Button::Group.new(self).render(...)
12
12
  end
13
-
14
- private
15
-
16
- def button_renderer
17
- Components::Button.new(self)
18
- end
19
13
  end
20
14
  end
21
15
  end
@@ -9,20 +9,13 @@ module StimulusPlumbers
9
9
  kwargs.deep_merge(
10
10
  data: {
11
11
  "calendar-month-year-value": date.year,
12
- "calendar-month-month-value": date.month - 1,
13
- "calendar-month-day-value": date.day
12
+ "calendar-month-month-value": date.month - 1
14
13
  }
15
14
  )
16
15
  else
17
16
  kwargs
18
17
  end
19
- calendar_renderer.month(**html_options)
20
- end
21
-
22
- private
23
-
24
- def calendar_renderer
25
- Components::Calendar.new(self)
18
+ Components::Calendar.new(self).month(**html_options)
26
19
  end
27
20
  end
28
21
  end
@@ -31,8 +31,7 @@ module StimulusPlumbers
31
31
  [
32
32
  content_tag(
33
33
  :turbo_frame,
34
- id: "calendar-month-frame",
35
- data: { "combobox-date-target": "monthView" }
34
+ id: "calendar-month-frame"
36
35
  ) do
37
36
  calendar_turbo_renderer.month(
38
37
  date: date,
@@ -48,8 +47,7 @@ module StimulusPlumbers
48
47
  content_tag(
49
48
  :turbo_frame,
50
49
  id: "calendar-year-frame",
51
- hidden: true,
52
- data: { "combobox-date-target": "yearView" }
50
+ hidden: true
53
51
  ) do
54
52
  calendar_turbo_renderer.year(
55
53
  date: date,
@@ -63,8 +61,7 @@ module StimulusPlumbers
63
61
  content_tag(
64
62
  :turbo_frame,
65
63
  id: "calendar-decade-frame",
66
- hidden: true,
67
- data: { "combobox-date-target": "decadeView" }
64
+ hidden: true
68
65
  ) do
69
66
  calendar_turbo_renderer.decade(
70
67
  date: date,
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Helpers
5
+ module ChecklistHelper
6
+ def sp_checklist(...)
7
+ Components::Checklist.new(self).render(...)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Helpers
5
+ module IndicatorHelper
6
+ def sp_indicator(variant: :primary, type: :dot, pulse: false, **kwargs, &block)
7
+ Components::Indicator.new(self).render(type: type, variant: variant, pulse: pulse, **kwargs, &block)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Helpers
5
+ module OrderedListHelper
6
+ def sp_ordered_list(...)
7
+ Components::OrderedList.new(self).render(...)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,13 +4,7 @@ module StimulusPlumbers
4
4
  module Helpers
5
5
  module PopoverHelper
6
6
  def sp_popover(...)
7
- popover_renderer.render(...)
8
- end
9
-
10
- private
11
-
12
- def popover_renderer
13
- Components::Popover.new(self)
7
+ Components::Popover.new(self).render(...)
14
8
  end
15
9
  end
16
10
  end