stimulus_plumbers 0.4.8 → 0.4.10

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +7 -9
  4. data/app/assets/javascripts/stimulus-plumbers/controllers.manifest.json +367 -23
  5. data/app/assets/javascripts/stimulus-plumbers/index.es.js +678 -233
  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 +1 -0
  13. data/docs/component/calendar.md +7 -16
  14. data/docs/component/checklist.md +153 -0
  15. data/docs/component/form.md +27 -5
  16. data/docs/component/icon.md +7 -5
  17. data/docs/component/indicator.md +55 -0
  18. data/docs/component/list.md +2 -3
  19. data/docs/component/ordered_list.md +118 -0
  20. data/docs/component/plumber.md +21 -0
  21. data/docs/component/popover.md +1 -2
  22. data/docs/component/progress.md +65 -0
  23. data/docs/component/timeline.md +47 -12
  24. data/docs/guide.md +21 -0
  25. data/lib/generators/stimulus_plumbers/install/install_generator.rb +21 -11
  26. data/lib/stimulus_plumbers/components/button.rb +1 -0
  27. data/lib/stimulus_plumbers/components/checklist/item/slots.rb +13 -0
  28. data/lib/stimulus_plumbers/components/checklist/item.rb +64 -0
  29. data/lib/stimulus_plumbers/components/checklist.rb +50 -0
  30. data/lib/stimulus_plumbers/components/combobox/typeahead.rb +1 -0
  31. data/lib/stimulus_plumbers/components/icon.rb +2 -2
  32. data/lib/stimulus_plumbers/components/indicator.rb +30 -0
  33. data/lib/stimulus_plumbers/components/link.rb +1 -0
  34. data/lib/stimulus_plumbers/components/ordered_list/item/slots.rb +13 -0
  35. data/lib/stimulus_plumbers/components/ordered_list/item.rb +129 -0
  36. data/lib/stimulus_plumbers/components/ordered_list.rb +24 -0
  37. data/lib/stimulus_plumbers/components/progress_bar.rb +48 -0
  38. data/lib/stimulus_plumbers/components/progress_meter.rb +37 -0
  39. data/lib/stimulus_plumbers/components/progress_ring.rb +40 -0
  40. data/lib/stimulus_plumbers/components/timeline/event.rb +2 -1
  41. data/lib/stimulus_plumbers/components/timeline/group.rb +5 -3
  42. data/lib/stimulus_plumbers/engine.rb +0 -4
  43. data/lib/stimulus_plumbers/form/builder.rb +4 -0
  44. data/lib/stimulus_plumbers/form/field.rb +1 -1
  45. data/lib/stimulus_plumbers/form/fields/inputs/code.rb +86 -0
  46. data/lib/stimulus_plumbers/form/fields/inputs/credit_card.rb +98 -0
  47. data/lib/stimulus_plumbers/form/fields/inputs/password.rb +1 -0
  48. data/lib/stimulus_plumbers/form/fields/inputs/search.rb +1 -0
  49. data/lib/stimulus_plumbers/form/fields/renderer.rb +2 -0
  50. data/lib/stimulus_plumbers/generators/css_entrypoint.rb +37 -9
  51. data/lib/stimulus_plumbers/generators/tokens_directive.rb +9 -5
  52. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +1 -7
  53. data/lib/stimulus_plumbers/helpers/button_helper.rb +1 -7
  54. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +2 -9
  55. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +3 -6
  56. data/lib/stimulus_plumbers/helpers/checklist_helper.rb +11 -0
  57. data/lib/stimulus_plumbers/helpers/indicator_helper.rb +11 -0
  58. data/lib/stimulus_plumbers/helpers/ordered_list_helper.rb +11 -0
  59. data/lib/stimulus_plumbers/helpers/popover_helper.rb +1 -7
  60. data/lib/stimulus_plumbers/helpers/progress_helper.rb +21 -0
  61. data/lib/stimulus_plumbers/helpers.rb +8 -0
  62. data/lib/stimulus_plumbers/themes/base.rb +4 -0
  63. data/lib/stimulus_plumbers/themes/schema/icon.rb +21 -4
  64. data/lib/stimulus_plumbers/themes/schema/indicator/ranges.rb +14 -0
  65. data/lib/stimulus_plumbers/themes/schema.rb +82 -25
  66. data/lib/stimulus_plumbers/version.rb +1 -1
  67. data/lib/stimulus_plumbers.rb +10 -0
  68. data/vendor/ARIA.md +29 -2
  69. data/vendor/component/manifest.json +215 -0
  70. data/vendor/controller/docs/calendar.md +4 -7
  71. data/vendor/controller/docs/checklist.md +45 -0
  72. data/vendor/controller/docs/combobox.md +1 -31
  73. data/vendor/controller/docs/input-clearable.md +1 -4
  74. data/vendor/controller/docs/input-formatter.md +88 -23
  75. data/vendor/controller/docs/modal.md +1 -4
  76. data/vendor/controller/docs/popover.md +1 -3
  77. data/vendor/controller/docs/progress.md +82 -0
  78. data/vendor/controller/docs/reorderable.md +82 -0
  79. data/vendor/controller/docs/visibility.md +31 -0
  80. data/vendor/controller/manifest.json +367 -23
  81. metadata +27 -1
