stimulus_plumbers 0.2.8 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/README.md +8 -4
- data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +450 -436
- data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
- data/lib/stimulus_plumbers/components/action_list/item.rb +27 -0
- data/lib/stimulus_plumbers/components/action_list/section.rb +21 -0
- data/lib/stimulus_plumbers/components/action_list.rb +23 -0
- data/lib/stimulus_plumbers/components/avatar.rb +73 -0
- data/lib/stimulus_plumbers/components/button/group.rb +17 -0
- data/lib/stimulus_plumbers/components/button.rb +27 -0
- data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +60 -41
- data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +15 -12
- data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +55 -0
- data/lib/stimulus_plumbers/components/calendar.rb +56 -0
- data/lib/stimulus_plumbers/components/card/section.rb +25 -0
- data/lib/stimulus_plumbers/components/card.rb +27 -0
- data/lib/stimulus_plumbers/components/combobox/autocomplete.rb +30 -34
- data/lib/stimulus_plumbers/components/combobox/date.rb +16 -18
- data/lib/stimulus_plumbers/components/combobox/dropdown.rb +13 -16
- data/lib/stimulus_plumbers/components/combobox/options/option.rb +34 -0
- data/lib/stimulus_plumbers/components/combobox/options/option_group.rb +29 -0
- data/lib/stimulus_plumbers/components/combobox/options.rb +59 -0
- data/lib/stimulus_plumbers/components/combobox/popover.rb +20 -0
- data/lib/stimulus_plumbers/components/combobox/time/drum.rb +37 -0
- data/lib/stimulus_plumbers/components/combobox/time.rb +32 -15
- data/lib/stimulus_plumbers/components/combobox/trigger.rb +38 -0
- data/lib/stimulus_plumbers/components/combobox.rb +59 -0
- data/lib/stimulus_plumbers/components/date_picker/navigation.rb +14 -22
- data/lib/stimulus_plumbers/components/date_picker/navigator.rb +1 -1
- data/lib/stimulus_plumbers/components/icon.rb +43 -0
- data/lib/stimulus_plumbers/components/popover/builder.rb +25 -0
- data/lib/stimulus_plumbers/components/popover.rb +31 -0
- data/lib/stimulus_plumbers/configuration.rb +3 -18
- data/lib/stimulus_plumbers/engine.rb +2 -2
- data/lib/stimulus_plumbers/form/builder.rb +8 -6
- data/lib/stimulus_plumbers/form/{field_component.rb → field.rb} +1 -1
- data/lib/stimulus_plumbers/form/fields/combobox.rb +7 -4
- data/lib/stimulus_plumbers/form/fields/error.rb +14 -0
- data/lib/stimulus_plumbers/form/fields/group.rb +14 -0
- data/lib/stimulus_plumbers/form/fields/hint.rb +14 -0
- data/lib/stimulus_plumbers/form/fields/label.rb +21 -0
- data/lib/stimulus_plumbers/form/fields/renderer.rb +20 -21
- data/lib/stimulus_plumbers/form/fields/search.rb +23 -9
- data/lib/stimulus_plumbers/form/fields/submit.rb +23 -0
- data/lib/stimulus_plumbers/helpers/action_list_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/avatar_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/button_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/calendar_helper.rb +1 -1
- data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +1 -1
- data/lib/stimulus_plumbers/helpers/card_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers/combobox_helper.rb +5 -5
- data/lib/stimulus_plumbers/helpers/popover_helper.rb +2 -2
- data/lib/stimulus_plumbers/plumber/base.rb +20 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/callable_inspector.rb +19 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/instance_exec.rb +35 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/klass_proxy.rb +34 -0
- data/lib/stimulus_plumbers/plumber/dispatcher/method_call.rb +36 -0
- data/lib/stimulus_plumbers/plumber/dispatcher.rb +28 -0
- data/lib/stimulus_plumbers/plumber/html_options.rb +52 -0
- data/lib/stimulus_plumbers/plumber/renderer.rb +89 -0
- data/lib/stimulus_plumbers/themes/base.rb +34 -20
- data/lib/stimulus_plumbers/themes/configuration.rb +38 -0
- data/lib/stimulus_plumbers/themes/schema/form/ranges.rb +14 -0
- data/lib/stimulus_plumbers/themes/schema/icon.rb +32 -0
- data/lib/stimulus_plumbers/themes/schema/ranges.rb +5 -5
- data/lib/stimulus_plumbers/themes/schema.rb +103 -0
- data/lib/stimulus_plumbers/version.rb +1 -1
- data/lib/stimulus_plumbers.rb +29 -19
- metadata +40 -34
- data/lib/stimulus_plumbers/components/action_list/renderer.rb +0 -47
- data/lib/stimulus_plumbers/components/avatar/renderer.rb +0 -74
- data/lib/stimulus_plumbers/components/button/renderer.rb +0 -33
- data/lib/stimulus_plumbers/components/calendar/month/turbo/renderer.rb +0 -57
- data/lib/stimulus_plumbers/components/calendar/renderer.rb +0 -35
- data/lib/stimulus_plumbers/components/card/renderer.rb +0 -41
- data/lib/stimulus_plumbers/components/combobox/option.rb +0 -27
- data/lib/stimulus_plumbers/components/combobox/option_group.rb +0 -52
- data/lib/stimulus_plumbers/components/combobox/renderer.rb +0 -78
- data/lib/stimulus_plumbers/components/icon/renderer.rb +0 -51
- data/lib/stimulus_plumbers/components/plumber/base.rb +0 -22
- data/lib/stimulus_plumbers/components/plumber/dispatcher.rb +0 -113
- data/lib/stimulus_plumbers/components/plumber/html_options.rb +0 -53
- data/lib/stimulus_plumbers/components/plumber/renderer.rb +0 -91
- data/lib/stimulus_plumbers/components/popover/renderer.rb +0 -46
- data/lib/stimulus_plumbers/components/time_picker/renderer.rb +0 -38
- data/lib/stimulus_plumbers/themes/base/action_list.rb +0 -14
- data/lib/stimulus_plumbers/themes/base/avatar.rb +0 -14
- data/lib/stimulus_plumbers/themes/base/button.rb +0 -18
- data/lib/stimulus_plumbers/themes/base/calendar.rb +0 -15
- data/lib/stimulus_plumbers/themes/base/card.rb +0 -12
- data/lib/stimulus_plumbers/themes/base/form.rb +0 -34
- data/lib/stimulus_plumbers/themes/base/layout.rb +0 -12
- data/lib/stimulus_plumbers/themes/tailwind/action_list.rb +0 -33
- data/lib/stimulus_plumbers/themes/tailwind/avatar.rb +0 -52
- data/lib/stimulus_plumbers/themes/tailwind/button.rb +0 -89
- data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +0 -34
- data/lib/stimulus_plumbers/themes/tailwind/card.rb +0 -24
- data/lib/stimulus_plumbers/themes/tailwind/form.rb +0 -108
- data/lib/stimulus_plumbers/themes/tailwind/layout.rb +0 -25
- data/lib/stimulus_plumbers/themes/tailwind_theme.rb +0 -29
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Plumber
|
|
5
|
+
class Base
|
|
6
|
+
include HtmlOptions
|
|
7
|
+
include Renderer
|
|
8
|
+
|
|
9
|
+
attr_reader :template
|
|
10
|
+
|
|
11
|
+
def initialize(template)
|
|
12
|
+
@template = template
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def theme
|
|
16
|
+
StimulusPlumbers.config.theme.current
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Plumber
|
|
5
|
+
module Dispatcher
|
|
6
|
+
module CallableInspector
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def args_for(callable)
|
|
10
|
+
callable.arity.negative? ? args : args.take(callable.arity)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def accepts_kwargs?(callable)
|
|
14
|
+
callable.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Plumber
|
|
5
|
+
module Dispatcher
|
|
6
|
+
class InstanceExec
|
|
7
|
+
include CallableInspector
|
|
8
|
+
|
|
9
|
+
attr_reader :block, :args, :kwargs
|
|
10
|
+
|
|
11
|
+
def initialize(block, *args, **kwargs)
|
|
12
|
+
@block = block
|
|
13
|
+
@args = args
|
|
14
|
+
@kwargs = kwargs
|
|
15
|
+
validate!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(target)
|
|
19
|
+
dispatched = args_for(block)
|
|
20
|
+
if accepts_kwargs?(block)
|
|
21
|
+
target.instance_exec(*dispatched, **kwargs, &block)
|
|
22
|
+
else
|
|
23
|
+
target.instance_exec(*dispatched, &block)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def validate!
|
|
30
|
+
raise ArgumentError, "invalid block: #{block.inspect}" unless block.is_a?(Proc)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Plumber
|
|
5
|
+
module Dispatcher
|
|
6
|
+
class KlassProxy
|
|
7
|
+
attr_reader :klass, :method_name, :args, :kwargs, :init_args, :init_kwargs
|
|
8
|
+
|
|
9
|
+
def initialize(klass, method_name, *args, init_args: [], init_kwargs: {}, **kwargs)
|
|
10
|
+
@klass = klass
|
|
11
|
+
@method_name = method_name
|
|
12
|
+
@args = args
|
|
13
|
+
@kwargs = kwargs
|
|
14
|
+
@init_args = init_args
|
|
15
|
+
@init_kwargs = init_kwargs
|
|
16
|
+
validate!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(_target)
|
|
20
|
+
klass.new(*init_args, **init_kwargs).public_send(method_name, *args, **kwargs)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def validate!
|
|
26
|
+
raise ArgumentError, "invalid class: #{klass.inspect}" unless klass.is_a?(Module)
|
|
27
|
+
return if method_name.is_a?(String) || method_name.is_a?(Symbol)
|
|
28
|
+
|
|
29
|
+
raise ArgumentError, "invalid method name: #{method_name.inspect}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Plumber
|
|
5
|
+
module Dispatcher
|
|
6
|
+
class MethodCall
|
|
7
|
+
include CallableInspector
|
|
8
|
+
|
|
9
|
+
attr_reader :method_name, :args, :kwargs
|
|
10
|
+
|
|
11
|
+
def initialize(method_name, *args, **kwargs)
|
|
12
|
+
@method_name = method_name
|
|
13
|
+
@args = args
|
|
14
|
+
@kwargs = kwargs
|
|
15
|
+
validate!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(target)
|
|
19
|
+
raise NotImplementedError, "#{method_name.inspect} not implemented" unless target.respond_to?(method_name, true)
|
|
20
|
+
|
|
21
|
+
method_call = target.method(method_name)
|
|
22
|
+
dispatched = args_for(method_call)
|
|
23
|
+
accepts_kwargs?(method_call) ? method_call.call(*dispatched, **kwargs) : method_call.call(*dispatched)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def validate!
|
|
29
|
+
return if method_name.is_a?(String) || method_name.is_a?(Symbol)
|
|
30
|
+
|
|
31
|
+
raise ArgumentError, "invalid method name: #{method_name.inspect}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "dispatcher/callable_inspector"
|
|
4
|
+
require_relative "dispatcher/method_call"
|
|
5
|
+
require_relative "dispatcher/instance_exec"
|
|
6
|
+
require_relative "dispatcher/klass_proxy"
|
|
7
|
+
|
|
8
|
+
module StimulusPlumbers
|
|
9
|
+
module Plumber
|
|
10
|
+
module Dispatcher
|
|
11
|
+
def self.build(callable, *args, method_name: nil, init_args: [], init_kwargs: {}, **kwargs)
|
|
12
|
+
case callable
|
|
13
|
+
when Symbol
|
|
14
|
+
MethodCall.new(callable, *args, **kwargs)
|
|
15
|
+
when Proc
|
|
16
|
+
InstanceExec.new(callable, *args, **kwargs)
|
|
17
|
+
when Module
|
|
18
|
+
KlassProxy.new(callable, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs)
|
|
19
|
+
when String
|
|
20
|
+
klass = callable.safe_constantize
|
|
21
|
+
raise ArgumentError, "could not resolve class from: #{callable.inspect}" unless klass
|
|
22
|
+
|
|
23
|
+
KlassProxy.new(klass, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module StimulusPlumbers
|
|
6
|
+
module Plumber
|
|
7
|
+
module HtmlOptions
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
def merge_html_options(*hashes)
|
|
11
|
+
class_value = merge_string_option(*extract_classes(*hashes)).presence
|
|
12
|
+
merged_data = merge_data_options(*hashes.map { |h| h[:data] || {} })
|
|
13
|
+
rest = hashes.map { |h| h.except(:class, :classes, :data) }.reduce({}, :deep_merge)
|
|
14
|
+
|
|
15
|
+
result = class_value ? rest.merge(class: class_value) : rest
|
|
16
|
+
merged_data.present? ? result.merge(data: merged_data) : result
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def extract_classes(*hashes)
|
|
20
|
+
hashes.flat_map { |h| [h[:class], h[:classes]] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
STIMULUS_SPACEJOIN_KEYS = %i[controller action].freeze
|
|
24
|
+
|
|
25
|
+
def merge_data_options(*hashes, spacejoin: STIMULUS_SPACEJOIN_KEYS)
|
|
26
|
+
hashes.reduce({}) do |acc, d|
|
|
27
|
+
acc.merge(d) do |key, old_val, new_val|
|
|
28
|
+
if spacejoin.include?(key.to_sym)
|
|
29
|
+
merge_string_option(old_val, new_val).presence || new_val
|
|
30
|
+
else
|
|
31
|
+
new_val
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def merge_string_option(*parts, delimiter: " ")
|
|
38
|
+
tokens = parts.flat_map { |part| normalize_part(part, delimiter) }
|
|
39
|
+
tokens.compact.uniq.join(delimiter)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def normalize_part(value, delimiter)
|
|
43
|
+
case value
|
|
44
|
+
when String then value.present? ? value.split(delimiter) : []
|
|
45
|
+
when Hash then value.filter_map { |key, val| key if val }
|
|
46
|
+
when Array then [merge_string_option(*value).presence]
|
|
47
|
+
else []
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module StimulusPlumbers
|
|
6
|
+
module Plumber
|
|
7
|
+
module Renderer
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
class_attribute :renderers, instance_writer: false, default: {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
16
|
+
def renders(method_name, with: nil, &block)
|
|
17
|
+
raise ArgumentError, "method_name must be Symbol" unless method_name.is_a?(Symbol)
|
|
18
|
+
raise ArgumentError, "provide either with: or a block" if !with.nil? && block_given?
|
|
19
|
+
|
|
20
|
+
with = block if block_given?
|
|
21
|
+
|
|
22
|
+
with_proc_or_symbol = with.is_a?(Proc) || with.is_a?(Symbol)
|
|
23
|
+
with_klazz = with.is_a?(Module) || with.is_a?(String)
|
|
24
|
+
raise ArgumentError, "with: must be a Symbol/Proc/Class" unless with_proc_or_symbol || with_klazz
|
|
25
|
+
|
|
26
|
+
self.renderers = renderers.merge(method_name => with)
|
|
27
|
+
ActiveSupport.version >= "7.2" ? generate_renderer_method(method_name) : eval_renderer_method(method_name)
|
|
28
|
+
end
|
|
29
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def generated_renderer_methods
|
|
34
|
+
@generated_renderer_methods ||= Module.new.tap { |mod| prepend mod }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def eval_renderer_method(method_name)
|
|
38
|
+
generated_renderer_methods.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
|
39
|
+
# def method_name(*args, **kwargs)
|
|
40
|
+
# renderer = renderers.fetch(:method_name, {})
|
|
41
|
+
#
|
|
42
|
+
# unless renderer.present?
|
|
43
|
+
# raise ArgumentError, "#method_name not found in renderer" unless defined?(super)
|
|
44
|
+
# super
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# dispatcher = StimulusPlumbers::Plumber::Dispatcher.build(
|
|
48
|
+
# renderer, *args, method_name: :#{method_name}, init_args: [template], **kwargs
|
|
49
|
+
# )
|
|
50
|
+
# raise ArgumentError, "invalid renderer, got: \#{renderer.inspect}" unless dispatcher
|
|
51
|
+
#
|
|
52
|
+
# dispatcher.call(self)
|
|
53
|
+
# end
|
|
54
|
+
#{renderer_method_template(method_name)}
|
|
55
|
+
RUBY
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def generate_renderer_method(method_name)
|
|
59
|
+
require "active_support/code_generator"
|
|
60
|
+
ActiveSupport::CodeGenerator.batch(generated_renderer_methods, __FILE__, __LINE__) do |owner|
|
|
61
|
+
owner.define_cached_method(method_name, namespace: :plumber_renderers) do |batch|
|
|
62
|
+
batch << renderer_method_template(method_name)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def renderer_method_template(method_name)
|
|
68
|
+
<<-RUBY
|
|
69
|
+
def #{method_name}(*args, **kwargs)
|
|
70
|
+
renderer = renderers.fetch(:#{method_name}, {})
|
|
71
|
+
|
|
72
|
+
unless renderer.present?
|
|
73
|
+
raise ArgumentError, "##{method_name} not found in renderer" unless defined?(super)
|
|
74
|
+
super
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
dispatcher = StimulusPlumbers::Plumber::Dispatcher.build(
|
|
78
|
+
renderer, *args, method_name: :#{method_name}, init_args: [template], **kwargs
|
|
79
|
+
)
|
|
80
|
+
raise ArgumentError, "invalid renderer, got: \#{renderer.inspect}" unless dispatcher
|
|
81
|
+
|
|
82
|
+
dispatcher.call(self)
|
|
83
|
+
end
|
|
84
|
+
RUBY
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "schema
|
|
4
|
-
require_relative "base/action_list"
|
|
5
|
-
require_relative "base/avatar"
|
|
6
|
-
require_relative "base/button"
|
|
7
|
-
require_relative "base/calendar"
|
|
8
|
-
require_relative "base/card"
|
|
9
|
-
require_relative "base/form"
|
|
10
|
-
require_relative "base/layout"
|
|
3
|
+
require_relative "schema"
|
|
11
4
|
|
|
12
5
|
module StimulusPlumbers
|
|
13
6
|
module Themes
|
|
14
7
|
class Base
|
|
15
8
|
SCHEMA = {
|
|
16
|
-
**
|
|
17
|
-
**
|
|
18
|
-
**
|
|
19
|
-
**
|
|
20
|
-
**
|
|
21
|
-
**
|
|
22
|
-
**
|
|
9
|
+
**Schema::ACTION_LIST,
|
|
10
|
+
**Schema::AVATAR,
|
|
11
|
+
**Schema::BUTTON,
|
|
12
|
+
**Schema::CALENDAR,
|
|
13
|
+
**Schema::CARD,
|
|
14
|
+
**Schema::COMBOBOX,
|
|
15
|
+
**Schema::FORM,
|
|
16
|
+
**Schema::LAYOUT
|
|
23
17
|
}.freeze
|
|
24
18
|
|
|
25
19
|
def name
|
|
26
20
|
@name ||= self.class.name.demodulize.delete_suffix("Theme")
|
|
27
21
|
end
|
|
28
22
|
|
|
23
|
+
def avatar_colors
|
|
24
|
+
{}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def avatar_color_range
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def icons
|
|
32
|
+
{}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def icon_range
|
|
36
|
+
icons.keys
|
|
37
|
+
end
|
|
38
|
+
|
|
29
39
|
def attribute_names(component)
|
|
30
40
|
SCHEMA.fetch(component, {}).keys
|
|
31
41
|
end
|
|
@@ -55,11 +65,7 @@ module StimulusPlumbers
|
|
|
55
65
|
def coerce_arg(component, key, value, schema)
|
|
56
66
|
return value unless schema
|
|
57
67
|
|
|
58
|
-
range =
|
|
59
|
-
respond_to?(schema[:range], true) ? send(schema[:range]) : []
|
|
60
|
-
else
|
|
61
|
-
schema[:range]
|
|
62
|
-
end
|
|
68
|
+
range = range_for(schema)
|
|
63
69
|
return value if range.empty? || range.include?(value)
|
|
64
70
|
|
|
65
71
|
StimulusPlumbers::Logger.warn(
|
|
@@ -68,6 +74,14 @@ module StimulusPlumbers
|
|
|
68
74
|
)
|
|
69
75
|
schema[:default]
|
|
70
76
|
end
|
|
77
|
+
|
|
78
|
+
def range_for(schema)
|
|
79
|
+
if schema[:range].is_a?(Symbol)
|
|
80
|
+
respond_to?(schema[:range], true) ? send(schema[:range]) : []
|
|
81
|
+
else
|
|
82
|
+
schema[:range]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
71
85
|
end
|
|
72
86
|
end
|
|
73
87
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
class Configuration
|
|
6
|
+
def register(name, klass)
|
|
7
|
+
raise ArgumentError, "#{klass} must be a subclass of Themes::Base" unless klass <= Base
|
|
8
|
+
|
|
9
|
+
registry[name.to_sym] = klass
|
|
10
|
+
self
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def use(name_or_instance)
|
|
14
|
+
@current = resolve(name_or_instance)
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def current
|
|
19
|
+
@current ||= Base.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def registry
|
|
23
|
+
@registry ||= {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def resolve(value)
|
|
29
|
+
return value if value.is_a?(Base)
|
|
30
|
+
|
|
31
|
+
klass = registry[value.to_sym]
|
|
32
|
+
raise ArgumentError, "Unknown theme #{value.inspect}. Registered: #{registry.keys.inspect}" unless klass
|
|
33
|
+
|
|
34
|
+
klass.new
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Schema
|
|
6
|
+
module Icon
|
|
7
|
+
DEFAULTS = {
|
|
8
|
+
fill: "none",
|
|
9
|
+
view_box: "0 0 24 24",
|
|
10
|
+
width: "24",
|
|
11
|
+
height: "24",
|
|
12
|
+
stroke: "currentColor",
|
|
13
|
+
stroke_width: 1.5,
|
|
14
|
+
stroke_linecap: :round,
|
|
15
|
+
stroke_linejoin: :round
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
ATTRS = ([:d] + DEFAULTS.keys).freeze
|
|
19
|
+
|
|
20
|
+
def self.resolve(icon_data)
|
|
21
|
+
return unless icon_data.is_a?(Hash)
|
|
22
|
+
|
|
23
|
+
merged = DEFAULTS.merge(icon_data.slice(*ATTRS)).transform_values(&:to_s)
|
|
24
|
+
return merged if merged[:d].present?
|
|
25
|
+
|
|
26
|
+
StimulusPlumbers::Logger.warn("Icon missing required :d attribute")
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -4,11 +4,11 @@ module StimulusPlumbers
|
|
|
4
4
|
module Themes
|
|
5
5
|
module Schema
|
|
6
6
|
module Ranges
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
BOOL = [true, false].freeze
|
|
8
|
+
SIZE = %i[sm md lg].freeze
|
|
9
|
+
FLEX_ALIGN = %i[left center right top bottom].freeze
|
|
10
|
+
FLEX_DIRECTION = %i[row col].freeze
|
|
11
|
+
BUTTON_VARIANT = %i[primary secondary outline destructive ghost link].freeze
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "schema/ranges"
|
|
4
|
+
require_relative "schema/form/ranges"
|
|
5
|
+
require_relative "schema/icon"
|
|
6
|
+
|
|
7
|
+
module StimulusPlumbers
|
|
8
|
+
module Themes
|
|
9
|
+
module Schema
|
|
10
|
+
ACTION_LIST = {
|
|
11
|
+
action_list_item: {
|
|
12
|
+
active: { default: false, range: Ranges::BOOL }
|
|
13
|
+
}.freeze,
|
|
14
|
+
action_list: {}.freeze
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
AVATAR = {
|
|
18
|
+
avatar: {
|
|
19
|
+
size: { default: :md, range: Ranges::SIZE },
|
|
20
|
+
color: { default: nil, range: :avatar_color_range }
|
|
21
|
+
}.freeze
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
BUTTON = {
|
|
25
|
+
button: {
|
|
26
|
+
variant: { default: :primary, range: Ranges::BUTTON_VARIANT },
|
|
27
|
+
size: { default: :md, range: Ranges::SIZE }
|
|
28
|
+
}.freeze,
|
|
29
|
+
button_group: {
|
|
30
|
+
alignment: { default: :left, range: Ranges::FLEX_ALIGN },
|
|
31
|
+
direction: { default: :row, range: Ranges::FLEX_DIRECTION }
|
|
32
|
+
}.freeze
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
CALENDAR = {
|
|
36
|
+
calendar: {}.freeze,
|
|
37
|
+
calendar_days_of_week: {}.freeze,
|
|
38
|
+
calendar_week: {}.freeze,
|
|
39
|
+
calendar_days_of_month: {}.freeze,
|
|
40
|
+
calendar_day: {
|
|
41
|
+
today: { default: false, range: Ranges::BOOL },
|
|
42
|
+
selected: { default: false, range: Ranges::BOOL },
|
|
43
|
+
outside: { default: false, range: Ranges::BOOL }
|
|
44
|
+
}.freeze,
|
|
45
|
+
calendar_navigation: {}.freeze,
|
|
46
|
+
calendar_navigation_navigator: {}.freeze,
|
|
47
|
+
calendar_navigation_navigator_icon: {
|
|
48
|
+
name: { default: "arrow-left", range: :icon_range }
|
|
49
|
+
}.freeze
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
CARD = {
|
|
53
|
+
card: {}.freeze,
|
|
54
|
+
card_section: {}.freeze
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
COMBOBOX = {
|
|
58
|
+
combobox_trigger: {}.freeze,
|
|
59
|
+
combobox_option: {
|
|
60
|
+
selected: { default: false, range: Ranges::BOOL },
|
|
61
|
+
disabled: { default: false, range: Ranges::BOOL }
|
|
62
|
+
}.freeze,
|
|
63
|
+
combobox_option_group: {}.freeze,
|
|
64
|
+
combobox_listbox: {}.freeze,
|
|
65
|
+
combobox_autocomplete_loading: {}.freeze,
|
|
66
|
+
combobox_autocomplete_empty: {}.freeze,
|
|
67
|
+
combobox_time: {}.freeze
|
|
68
|
+
}.freeze
|
|
69
|
+
|
|
70
|
+
FORM = {
|
|
71
|
+
form_group: {
|
|
72
|
+
layout: { default: :stacked, range: Form::Ranges::LAYOUT },
|
|
73
|
+
error: { default: false, range: Ranges::BOOL }
|
|
74
|
+
}.freeze,
|
|
75
|
+
form_label: {
|
|
76
|
+
required: { default: false, range: Ranges::BOOL },
|
|
77
|
+
hidden: { default: false, range: Ranges::BOOL }
|
|
78
|
+
}.freeze,
|
|
79
|
+
form_required_mark: {}.freeze,
|
|
80
|
+
form_details: {}.freeze,
|
|
81
|
+
form_error: {}.freeze,
|
|
82
|
+
form_input: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
83
|
+
form_textarea: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
84
|
+
form_file: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
85
|
+
form_select: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
86
|
+
form_checkbox: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
87
|
+
form_radio: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
88
|
+
form_input_group: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
89
|
+
form_combobox: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
90
|
+
form_input_reveal: {}.freeze,
|
|
91
|
+
form_button_reveal: {}.freeze,
|
|
92
|
+
form_submit: {
|
|
93
|
+
variant: { default: :default, range: Form::Ranges::SUBMIT_VARIANT }
|
|
94
|
+
}.freeze
|
|
95
|
+
}.freeze
|
|
96
|
+
|
|
97
|
+
LAYOUT = {
|
|
98
|
+
divider: {}.freeze,
|
|
99
|
+
popover: {}.freeze
|
|
100
|
+
}.freeze
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|