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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +60 -41
  4. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +760 -237
  5. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -1
  6. data/lib/stimulus_plumbers/components/action_list/item.rb +27 -0
  7. data/lib/stimulus_plumbers/components/action_list/section.rb +21 -0
  8. data/lib/stimulus_plumbers/components/action_list.rb +23 -0
  9. data/lib/stimulus_plumbers/components/avatar.rb +72 -0
  10. data/lib/stimulus_plumbers/components/button/group.rb +17 -0
  11. data/lib/stimulus_plumbers/components/button.rb +27 -0
  12. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_month.rb +2 -2
  13. data/lib/stimulus_plumbers/components/calendar/month/turbo/days_of_week.rb +2 -2
  14. data/lib/stimulus_plumbers/components/calendar/month/turbo.rb +55 -0
  15. data/lib/stimulus_plumbers/components/calendar.rb +33 -0
  16. data/lib/stimulus_plumbers/components/card/section.rb +25 -0
  17. data/lib/stimulus_plumbers/components/card.rb +27 -0
  18. data/lib/stimulus_plumbers/components/combobox/autocomplete.rb +53 -0
  19. data/lib/stimulus_plumbers/components/combobox/date.rb +50 -0
  20. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +38 -0
  21. data/lib/stimulus_plumbers/components/combobox/options/option.rb +34 -0
  22. data/lib/stimulus_plumbers/components/combobox/options/option_group.rb +29 -0
  23. data/lib/stimulus_plumbers/components/combobox/options.rb +59 -0
  24. data/lib/stimulus_plumbers/components/combobox/popover.rb +20 -0
  25. data/lib/stimulus_plumbers/components/combobox/time/drum.rb +37 -0
  26. data/lib/stimulus_plumbers/components/combobox/time.rb +120 -0
  27. data/lib/stimulus_plumbers/components/combobox/trigger.rb +38 -0
  28. data/lib/stimulus_plumbers/components/combobox.rb +59 -0
  29. data/lib/stimulus_plumbers/components/date_picker/navigation.rb +1 -1
  30. data/lib/stimulus_plumbers/components/date_picker/navigator.rb +1 -1
  31. data/lib/stimulus_plumbers/components/icon.rb +49 -0
  32. data/lib/stimulus_plumbers/components/popover/builder.rb +25 -0
  33. data/lib/stimulus_plumbers/components/popover.rb +26 -0
  34. data/lib/stimulus_plumbers/form/builder.rb +64 -17
  35. data/lib/stimulus_plumbers/form/{field_component.rb → field.rb} +13 -11
  36. data/lib/stimulus_plumbers/form/fields/combobox.rb +41 -0
  37. data/lib/stimulus_plumbers/form/fields/error.rb +14 -0
  38. data/lib/stimulus_plumbers/form/fields/group.rb +14 -0
  39. data/lib/stimulus_plumbers/form/fields/hint.rb +14 -0
  40. data/lib/stimulus_plumbers/form/fields/label.rb +21 -0
  41. data/lib/stimulus_plumbers/form/fields/password.rb +55 -0
  42. data/lib/stimulus_plumbers/form/fields/renderer.rb +16 -21
  43. data/lib/stimulus_plumbers/form/fields/search.rb +54 -0
  44. data/lib/stimulus_plumbers/form/fields/select.rb +8 -2
  45. data/lib/stimulus_plumbers/form/fields/submit.rb +23 -0
  46. data/lib/stimulus_plumbers/form/fields/text.rb +12 -4
  47. data/lib/stimulus_plumbers/helpers/action_list_helper.rb +2 -2
  48. data/lib/stimulus_plumbers/helpers/avatar_helper.rb +2 -2
  49. data/lib/stimulus_plumbers/helpers/button_helper.rb +2 -2
  50. data/lib/stimulus_plumbers/helpers/calendar_helper.rb +1 -1
  51. data/lib/stimulus_plumbers/helpers/calendar_turbo_helper.rb +1 -1
  52. data/lib/stimulus_plumbers/helpers/card_helper.rb +2 -2
  53. data/lib/stimulus_plumbers/helpers/combobox_helper.rb +74 -0
  54. data/lib/stimulus_plumbers/helpers/popover_helper.rb +2 -2
  55. data/lib/stimulus_plumbers/helpers.rb +2 -2
  56. data/lib/stimulus_plumbers/plumber/base.rb +20 -0
  57. data/lib/stimulus_plumbers/plumber/dispatcher.rb +111 -0
  58. data/lib/stimulus_plumbers/plumber/html_options.rb +51 -0
  59. data/lib/stimulus_plumbers/plumber/renderer.rb +89 -0
  60. data/lib/stimulus_plumbers/themes/base.rb +9 -15
  61. data/lib/stimulus_plumbers/themes/schema/ranges.rb +5 -5
  62. data/lib/stimulus_plumbers/themes/schema.rb +97 -0
  63. data/lib/stimulus_plumbers/themes/tailwind/calendar.rb +48 -2
  64. data/lib/stimulus_plumbers/themes/tailwind/combobox.rb +75 -0
  65. data/lib/stimulus_plumbers/themes/tailwind/form.rb +10 -6
  66. data/lib/stimulus_plumbers/themes/tailwind_theme.rb +2 -0
  67. data/lib/stimulus_plumbers/version.rb +1 -1
  68. data/lib/stimulus_plumbers.rb +41 -14
  69. metadata +42 -23
  70. data/lib/stimulus_plumbers/components/action_list/renderer.rb +0 -47
  71. data/lib/stimulus_plumbers/components/avatar/renderer.rb +0 -74
  72. data/lib/stimulus_plumbers/components/button/renderer.rb +0 -33
  73. data/lib/stimulus_plumbers/components/calendar/month/turbo/renderer.rb +0 -57
  74. data/lib/stimulus_plumbers/components/calendar/renderer.rb +0 -35
  75. data/lib/stimulus_plumbers/components/card/renderer.rb +0 -41
  76. data/lib/stimulus_plumbers/components/date_picker/renderer.rb +0 -82
  77. data/lib/stimulus_plumbers/components/icon/renderer.rb +0 -51
  78. data/lib/stimulus_plumbers/components/plumber/base.rb +0 -22
  79. data/lib/stimulus_plumbers/components/plumber/dispatcher.rb +0 -113
  80. data/lib/stimulus_plumbers/components/plumber/html_options.rb +0 -34
  81. data/lib/stimulus_plumbers/components/plumber/renderer.rb +0 -91
  82. data/lib/stimulus_plumbers/components/popover/renderer.rb +0 -46
  83. data/lib/stimulus_plumbers/helpers/date_picker_helper.rb +0 -17
  84. data/lib/stimulus_plumbers/themes/action_list.rb +0 -14
  85. data/lib/stimulus_plumbers/themes/avatar.rb +0 -14
  86. data/lib/stimulus_plumbers/themes/button.rb +0 -18
  87. data/lib/stimulus_plumbers/themes/calendar.rb +0 -15
  88. data/lib/stimulus_plumbers/themes/card.rb +0 -12
  89. data/lib/stimulus_plumbers/themes/form.rb +0 -30
  90. 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/ranges"
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
- **ActionList::SCHEMA,
17
- **Avatar::SCHEMA,
18
- **Button::SCHEMA,
19
- **Calendar::SCHEMA,
20
- **Card::SCHEMA,
21
- **Form::SCHEMA,
22
- **Layout::SCHEMA
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
- BOOL_RANGE = [true, false].freeze
8
- SIZE_RANGE = %i[sm md lg].freeze
9
- ALIGN_RANGE = %i[left center right top bottom].freeze
10
- DIR_RANGE = %i[row col].freeze
11
- LAYOUT_RANGE = %i[stacked inline].freeze
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
- BASE = %w[
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
- *BASE,
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
- ACTOR_BASE = %w[flex items-center overflow-hidden rounded-md border].freeze
25
- ACTOR_BORDER = { error: "border-red-700", default: "border-gray-500" }.freeze
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 form_actor_classes(error: false)
82
- { classes: klasses(*ACTOR_BASE, ACTOR_BORDER[error ? :error : :default]) }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusPlumbers
4
- VERSION = "0.2.7"
4
+ VERSION = "0.2.9"
5
5
  end