@@ -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
@@ -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
@@ -9,10 +9,6 @@ module StimulusPlumbers
9
9
  config.autoload_paths << File.expand_path("../stimulus-plumbers", __dir__)
10
10
  config.i18n.load_path += Dir[File.expand_path("../../config/locales/*.{rb,yml}", __dir__)]
11
11
 
12
- initializer "stimulus_plumbers.assets", after: :set_default_precompile do |app|
13
- app.config.assets.precompile += %w[stimulus_plumbers/tokens.css] if app.config.respond_to?(:assets)
14
- end
15
-
16
12
  initializer "stimulus_plumbers.helpers" do
17
13
  ActiveSupport.on_load(:action_view) do
18
14
  include StimulusPlumbers::Helpers
@@ -11,6 +11,8 @@ require_relative "fields/renderer"
11
11
  require_relative "fields/fieldset"
12
12
  require_relative "fields/inputs/checkbox"
13
13
  require_relative "fields/inputs/combobox"
14
+ require_relative "fields/inputs/code"
15
+ require_relative "fields/inputs/credit_card"
14
16
  require_relative "fields/inputs/datetime"
15
17
  require_relative "fields/inputs/radio"
16
18
  require_relative "fields/inputs/file"
@@ -30,6 +32,8 @@ module StimulusPlumbers
30
32
  include Plumber::Options::Html
31
33
  include Plumber::Options::Aria
32
34
  include Fields::Inputs::Checkbox
35
+ include Fields::Inputs::Code
36
+ include Fields::Inputs::CreditCard
33
37
  include Fields::Inputs::Datetime
34
38
  include Fields::Inputs::Radio
35
39
  include Fields::Inputs::File
@@ -8,7 +8,7 @@ module StimulusPlumbers
8
8
  class Field < Base
9
9
  TYPES = %i[
10
10
  text email number url tel color month week range datetime_local
11
- text_area file password date time select search
11
+ text_area file password date time select search code credit_card
12
12
  ].freeze
13
13
  COLLECTION_TYPES = %i[radio check_box collection_select grouped_collection_select].freeze
