stimulus_plumbers 0.2.7 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -0
- data/README.md +60 -41
- data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +760 -237
- 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 +72 -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 +2 -2
- data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +2 -2
- data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +55 -0
- data/lib/stimulus_plumbers/components/calendar.rb +33 -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 +53 -0
- data/lib/stimulus_plumbers/components/combobox/date.rb +50 -0
- data/lib/stimulus_plumbers/components/combobox/dropdown.rb +38 -0
- 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 +120 -0
- 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 +1 -1
- data/lib/stimulus_plumbers/components/date_picker/navigator.rb +1 -1
- data/lib/stimulus_plumbers/components/icon.rb +49 -0
- data/lib/stimulus_plumbers/components/popover/builder.rb +25 -0
- data/lib/stimulus_plumbers/components/popover.rb +26 -0
- data/lib/stimulus_plumbers/form/builder.rb +64 -17
- data/lib/stimulus_plumbers/form/{field_component.rb → field.rb} +13 -11
- data/lib/stimulus_plumbers/form/fields/combobox.rb +41 -0
- 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/password.rb +55 -0
- data/lib/stimulus_plumbers/form/fields/renderer.rb +16 -21
- data/lib/stimulus_plumbers/form/fields/search.rb +54 -0
- data/lib/stimulus_plumbers/form/fields/select.rb +8 -2
- data/lib/stimulus_plumbers/form/fields/submit.rb +23 -0
- data/lib/stimulus_plumbers/form/fields/text.rb +12 -4
- 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 +74 -0
- data/lib/stimulus_plumbers/helpers/popover_helper.rb +2 -2
- data/lib/stimulus_plumbers/helpers.rb +2 -2
- data/lib/stimulus_plumbers/plumber/base.rb +20 -0
- data/lib/stimulus_plumbers/plumber/dispatcher.rb +111 -0
- data/lib/stimulus_plumbers/plumber/html_options.rb +51 -0
- data/lib/stimulus_plumbers/plumber/renderer.rb +89 -0
- data/lib/stimulus_plumbers/themes/base.rb +9 -15
- data/lib/stimulus_plumbers/themes/schema/ranges.rb +5 -5
- data/lib/stimulus_plumbers/themes/schema.rb +97 -0
- data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +48 -2
- data/lib/stimulus_plumbers/themes/tailwind/combobox.rb +75 -0
- data/lib/stimulus_plumbers/themes/tailwind/form.rb +10 -6
- data/lib/stimulus_plumbers/themes/tailwind_theme.rb +2 -0
- data/lib/stimulus_plumbers/version.rb +1 -1
- data/lib/stimulus_plumbers.rb +41 -14
- metadata +42 -23
- 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/date_picker/renderer.rb +0 -82
- 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 -34
- data/lib/stimulus_plumbers/components/plumber/renderer.rb +0 -91
- data/lib/stimulus_plumbers/components/popover/renderer.rb +0 -46
- data/lib/stimulus_plumbers/helpers/date_picker_helper.rb +0 -17
- data/lib/stimulus_plumbers/themes/action_list.rb +0 -14
- data/lib/stimulus_plumbers/themes/avatar.rb +0 -14
- data/lib/stimulus_plumbers/themes/button.rb +0 -18
- data/lib/stimulus_plumbers/themes/calendar.rb +0 -15
- data/lib/stimulus_plumbers/themes/card.rb +0 -12
- data/lib/stimulus_plumbers/themes/form.rb +0 -30
- data/lib/stimulus_plumbers/themes/layout.rb +0 -12
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module StimulusPlumbers
|
|
6
|
+
module Plumber
|
|
7
|
+
module Dispatcher
|
|
8
|
+
class MethodCall
|
|
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
|
+
accepts_args = method_call.arity.negative? ? args : args.take(method_call.arity)
|
|
23
|
+
accepts_kwargs = method_call.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
|
|
24
|
+
accepts_kwargs ? method_call.call(*accepts_args, **kwargs) : method_call.call(*accepts_args)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def validate!
|
|
30
|
+
return if method_name.is_a?(String) || method_name.is_a?(Symbol)
|
|
31
|
+
|
|
32
|
+
raise ArgumentError, "invalid method name: #{method_name.inspect}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class InstanceExec
|
|
37
|
+
attr_reader :block, :args, :kwargs
|
|
38
|
+
|
|
39
|
+
def initialize(block, *args, **kwargs)
|
|
40
|
+
@block = block
|
|
41
|
+
@args = args
|
|
42
|
+
@kwargs = kwargs
|
|
43
|
+
validate!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def call(target)
|
|
47
|
+
accepts_args = block.arity.negative? ? args : args.take(block.arity)
|
|
48
|
+
accepts_kwargs = block.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
|
|
49
|
+
if accepts_kwargs
|
|
50
|
+
target.instance_exec(
|
|
51
|
+
*accepts_args,
|
|
52
|
+
**kwargs,
|
|
53
|
+
&block
|
|
54
|
+
)
|
|
55
|
+
else
|
|
56
|
+
target.instance_exec(*accepts_args, &block)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def validate!
|
|
63
|
+
raise ArgumentError, "invalid block: #{block.inspect}" unless block.is_a?(Proc)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class KlassProxy
|
|
68
|
+
attr_reader :klass, :method_name, :args, :kwargs, :init_args, :init_kwargs
|
|
69
|
+
|
|
70
|
+
def initialize(klass, method_name, *args, init_args: [], init_kwargs: {}, **kwargs)
|
|
71
|
+
@klass = klass
|
|
72
|
+
@method_name = method_name
|
|
73
|
+
@args = args
|
|
74
|
+
@kwargs = kwargs
|
|
75
|
+
@init_args = init_args
|
|
76
|
+
@init_kwargs = init_kwargs
|
|
77
|
+
validate!
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def call(_target)
|
|
81
|
+
klass.new(*init_args, **init_kwargs).public_send(method_name, *args, **kwargs)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def validate!
|
|
87
|
+
raise ArgumentError, "invalid class: #{klass.inspect}" unless klass.is_a?(Module)
|
|
88
|
+
return if method_name.is_a?(String) || method_name.is_a?(Symbol)
|
|
89
|
+
|
|
90
|
+
raise ArgumentError, "invalid method name: #{method_name.inspect}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.build(callable, *args, method_name: nil, init_args: [], init_kwargs: {}, **kwargs)
|
|
95
|
+
case callable
|
|
96
|
+
when Symbol
|
|
97
|
+
MethodCall.new(callable, *args, **kwargs)
|
|
98
|
+
when Proc
|
|
99
|
+
InstanceExec.new(callable, *args, **kwargs)
|
|
100
|
+
when Module
|
|
101
|
+
KlassProxy.new(callable, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs)
|
|
102
|
+
when String
|
|
103
|
+
klass = callable.safe_constantize
|
|
104
|
+
raise ArgumentError, "could not resolve class from: #{callable.inspect}" unless klass
|
|
105
|
+
|
|
106
|
+
KlassProxy.new(klass, method_name, *args, init_args: init_args, init_kwargs: init_kwargs, **kwargs)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
classes = hashes.flat_map { |h| [h[:class], h[:classes]] }
|
|
12
|
+
data_hashes = hashes.map { |h| h[:data] || {} }
|
|
13
|
+
rest = hashes.map { |h| h.except(:class, :classes, :data) }.reduce({}, :deep_merge)
|
|
14
|
+
|
|
15
|
+
class_value = merge_string_option(*classes).presence
|
|
16
|
+
merged_data = merge_data_options(*data_hashes)
|
|
17
|
+
|
|
18
|
+
result = class_value ? rest.merge(class: class_value) : rest
|
|
19
|
+
merged_data.present? ? result.merge(data: merged_data) : result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
STIMULUS_SPACEJOIN_KEYS = %i[controller action].freeze
|
|
23
|
+
|
|
24
|
+
def merge_data_options(*hashes, spacejoin: STIMULUS_SPACEJOIN_KEYS)
|
|
25
|
+
hashes.reduce({}) do |acc, d|
|
|
26
|
+
acc.merge(d) do |key, old_val, new_val|
|
|
27
|
+
if spacejoin.include?(key.to_sym)
|
|
28
|
+
merge_string_option(old_val, new_val).presence || new_val
|
|
29
|
+
else
|
|
30
|
+
new_val
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def merge_string_option(*parts, delimiter: " ")
|
|
37
|
+
tokens = parts.flat_map { |part| normalize_part(part, delimiter) }
|
|
38
|
+
tokens.compact.uniq.join(delimiter)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def normalize_part(value, delimiter)
|
|
42
|
+
case value
|
|
43
|
+
when String then value.present? ? value.split(delimiter) : []
|
|
44
|
+
when Hash then value.filter_map { |key, val| key if val }
|
|
45
|
+
when Array then [merge_string_option(*value).presence]
|
|
46
|
+
else []
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
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/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/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,25 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "schema
|
|
4
|
-
require_relative "action_list"
|
|
5
|
-
require_relative "avatar"
|
|
6
|
-
require_relative "button"
|
|
7
|
-
require_relative "calendar"
|
|
8
|
-
require_relative "card"
|
|
9
|
-
require_relative "form"
|
|
10
|
-
require_relative "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
|
|
@@ -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
|
+
FORM_LAYOUT = %i[stacked inline].freeze
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "schema/ranges"
|
|
4
|
+
|
|
5
|
+
module StimulusPlumbers
|
|
6
|
+
module Themes
|
|
7
|
+
module Schema
|
|
8
|
+
ACTION_LIST = {
|
|
9
|
+
action_list_item: {
|
|
10
|
+
active: { default: false, range: Ranges::BOOL }
|
|
11
|
+
}.freeze,
|
|
12
|
+
action_list: {}.freeze
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
AVATAR = {
|
|
16
|
+
avatar: {
|
|
17
|
+
size: { default: :md, range: Ranges::SIZE },
|
|
18
|
+
color: { default: nil, range: :avatar_color_range }
|
|
19
|
+
}.freeze
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
BUTTON = {
|
|
23
|
+
button: {
|
|
24
|
+
variant: { default: :primary, range: %i[primary secondary outline destructive ghost link].freeze },
|
|
25
|
+
size: { default: :md, range: Ranges::SIZE }
|
|
26
|
+
}.freeze,
|
|
27
|
+
button_group: {
|
|
28
|
+
alignment: { default: :left, range: Ranges::FLEX_ALIGN },
|
|
29
|
+
direction: { default: :row, range: Ranges::FLEX_DIRECTION }
|
|
30
|
+
}.freeze
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
CALENDAR = {
|
|
34
|
+
calendar: {}.freeze,
|
|
35
|
+
calendar_days_of_week: {}.freeze,
|
|
36
|
+
calendar_days_of_month: {}.freeze,
|
|
37
|
+
calendar_day: {
|
|
38
|
+
today: { default: false, range: Ranges::BOOL },
|
|
39
|
+
selected: { default: false, range: Ranges::BOOL },
|
|
40
|
+
outside: { default: false, range: Ranges::BOOL }
|
|
41
|
+
}.freeze,
|
|
42
|
+
calendar_navigation: {}.freeze,
|
|
43
|
+
calendar_navigation_navigator: {}.freeze,
|
|
44
|
+
calendar_navigation_navigator_icon: {}.freeze
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
CARD = {
|
|
48
|
+
card: {}.freeze,
|
|
49
|
+
card_section: {}.freeze
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
COMBOBOX = {
|
|
53
|
+
combobox_option: {
|
|
54
|
+
selected: { default: false, range: Ranges::BOOL },
|
|
55
|
+
disabled: { default: false, range: Ranges::BOOL }
|
|
56
|
+
}.freeze,
|
|
57
|
+
combobox_option_group: {}.freeze,
|
|
58
|
+
combobox_listbox: {}.freeze,
|
|
59
|
+
combobox_autocomplete_loading: {}.freeze,
|
|
60
|
+
combobox_autocomplete_empty: {}.freeze,
|
|
61
|
+
combobox_time: {}.freeze
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
FORM = {
|
|
65
|
+
form_group: {
|
|
66
|
+
layout: { default: :stacked, range: Ranges::FORM_LAYOUT },
|
|
67
|
+
error: { default: false, range: Ranges::BOOL }
|
|
68
|
+
}.freeze,
|
|
69
|
+
form_label: {
|
|
70
|
+
required: { default: false, range: Ranges::BOOL },
|
|
71
|
+
hidden: { default: false, range: Ranges::BOOL }
|
|
72
|
+
}.freeze,
|
|
73
|
+
form_required_mark: {}.freeze,
|
|
74
|
+
form_details: {}.freeze,
|
|
75
|
+
form_error: {}.freeze,
|
|
76
|
+
form_input: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
77
|
+
form_textarea: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
78
|
+
form_file: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
79
|
+
form_select: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
80
|
+
form_checkbox: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
81
|
+
form_radio: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
82
|
+
form_input_group: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
83
|
+
form_combobox: { error: { default: false, range: Ranges::BOOL } }.freeze,
|
|
84
|
+
form_input_reveal: {}.freeze,
|
|
85
|
+
form_button_reveal: {}.freeze,
|
|
86
|
+
form_submit: {
|
|
87
|
+
variant: { default: :default, range: %i[default button].freeze }
|
|
88
|
+
}.freeze
|
|
89
|
+
}.freeze
|
|
90
|
+
|
|
91
|
+
LAYOUT = {
|
|
92
|
+
divider: {}.freeze,
|
|
93
|
+
popover: {}.freeze
|
|
94
|
+
}.freeze
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -4,7 +4,16 @@ module StimulusPlumbers
|
|
|
4
4
|
module Themes
|
|
5
5
|
module Tailwind
|
|
6
6
|
module Calendar
|
|
7
|
-
|
|
7
|
+
GRID = %w[w-full].freeze
|
|
8
|
+
|
|
9
|
+
DAYS_OF_WEEK = %w[
|
|
10
|
+
grid grid-cols-7 text-center text-[--sp-text-xs]
|
|
11
|
+
font-medium text-[--sp-color-muted-fg] mb-1
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
DAYS_OF_MONTH = %w[grid grid-cols-7].freeze
|
|
15
|
+
|
|
16
|
+
DAY = %w[
|
|
8
17
|
size-[--sp-calendar-day-size] rounded-[--sp-radius-md]
|
|
9
18
|
flex items-center justify-center text-[--sp-text-sm]
|
|
10
19
|
hover:bg-[--sp-color-muted] cursor-pointer
|
|
@@ -16,18 +25,55 @@ module StimulusPlumbers
|
|
|
16
25
|
hover:bg-[--sp-color-primary]/90
|
|
17
26
|
].freeze
|
|
18
27
|
|
|
28
|
+
NAV = %w[flex items-center justify-between gap-1 mb-2].freeze
|
|
29
|
+
|
|
30
|
+
NAV_BTN = %w[
|
|
31
|
+
inline-flex items-center justify-center
|
|
32
|
+
size-[--sp-calendar-day-size] rounded-[--sp-radius-md]
|
|
33
|
+
text-[--sp-color-fg] hover:bg-[--sp-color-muted]
|
|
34
|
+
focus-visible:outline-none focus-visible:ring-2
|
|
35
|
+
focus-visible:ring-[--sp-focus-ring-color]
|
|
36
|
+
disabled:pointer-events-none disabled:opacity-50
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
NAV_ICON = %w[size-4 stroke-current].freeze
|
|
40
|
+
|
|
19
41
|
private
|
|
20
42
|
|
|
43
|
+
def calendar_classes
|
|
44
|
+
{ classes: klasses(*GRID) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def calendar_days_of_week_classes
|
|
48
|
+
{ classes: klasses(*DAYS_OF_WEEK) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def calendar_days_of_month_classes
|
|
52
|
+
{ classes: klasses(*DAYS_OF_MONTH) }
|
|
53
|
+
end
|
|
54
|
+
|
|
21
55
|
def calendar_day_classes(today: false, selected: false, outside: false)
|
|
22
56
|
{
|
|
23
57
|
classes: klasses(
|
|
24
|
-
*
|
|
58
|
+
*DAY,
|
|
25
59
|
*(today ? ["font-bold"] : []),
|
|
26
60
|
*(selected ? DAY_SELECTED : []),
|
|
27
61
|
*(outside ? %w[text-[--sp-color-muted-fg] opacity-50] : [])
|
|
28
62
|
)
|
|
29
63
|
}
|
|
30
64
|
end
|
|
65
|
+
|
|
66
|
+
def calendar_navigation_classes
|
|
67
|
+
{ classes: klasses(*NAV) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def calendar_navigation_navigator_classes
|
|
71
|
+
{ classes: klasses(*NAV_BTN) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def calendar_navigation_navigator_icon_classes
|
|
75
|
+
{ classes: klasses(*NAV_ICON) }
|
|
76
|
+
end
|
|
31
77
|
end
|
|
32
78
|
end
|
|
33
79
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPlumbers
|
|
4
|
+
module Themes
|
|
5
|
+
module Tailwind
|
|
6
|
+
module Combobox
|
|
7
|
+
LISTBOX = %w[
|
|
8
|
+
py-[--sp-space-1] overflow-y-auto max-h-60
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
OPTION_BASE = %w[
|
|
12
|
+
flex items-center gap-[--sp-space-2] w-full
|
|
13
|
+
px-[--sp-space-2] py-[--sp-space-1]
|
|
14
|
+
rounded-[--sp-radius-sm] text-[--sp-text-sm]
|
|
15
|
+
cursor-pointer select-none outline-none
|
|
16
|
+
hover:bg-[--sp-color-muted] focus:bg-[--sp-color-muted]
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
OPTION_SELECTED = %w[
|
|
20
|
+
bg-[--sp-color-primary]/10 text-[--sp-color-primary]
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
OPTION_DISABLED = %w[
|
|
24
|
+
opacity-50 cursor-not-allowed pointer-events-none
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
OPTION_GROUP = %w[py-[--sp-space-1]].freeze
|
|
28
|
+
|
|
29
|
+
AUTOCOMPLETE_LOADING = %w[
|
|
30
|
+
flex items-center justify-center
|
|
31
|
+
py-[--sp-space-2] text-[--sp-text-sm] text-[--sp-color-muted-fg]
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
AUTOCOMPLETE_EMPTY = %w[
|
|
35
|
+
flex items-center justify-center
|
|
36
|
+
py-[--sp-space-2] text-[--sp-text-sm] text-[--sp-color-muted-fg]
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
TIME = %w[flex gap-[--sp-space-2] overflow-hidden].freeze
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def combobox_listbox_classes
|
|
44
|
+
{ classes: klasses(*LISTBOX) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def combobox_option_classes(selected: false, disabled: false)
|
|
48
|
+
{
|
|
49
|
+
classes: klasses(
|
|
50
|
+
*OPTION_BASE,
|
|
51
|
+
*(selected ? OPTION_SELECTED : []),
|
|
52
|
+
*(disabled ? OPTION_DISABLED : [])
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def combobox_option_group_classes
|
|
58
|
+
{ classes: klasses(*OPTION_GROUP) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def combobox_autocomplete_loading_classes
|
|
62
|
+
{ classes: klasses(*AUTOCOMPLETE_LOADING) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def combobox_autocomplete_empty_classes
|
|
66
|
+
{ classes: klasses(*AUTOCOMPLETE_EMPTY) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def combobox_time_classes
|
|
70
|
+
{ classes: klasses(*TIME) }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -21,8 +21,8 @@ module StimulusPlumbers
|
|
|
21
21
|
CHECKBOX = %w[size-4 rounded border-gray-500 text-blue-700].freeze
|
|
22
22
|
RADIO = %w[size-4 border-gray-500 text-blue-700].freeze
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
INPUT_GROUP_BASE = %w[flex items-center overflow-hidden rounded-md border].freeze
|
|
25
|
+
INPUT_GROUP_BORDER = { error: "border-red-700", default: "border-gray-500" }.freeze
|
|
26
26
|
|
|
27
27
|
INPUT_REVEAL = %w[
|
|
28
28
|
flex-1 border-0 bg-transparent px-3 py-2 text-sm text-gray-900 focus:outline-none
|
|
@@ -38,8 +38,8 @@ module StimulusPlumbers
|
|
|
38
38
|
{ classes: klasses(*GROUP_BASE, layout == :inline ? GROUP_INLINE : "flex-col") }
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def form_label_classes(**)
|
|
42
|
-
{ classes: klasses(*LABEL) }
|
|
41
|
+
def form_label_classes(hidden: false, **)
|
|
42
|
+
{ classes: klasses(*LABEL, hidden ? "sr-only" : nil) }
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def form_required_mark_classes
|
|
@@ -78,8 +78,12 @@ module StimulusPlumbers
|
|
|
78
78
|
{ classes: klasses(*RADIO) }
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
def
|
|
82
|
-
{ classes: klasses(*
|
|
81
|
+
def form_input_group_classes(error: false)
|
|
82
|
+
{ classes: klasses(*INPUT_GROUP_BASE, INPUT_GROUP_BORDER[error ? :error : :default]) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def form_combobox_classes(error: false)
|
|
86
|
+
form_input_classes(error: error)
|
|
83
87
|
end
|
|
84
88
|
|
|
85
89
|
def form_input_reveal_classes
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "tailwind/action_list"
|
|
4
|
+
require_relative "tailwind/combobox"
|
|
4
5
|
require_relative "tailwind/avatar"
|
|
5
6
|
require_relative "tailwind/button"
|
|
6
7
|
require_relative "tailwind/calendar"
|
|
@@ -12,6 +13,7 @@ module StimulusPlumbers
|
|
|
12
13
|
module Themes
|
|
13
14
|
class TailwindTheme < Base
|
|
14
15
|
include Tailwind::ActionList
|
|
16
|
+
include Tailwind::Combobox
|
|
15
17
|
include Tailwind::Avatar
|
|
16
18
|
include Tailwind::Button
|
|
17
19
|
include Tailwind::Calendar
|