14
14
  OPTIONS = (Base::OPTIONS + %i[hide_label]).freeze
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module Code
8
+ private
9
+
10
+ def render_code_input(attribute, html_opts, opts, error, length:, charset: :digits, groups: [], **kwargs)
11
+ floating = kwargs.delete(:floating)
12
+ raise ArgumentError, "floating labels are not supported for code fields" if floating.present?
13
+
14
+ validate_code_options!(length, groups)
15
+ inputmode = kwargs.delete(:inputmode) || (charset.to_sym == :digits ? "numeric" : nil)
16
+ autocomplete = kwargs.delete(:autocomplete) || "one-time-code"
17
+ config = { error: error, length: length, charset: charset, groups: groups,
18
+ autocomplete: autocomplete, inputmode: inputmode, kwargs: kwargs
19
+ }
20
+ render_code_field(attribute, html_opts, opts, config)
21
+ end
22
+
23
+ def render_code_field(attribute, html_opts, opts, config)
24
+ @template.content_tag(:div, **code_wrapper_options(config)) do
25
+ @template.safe_join(
26
+ [render_code_cells(config), code_input(attribute, html_opts, opts, config)]
27
+ )
28
+ end
29
+ end
30
+
31
+ def code_wrapper_options(config)
32
+ merge_html_options(
33
+ theme.resolve(:form_field_input_code, error: config[:error]),
34
+ { data: {
35
+ controller: "input-formatter",
36
+ input_formatter_format_value: "code",
37
+ input_formatter_options_value: { charset: config[:charset].to_s, length: config[:length] }.to_json,
38
+ input_formatter_groups_value: config[:groups].presence
39
+ }
40
+ }
41
+ )
42
+ end
43
+
44
+ def code_input(attribute, html_opts, opts, config)
45
+ input_options = merge_html_options(
46
+ theme.resolve(:form_field_input_code_overlay, error: config[:error]),
47
+ opts,
48
+ html_opts,
49
+ config[:kwargs],
50
+ { maxlength: config[:length], autocomplete: config[:autocomplete], inputmode: config[:inputmode],
51
+ data: { input_formatter_target: "input", action: formatter_actions }
52
+ }
53
+ )
54
+ @template.text_field(@object_name, attribute, objectify_options(input_options))
55
+ end
56
+
57
+ def render_code_cells(config)
58
+ count = config[:groups].present? ? config[:groups].sum : config[:length]
59
+ cell_options = merge_html_options(
60
+ theme.resolve(:form_field_input_code_cell, error: config[:error]),
61
+ { data: { input_formatter_target: "cell" } }
62
+ )
63
+ @template.content_tag(:div, **merge_html_options(theme.resolve(:form_field_input_code_cells))) do
64
+ @template.safe_join(Array.new(count) { @template.content_tag(:span, nil, **cell_options) })
65
+ end
66
+ end
67
+
68
+ def validate_code_options!(length, groups)
69
+ raise ArgumentError, "length must be a positive integer" unless positive_integer?(length)
70
+ return if groups.blank?
71
+
72
+ raise ArgumentError, "groups must be an array of positive integers" unless valid_groups?(groups)
73
+ raise ArgumentError, "groups must add up to length" unless groups.sum == length
74
+ end
75
+
76
+ def valid_groups?(groups) = groups.is_a?(Array) && groups.all? { |group| positive_integer?(group) }
77
+ def positive_integer?(value) = value.is_a?(Integer) && value.positive?
78
+
79
+ def formatter_actions
80
+ "input->input-formatter#onInput focus->input-formatter#onFocus blur->input-formatter#onBlur"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Form
5
+ module Fields
6
+ module Inputs
7
+ module CreditCard
8
+ DEFAULT_GROUPS = [4, 4, 4, 4].freeze
9
+
10
+ private
11
+
12
+ def render_credit_card_input(attribute, html_opts, opts, error, groups: DEFAULT_GROUPS, floating: nil, **kwargs)
13
+ raise ArgumentError, "floating labels are not supported for credit card fields" if floating.present?
14
+
15
+ normalized_groups = groups.presence || DEFAULT_GROUPS
16
+ inputmode = kwargs.delete(:inputmode) || "numeric"
17
+ autocomplete = kwargs.delete(:autocomplete) || "cc-number"
18
+ validate_credit_card_groups!(normalized_groups)
19
+ length = normalized_groups.sum
20
+ config = { error: error, groups: normalized_groups, length: length,
21
+ autocomplete: autocomplete, inputmode: inputmode, kwargs: kwargs
22
+ }
23
+ render_credit_card_field(attribute, html_opts, opts, config)
24
+ end
25
+
26
+ def render_credit_card_field(attribute, html_opts, opts, config)
27
+ @template.content_tag(:div, **credit_card_wrapper_options(config)) do
28
+ @template.safe_join(
29
+ [render_credit_card_cells(config), credit_card_input(attribute, html_opts, opts, config)]
30
+ )
31
+ end
32
+ end
33
+
34
+ def credit_card_wrapper_options(config)
35
+ merge_html_options(
36
+ theme.resolve(:form_field_input_credit_card, error: config[:error]),
37
+ { data: {
38
+ controller: "input-formatter",
39
+ input_formatter_format_value: "creditCard",
40
+ input_formatter_groups_value: config[:groups]
41
+ }
42
+ }
43
+ )
44
+ end
45
+
46
+ def credit_card_input(attribute, html_opts, opts, config)
47
+ input_options = merge_html_options(
48
+ theme.resolve(:form_field_input_credit_card_overlay, error: config[:error]),
49
+ opts,
50
+ html_opts,
51
+ config[:kwargs],
52
+ { maxlength: config[:length], autocomplete: config[:autocomplete], inputmode: config[:inputmode],
53
+ data: { input_formatter_target: "input", action: formatter_actions }
54
+ }
55
+ )
56
+ @template.text_field(@object_name, attribute, objectify_options(input_options))
57
+ end
58
+
59
+ def render_credit_card_cells(config)
60
+ cells = config[:groups].each_index.flat_map do |index|
61
+ index.zero? ? [render_credit_card_cell(config)] : [render_credit_card_separator, render_credit_card_cell(config)]
62
+ end
63
+
64
+ @template.content_tag(:div, **merge_html_options(theme.resolve(:form_field_input_credit_card_cells))) do
65
+ @template.safe_join(cells)
66
+ end
67
+ end
68
+
69
+ def render_credit_card_cell(config)
70
+ cell_options = merge_html_options(
71
+ theme.resolve(:form_field_input_credit_card_cell, error: config[:error]),
72
+ { data: { input_formatter_target: "cell" } }
73
+ )
74
+ @template.content_tag(:span, nil, **cell_options)
75
+ end
76
+
77
+ def render_credit_card_separator
78
+ separator_options = merge_html_options(
79
+ theme.resolve(:form_field_input_credit_card_separator),
80
+ { aria: { hidden: true } }
81
+ )
82
+ @template.content_tag(:span, "-", **separator_options)
83
+ end
84
+
85
+ def validate_credit_card_groups!(groups)
86
+ return if groups.is_a?(Array) && groups.all? { |group| group.is_a?(Integer) && group.positive? }
87
+
88
+ raise ArgumentError, "groups must be an array of positive integers"
89
+ end
90
+
91
+ def formatter_actions
92
+ "input->input-formatter#onInput focus->input-formatter#onFocus blur->input-formatter#onBlur"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ 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
  )
@@ -18,6 +18,8 @@ module StimulusPlumbers
18
18
  text_area: :render_text_area_input,
19
19
  file: :render_file_input,
20
20
  password: :render_password_input,
21
+ code: :render_code_input,
22
+ credit_card: :render_credit_card_input,
21
23
  date: :render_combobox_date,
22
24
  time: :render_combobox_time,
23
25
  select: :render_combobox_dropdown,
@@ -1,15 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module StimulusPlumbers
4
6
  module Generators
5
7
  module CssEntrypoint
6
- STIMULUS_PLUMBERS_CSS_FILE = "STIMULUS_PLUMBERS_CSS_FILE"
8
+ STIMULUS_PLUMBERS_CSS_ENTRY = "STIMULUS_PLUMBERS_CSS_ENTRY"
7
9
 
8
- def entry_css_file(candidates:, env_var:, fallback_env_var: nil)
9
- [env_var, fallback_env_var].compact.each do |var|
10
- next unless ENV[var]
10
+ # Shared with stimulus_plumbers_tailwind's InstallGenerator — checked in order,
11
+ # each path is another gem/tool's own default entry file. Detection is theme-
12
+ # agnostic: any of these may be the CSS entry regardless of which theme is active.
13
+ ENTRY_CANDIDATES = [
14
+ "app/assets/stylesheets/application.tailwind.css", # tailwindcss-rails 2.x default
15
+ "app/assets/tailwind/application.css", # tailwindcss-rails 3.x+ default
16
+ "app/assets/stylesheets/application.css", # Rails/Propshaft default manifest
17
+ "app/javascript/entrypoints/application.css" # jsbundling-rails (esbuild/webpack) default
18
+ ].freeze
11
19
 
12
- path = File.expand_path(ENV.fetch(var, nil))
20
+ def entry_css_file(candidates:, env_var:)
21
+ if ENV[env_var]
22
+ path = File.expand_path(ENV.fetch(env_var, nil))
13
23
  return path if File.exist?(path)
14
24
  end
15
25
 
@@ -18,11 +28,9 @@ module StimulusPlumbers
18
28
  .find { |f| File.exist?(f) }
19
29
  end
20
30
 
21
- def warn_entry_css_not_found(candidates:, env_var:, label:, fallback_env_var: nil)
31
+ def warn_entry_css_not_found(candidates:, env_var:, label:)
22
32
  tried = candidates.map { |c| File.join(destination_root, c) }
23
- [env_var, fallback_env_var].compact.each do |var|
24
- tried.unshift(File.expand_path(ENV[var])) if ENV[var]
25
- end
33
+ tried.unshift(File.expand_path(ENV[env_var])) if ENV[env_var]
26
34
  say "Could not find a #{label} entry file. Tried: #{tried.join(", ")}. " \
27
35
  "Set #{env_var}=/path/to/entry.css and re-run.",
28
36
  :red
@@ -42,6 +50,26 @@ module StimulusPlumbers
42
50
  say "Could not update #{relative_to_destination(css_file)}: #{e.message}. Skipping.", :yellow
43
51
  end
44
52
 
53
+ # CSS copied by an installer belongs to the application from this point on.
54
+ # Keep an existing file intact so a generator rerun never overwrites local
55
+ # customizations (the same policy used by tailwindcss-rails' installer).
56
+ def copy_asset(source, destination)
57
+ path = File.join(destination_root, destination)
58
+
59
+ if File.exist?(path)
60
+ say_status :identical, destination
61
+ return true
62
+ end
63
+
64
+ FileUtils.mkdir_p(File.dirname(path))
65
+ FileUtils.cp(source, path)
66
+ say_status :create, destination, :green
67
+ true
68
+ rescue Errno::EROFS, Errno::EACCES => e
69
+ say "Could not copy #{destination}: #{e.message}. Skipping.", :yellow
70
+ false
71
+ end
72
+
45
73
  # `stale_pattern:` is required — every call site needs stale-import detection,
46
74
  # so making it optional would leave an untested "anchor without stale" state.
47
75
  def content_edit(content, directive, stale_pattern:, anchor_pattern: nil)
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  module StimulusPlumbers
4
6
  module Generators
5
7
  module TokensDirective
6
- GEM_ROOT = File.expand_path("../../..", __dir__)
7
- TOKENS_CSS_REL_PATH = "app/assets/stylesheets/stimulus_plumbers/tokens.css"
8
+ TOKENS_CSS_PATH = "app/assets/stylesheets/stimulus_plumbers/tokens.css"
8
9
 
9
10
  module_function
10
11
 
11
- def directive
12
- %(@import "#{GEM_ROOT}/#{TOKENS_CSS_REL_PATH}";)
12
+ def directive(from:, destination_root:)
13
+ path = Pathname.new(destination_root).join(TOKENS_CSS_PATH)
14
+ rel = path.relative_path_from(Pathname.new(from))
15
+ rel = "./#{rel}" unless rel.to_s.start_with?(".", "/")
16
+ %(@import "#{rel}";)
13
17
  end
14
18
 
15
19
  def stale_pattern
16
- %r{@import "[^"]*#{Regexp.escape(TOKENS_CSS_REL_PATH)}";}
20
+ %r{@import "[^"]*#{Regexp.escape(TOKENS_CSS_PATH)}";}
17
21
  end
18
22
  end
19
23
  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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Helpers
5
+ module ProgressHelper
6
+ def sp_progress_bar(value:, min: 0, max: 100, indeterminate: false, **kwargs)
7
+ Components::ProgressBar.new(self).render(value: value, min: min, max: max, indeterminate: indeterminate, **kwargs)
8
+ end
9
+
10
+ def sp_progress_ring(value:, min: 0, max: 100, indeterminate: false, **kwargs)
11
+ Components::ProgressRing.new(self).render(value: value, min: min, max: max, indeterminate: indeterminate, **kwargs)
12
+ end
13
+
14
+ def sp_progress_meter(value:, min: 0, max: 100, low: nil, high: nil, optimum: nil, **kwargs)
15
+ Components::ProgressMeter.new(self).render(
16
+ value: value, min: min, max: max, low: low, high: high, optimum: optimum, **kwargs
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end