super 0.0.7 → 0.0.12

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +0 -6
  3. data/CONTRIBUTING.md +56 -0
  4. data/README.md +49 -61
  5. data/Rakefile +16 -14
  6. data/app/assets/javascripts/super/application.js +297 -97
  7. data/app/assets/stylesheets/super/application.css +5600 -0
  8. data/app/controllers/super/application_controller.rb +20 -14
  9. data/app/helpers/super/form_builder_helper.rb +25 -0
  10. data/app/views/layouts/super/application.html.erb +23 -9
  11. data/app/views/super/application/{_super_schema_display_actions.html.erb → _display_actions.html.erb} +0 -0
  12. data/app/views/super/application/{_super_schema_display_index.html.erb → _display_index.html.erb} +6 -6
  13. data/app/views/super/application/_display_rich_text.html.erb +1 -0
  14. data/app/views/super/application/_display_show.html.erb +8 -0
  15. data/app/views/super/application/_filter.html.erb +5 -13
  16. data/app/views/super/application/_filter_type_select.html.erb +9 -19
  17. data/app/views/super/application/_filter_type_text.html.erb +7 -11
  18. data/app/views/super/application/_filter_type_timestamp.html.erb +6 -17
  19. data/app/views/super/application/_form.html.erb +15 -0
  20. data/app/views/super/application/_form_field__destroy.html.erb +1 -9
  21. data/app/views/super/application/_form_field_checkbox.html.erb +1 -0
  22. data/app/views/super/application/_form_field_rich_text_area.html.erb +1 -0
  23. data/app/views/super/application/_form_field_select.html.erb +1 -23
  24. data/app/views/super/application/_form_field_text.html.erb +1 -13
  25. data/app/views/super/application/{_super_layout.html.erb → _layout.html.erb} +7 -7
  26. data/app/views/super/application/{_super_pagination.html.erb → _pagination.html.erb} +1 -1
  27. data/app/views/super/application/{_super_panel.html.erb → _panel.html.erb} +2 -2
  28. data/app/views/super/application/_query.html.erb +18 -0
  29. data/app/views/super/application/_sort.html.erb +18 -0
  30. data/app/views/super/application/_sort_expression.html.erb +25 -0
  31. data/app/views/super/application/edit.html.erb +1 -0
  32. data/app/views/super/application/index.html.erb +1 -0
  33. data/app/views/super/application/new.html.erb +1 -0
  34. data/app/views/super/application/show.html.erb +1 -0
  35. data/app/views/super/feather/README.md +1 -0
  36. data/app/views/super/feather/_x.html +15 -0
  37. data/config/routes.rb +2 -0
  38. data/docs/cheat.md +8 -8
  39. data/frontend/super-frontend/dist/application.css +5600 -0
  40. data/frontend/super-frontend/dist/application.js +297 -97
  41. data/lib/generators/super/action_text/USAGE +23 -0
  42. data/lib/generators/super/action_text/action_text_generator.rb +32 -0
  43. data/lib/generators/super/action_text/templates/pack_super_action_text.css +23 -0
  44. data/lib/generators/super/action_text/templates/pack_super_action_text.js +4 -0
  45. data/lib/generators/super/install/install_generator.rb +18 -7
  46. data/lib/generators/super/install/templates/base_controller.rb.tt +9 -1
  47. data/lib/generators/super/install/templates/initializer.rb.tt +9 -2
  48. data/lib/generators/super/resource/resource_generator.rb +107 -30
  49. data/lib/generators/super/resource/templates/resources_controller.rb.tt +3 -39
  50. data/lib/generators/super/webpacker/USAGE +5 -4
  51. data/lib/generators/super/webpacker/webpacker_generator.rb +5 -2
  52. data/lib/super.rb +17 -0
  53. data/lib/super/action_inquirer.rb +2 -0
  54. data/lib/super/assets.rb +114 -38
  55. data/lib/super/client_error.rb +2 -0
  56. data/lib/super/compatibility.rb +2 -0
  57. data/lib/super/configuration.rb +16 -79
  58. data/lib/super/controls.rb +11 -25
  59. data/lib/super/controls/optional.rb +75 -16
  60. data/lib/super/controls/steps.rb +36 -58
  61. data/lib/super/controls/view.rb +55 -0
  62. data/lib/super/display.rb +88 -0
  63. data/lib/super/display/guesser.rb +36 -0
  64. data/lib/super/display/schema_types.rb +80 -78
  65. data/lib/super/engine.rb +5 -1
  66. data/lib/super/error.rb +21 -0
  67. data/lib/super/filter.rb +7 -130
  68. data/lib/super/filter/form_object.rb +94 -0
  69. data/lib/super/filter/guesser.rb +32 -0
  70. data/lib/super/filter/operator.rb +2 -0
  71. data/lib/super/filter/schema_types.rb +3 -7
  72. data/lib/super/form.rb +29 -40
  73. data/lib/super/form/builder.rb +206 -0
  74. data/lib/super/form/guesser.rb +29 -0
  75. data/lib/super/form/inline_errors.rb +28 -0
  76. data/lib/super/form/schema_types.rb +31 -29
  77. data/lib/super/form/strong_params.rb +31 -0
  78. data/lib/super/layout.rb +3 -1
  79. data/lib/super/link.rb +7 -0
  80. data/lib/super/navigation/automatic.rb +4 -2
  81. data/lib/super/pagination.rb +13 -17
  82. data/lib/super/panel.rb +3 -1
  83. data/lib/super/partial.rb +2 -0
  84. data/lib/super/partial/resolving.rb +2 -0
  85. data/lib/super/plugin.rb +2 -0
  86. data/lib/super/query/form_object.rb +48 -0
  87. data/lib/super/schema.rb +2 -25
  88. data/lib/super/schema/common.rb +27 -0
  89. data/lib/super/schema/guesser.rb +79 -0
  90. data/lib/super/sort.rb +110 -0
  91. data/lib/super/useful/builder.rb +25 -0
  92. data/lib/super/useful/enum.rb +63 -0
  93. data/lib/super/version.rb +3 -1
  94. data/lib/super/view_helper.rb +2 -19
  95. metadata +63 -34
  96. data/app/views/super/application/_form_inline_errors.html.erb +0 -10
  97. data/app/views/super/application/_super_schema_display_show.html.erb +0 -8
  98. data/app/views/super/application/_super_schema_form.html.erb +0 -15
  99. data/docs/README.md +0 -6
  100. data/docs/faq.md +0 -44
  101. data/docs/quick_start.md +0 -45
  102. data/docs/webpacker.md +0 -17
  103. data/docs/yard_customizations.rb +0 -41
  104. data/frontend/super-frontend/build.js +0 -36
  105. data/frontend/super-frontend/package.json +0 -21
  106. data/frontend/super-frontend/postcss.config.js +0 -6
  107. data/frontend/super-frontend/src/javascripts/super/application.ts +0 -18
  108. data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +0 -19
  109. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +0 -1
  110. data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +0 -15
  111. data/frontend/super-frontend/src/stylesheets/super/application.css +0 -77
  112. data/frontend/super-frontend/tailwind.config.js +0 -15
  113. data/frontend/super-frontend/tsconfig.json +0 -13
  114. data/frontend/super-frontend/yarn.lock +0 -5448
  115. data/lib/super/controls/required.rb +0 -41
data/lib/super/engine.rb CHANGED
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  # Configures the host Rails app to work with Super
3
5
  class Engine < ::Rails::Engine
6
+ isolate_namespace Super
7
+
4
8
  initializer "super.assets.precompile" do |app|
5
- if Super::Assets.sprockets_available?
9
+ if Super::Assets::Handler.sprockets_available?
6
10
  app.config.assets.precompile << "config/super_manifest.js"
7
11
  end
8
12
  end
data/lib/super/error.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  # A container class for all internal errors thrown by this library
3
5
  #
@@ -13,5 +15,24 @@ module Super
13
15
  class ActionInquirerError < Error; end
14
16
  # Error raised when a `Super::Link` couldn't be found
15
17
  class LinkNotRegistered < Error; end
18
+ # Error raised when rendering if `@view` wasn't set by the controller
19
+ class NothingToRender < Error
20
+ def initialize(basename)
21
+ super(
22
+ "Super's built-in `#{basename}.html.erb` requires `@view` to be set " \
23
+ "by the controller, but it wasn't set"
24
+ )
25
+ end
26
+ end
27
+ # Error raised when something wasn't initalized correctly, and if there isn't
28
+ # a more specific error
29
+ class Initalization < Error; end
30
+ class ArgumentError < Error; end
31
+
32
+ class Enum < Error
33
+ class ImpossibleValue < Enum; end
34
+ class ArgumentError < Enum; end
35
+ class UndefinedCase < Enum; end
36
+ end
16
37
  end
17
38
  end
data/lib/super/filter.rb CHANGED
@@ -1,137 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  class Filter
3
- module ControllerMethods
4
- def index
5
- super
6
- @filter_form = controls.initialize_filtering(params: params, query_params: request.GET)
7
- @records = controls.filter_records(filter_form: @filter_form, records: @records)
8
- @view.asides.push(:@filter_form)
9
- end
10
- end
11
-
12
- class FilterFormField
13
- def initialize(humanized_field_name:, field_name:, type:, params:)
14
- @humanized_field_name = humanized_field_name
15
- @field_name = field_name
16
- @field_type = type
17
- @params = params
18
- @specified_values =
19
- type.q
20
- .map do |query_field_name|
21
- [
22
- query_field_name,
23
- (params || {})[query_field_name],
24
- ]
25
- end
26
- .to_h
27
-
28
- @specified_values.each do |key, value|
29
- define_singleton_method(key) { value }
30
- end
31
- end
32
-
33
- attr_reader :humanized_field_name
34
- attr_reader :field_name
35
- attr_reader :field_type
36
- attr_reader :specified_values
37
-
38
- def op
39
- (@params || {})[:op]
40
- end
41
-
42
- def operators
43
- @field_type.operators
44
- .map { |o| [o.name, o.identifier] }
45
- .to_h
46
- end
47
-
48
- def to_partial_path
49
- @field_type.to_partial_path
50
- end
51
- end
52
-
53
- def initialize(model:, url:, schema:, params:)
54
- @model = model
55
- @url = url
56
- @schema = schema
57
- @params = params
58
-
59
- @form_fields = {}
60
- end
61
-
62
- def each_field
63
- @schema.fields.each do |field_name, _field_type|
64
- yield(form_field_for(field_name))
65
- end
66
- end
67
-
68
- def url
69
- @url
70
- end
71
-
72
- def to_partial_path
73
- "filter"
74
- end
75
-
76
- def to_search_query(relation)
77
- each_field do |form_field|
78
- next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
79
- next if !Super::Filter::Operator.registry.key?(form_field.op)
5
+ def initialize
6
+ @schema_type = Filter::SchemaTypes.new
7
+ @fields = Schema::Fields.new
80
8
 
81
- operator = Super::Filter::Operator.registry[form_field.op]
82
- updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
83
-
84
- if updated_relation.is_a?(ActiveRecord::Relation)
85
- relation = updated_relation
86
- end
87
- end
88
-
89
- relation
9
+ yield(@fields, @schema_type)
90
10
  end
91
11
 
92
- private
93
-
94
- def form_field_for(field_name)
95
- @form_fields[field_name] ||=
96
- FilterFormField.new(
97
- humanized_field_name: @model.human_attribute_name(field_name),
98
- field_name: field_name,
99
- type: @schema.fields[field_name],
100
- params: (@params || {})[field_name]
101
- )
102
- end
103
- end
104
-
105
- class Controls
106
- module Optional
107
- def filters_enabled?
108
- actual.respond_to?(:filter_schema)
109
- end
110
-
111
- def filter_schema
112
- actual.filter_schema
113
- end
114
- end
115
-
116
- module Steps
117
- def initialize_filtering(params:, query_params:)
118
- if filters_enabled?
119
- Super::Filter.new(
120
- model: model,
121
- schema: filter_schema,
122
- params: params[:q],
123
- url: query_params
124
- )
125
- end
126
- end
127
-
128
- def filter_records(filter_form:, records:)
129
- if filters_enabled? && records
130
- filter_form.to_search_query(records)
131
- else
132
- records
133
- end
134
- end
135
- end
12
+ attr_reader :fields
136
13
  end
137
14
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Filter
5
+ class FormObject
6
+ class FilterFormField
7
+ def initialize(humanized_field_name:, field_name:, type:, params:)
8
+ @humanized_field_name = humanized_field_name
9
+ @field_name = field_name
10
+ @field_type = type
11
+ @params = params
12
+ @specified_values =
13
+ type.q
14
+ .map do |query_field_name|
15
+ [
16
+ query_field_name,
17
+ (params || {})[query_field_name],
18
+ ]
19
+ end
20
+ .to_h
21
+
22
+ @specified_values.each do |key, value|
23
+ define_singleton_method(key) { value }
24
+ end
25
+ end
26
+
27
+ attr_reader :humanized_field_name
28
+ attr_reader :field_name
29
+ attr_reader :field_type
30
+ attr_reader :specified_values
31
+
32
+ def op
33
+ (@params || {})[:op]
34
+ end
35
+
36
+ def operators
37
+ @field_type.operators
38
+ .map { |o| [o.name, o.identifier] }
39
+ .to_h
40
+ end
41
+
42
+ def to_partial_path
43
+ @field_type.to_partial_path
44
+ end
45
+ end
46
+
47
+ def initialize(model:, params:, schema:)
48
+ @model = model
49
+ @params = params
50
+ @schema = schema
51
+
52
+ @form_fields = {}
53
+ end
54
+
55
+ def each_field
56
+ @schema.fields.each do |field_name, _field_type|
57
+ yield(form_field_for(field_name))
58
+ end
59
+ end
60
+
61
+ def to_partial_path
62
+ "filter"
63
+ end
64
+
65
+ def apply_changes(relation)
66
+ each_field do |form_field|
67
+ next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
68
+ next if !Super::Filter::Operator.registry.key?(form_field.op)
69
+
70
+ operator = Super::Filter::Operator.registry[form_field.op]
71
+ updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
72
+
73
+ if updated_relation.is_a?(ActiveRecord::Relation)
74
+ relation = updated_relation
75
+ end
76
+ end
77
+
78
+ relation
79
+ end
80
+
81
+ private
82
+
83
+ def form_field_for(field_name)
84
+ @form_fields[field_name] ||=
85
+ FilterFormField.new(
86
+ humanized_field_name: @model.human_attribute_name(field_name),
87
+ field_name: field_name,
88
+ type: @schema.fields[field_name],
89
+ params: (@params || {})[field_name]
90
+ )
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Filter
5
+ class Guesser
6
+ def initialize(model:, fields:, type:)
7
+ @model = model
8
+ @fields = fields
9
+ @type = type
10
+ end
11
+
12
+ def call
13
+ Schema::Guesser
14
+ .new(model: @model, fields: @fields, type: @type)
15
+ .assign_type { |attribute_name| attribute_type_for(attribute_name) }
16
+ .call
17
+ end
18
+
19
+ private
20
+
21
+ def attribute_type_for(attribute_name)
22
+ type = @model.type_for_attribute(attribute_name).type
23
+ case type
24
+ when :datetime
25
+ @type.timestamp
26
+ else
27
+ @type.text
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  class Filter
3
5
  module Operator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  class Filter
3
5
  # This schema type is used to configure the filtering form on your +#index+
@@ -13,7 +15,7 @@ module Super
13
15
  # # ...
14
16
  #
15
17
  # def filter_schema
16
- # Super::Schema.new(Super::Filter::SchemaTypes.new) do |fields, type|
18
+ # Super::Filter.new do |fields, type|
17
19
  # fields[:name] = type.text(operators: [
18
20
  # Super::Filter::Operator.eq,
19
21
  # Super::Filter::Operator.contain,
@@ -90,12 +92,6 @@ module Super
90
92
  end
91
93
  end
92
94
 
93
- def before_yield(fields:)
94
- end
95
-
96
- def after_yield
97
- end
98
-
99
95
  def select(collection:, operators: Filter::Operator.select_defaults)
100
96
  Select.new(
101
97
  collection: collection,
data/lib/super/form.rb CHANGED
@@ -1,48 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
4
+ # This schema type is used on your +#edit+ and +#new+ forms
5
+ #
6
+ # ```ruby
7
+ # class MembersController::Controls
8
+ # # ...
9
+ #
10
+ # def new_schema
11
+ # Super::Form.new do |fields, type|
12
+ # fields[:name] = type.string
13
+ # fields[:rank] = type.select
14
+ # fields[:position] = type.string
15
+ # fields[:ship_id] = type.select(
16
+ # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
17
+ # )
18
+ # end
19
+ # end
20
+ #
21
+ # # ...
22
+ # end
23
+ # ```
2
24
  class Form
3
- module FieldErrorProc
4
- def error_wrapping(html_tag)
5
- if Thread.current[:super_form_builder]
6
- return html_tag
7
- end
25
+ include Schema::Common
8
26
 
9
- super
10
- end
27
+ def initialize
28
+ @fields = Schema::Fields.new
29
+ @schema_types = SchemaTypes.new(fields: @fields)
30
+ yield(@fields, @schema_types)
11
31
  end
12
32
 
13
- class Builder < ActionView::Helpers::FormBuilder
14
- # These methods were originally defined in the following files
15
- #
16
- # * actionview/lib/action_view/helpers/form_helper.rb
17
- # * actionview/lib/action_view/helpers/form_options_helper.rb
18
- # * actionview/lib/action_view/helpers/date_helper.rb
19
- %w[
20
- label text_field password_field hidden_field file_field text_area
21
- check_box radio_button color_field search_field telephone_field
22
- date_field time_field datetime_field month_field week_field url_field
23
- email_field number_field range_field
24
-
25
- select collection_select grouped_collection_select time_zone_select
26
- collection_radio_buttons collection_check_boxes
27
-
28
- time_select datetime_select date_select
29
- ].each do |field_type_method|
30
- class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
31
- def #{field_type_method}(*)
32
- Thread.current[:super_form_builder] = true
33
- super
34
- ensure
35
- Thread.current[:super_form_builder] = nil
36
- end
37
- RUBY
38
- end
39
-
40
- alias datetime_local_field datetime_field
41
- alias phone_field telephone_field
33
+ def to_partial_path
34
+ "form"
42
35
  end
43
36
  end
44
37
  end
45
-
46
- ActionView::Helpers::Tags::Base.class_eval do
47
- prepend Super::Form::FieldErrorProc
48
- end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Form
5
+ # Example
6
+ #
7
+ # ```ruby
8
+ # super_form_for([:admin, @member]) do |f|
9
+ # # the long way
10
+ # f.super.label :name
11
+ # f.super.text_field :name
12
+ # f.super.inline_errors :name
13
+ #
14
+ # # the short way (slightly different from the long way, for alignment)
15
+ # f.super.text_field! :position
16
+ # end
17
+ # ```
18
+ #
19
+ # Refer to the Rails docs:
20
+ # https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html
21
+ class Builder < ActionView::Helpers::FormBuilder
22
+ FIELD_ERROR_PROC = proc { |html_tag, instance| html_tag }
23
+ FORM_BUILDER_DEFAULTS = { builder: self }.freeze
24
+
25
+ def super(**options)
26
+ @super_wrappers ||= Wrappers.new(self, @template)
27
+ end
28
+
29
+ class Wrappers
30
+ def initialize(builder, template)
31
+ @builder = builder
32
+ @template = template
33
+ end
34
+
35
+ def inline_errors(attribute)
36
+ if @builder.object
37
+ messages = InlineErrors.error_messages(@builder.object, attribute).map do |msg|
38
+ error_content_tag(msg)
39
+ end
40
+
41
+ @template.safe_join(messages)
42
+ else
43
+ error_content_tag(<<~MSG.html_safe)
44
+ This form doesn't have an object, so something is probably wrong.
45
+ Maybe <code>accepts_nested_attributes_for</code> isn't set up?
46
+ MSG
47
+ end
48
+ end
49
+
50
+ def label(attribute, text = nil, options = {}, &block)
51
+ options, defaults = split_defaults(options, class: "block")
52
+ options[:class] = join_classes(defaults[:class], options[:class])
53
+
54
+ @builder.label(attribute, text, options, &block)
55
+ end
56
+
57
+ def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0")
58
+ @builder.check_box(attribute, options, checked_value, unchecked_value)
59
+ end
60
+
61
+ def password_field(attribute, options = {})
62
+ options, defaults = split_defaults(options, class: "super-input w-full")
63
+ options[:class] = join_classes(defaults[:class], options[:class])
64
+
65
+ @builder.password_field(attribute, options)
66
+ end
67
+
68
+ def rich_text_area(attribute, options = {})
69
+ options, defaults = split_defaults(options, class: "trix-content super-input w-full")
70
+ options[:class] = join_classes(defaults[:class], options[:class])
71
+
72
+ @builder.rich_text_area(attribute, options)
73
+ end
74
+
75
+ def select(attribute, choices, options = {}, html_options = {}, &block)
76
+ options, defaults = split_defaults(options, include_blank: true)
77
+ options = defaults.merge(options)
78
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select-field")
79
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
80
+
81
+ parts = [
82
+ %(<div class="super-input-select">).html_safe,
83
+ @builder.select(attribute, choices, options, html_options, &block),
84
+ <<~HTML.html_safe,
85
+ <div class="super-input-select-icon text-blue-700">
86
+ <span class="h-4 w-4">
87
+ HTML
88
+ @template.render("super/feather/chevron_down"),
89
+ <<~HTML.html_safe,
90
+ </span>
91
+ </div>
92
+ HTML
93
+ %(</div>).html_safe,
94
+ ]
95
+
96
+ @template.safe_join(parts)
97
+ end
98
+
99
+ def submit(value = nil, options = {})
100
+ value, options = nil, value if value.is_a?(Hash)
101
+ options, defaults = split_defaults(options, class: "super-button")
102
+ options[:class] = join_classes(defaults[:class], options[:class])
103
+
104
+ @builder.submit(value, options)
105
+ end
106
+
107
+ def text_field(attribute, options = {})
108
+ options, defaults = split_defaults(options, class: "super-input w-full")
109
+ options[:class] = join_classes(defaults[:class], options[:class])
110
+
111
+ @builder.text_field(attribute, options)
112
+ end
113
+
114
+ def container(&block)
115
+ @template.content_tag(:div, class: "super-field-group", &block)
116
+ end
117
+
118
+ def check_box!(attribute, checked_value = "1", unchecked_value = "0", label: {}, field: {}, show_errors: true)
119
+ label[:super] ||= {}
120
+ label[:super] = { class: "select-none ml-1" }.merge(label[:super])
121
+ container do
122
+ compact_join([
123
+ "<div>".html_safe,
124
+ public_send(:check_box, attribute, field, checked_value, unchecked_value),
125
+ public_send(:label, attribute, nil, label),
126
+ "</div>".html_safe,
127
+ show_errors && inline_errors(attribute),
128
+ ])
129
+ end
130
+ end
131
+
132
+ def password_field!(attribute, label: {}, field: {}, show_errors: true)
133
+ container do
134
+ compact_join([
135
+ public_send(:label, attribute, label),
136
+ %(<div class="mt-1">).html_safe,
137
+ public_send(:password_field, attribute, field),
138
+ show_errors && inline_errors(attribute),
139
+ %(</div>).html_safe,
140
+ ])
141
+ end
142
+ end
143
+
144
+ def rich_text_area!(attribute, label: {}, field: {}, show_errors: true)
145
+ container do
146
+ compact_join([
147
+ public_send(:label, attribute, label),
148
+ %(<div class="mt-1">).html_safe,
149
+ public_send(:rich_text_area, attribute, field),
150
+ show_errors && inline_errors(attribute),
151
+ %(</div>).html_safe,
152
+ ])
153
+ end
154
+ end
155
+
156
+ def select!(attribute, collection, label: {}, field: {}, show_errors: true)
157
+ container do
158
+ compact_join([
159
+ public_send(:label, attribute, label),
160
+ %(<div class="mt-1">).html_safe,
161
+ public_send(:select, attribute, collection, field),
162
+ show_errors && inline_errors(attribute),
163
+ %(</div>).html_safe,
164
+ ])
165
+ end
166
+ end
167
+
168
+ def text_field!(attribute, label: {}, field: {}, show_errors: true)
169
+ container do
170
+ compact_join([
171
+ public_send(:label, attribute, label),
172
+ %(<div class="mt-1">).html_safe,
173
+ public_send(:text_field, attribute, field),
174
+ show_errors && inline_errors(attribute),
175
+ %(</div>).html_safe,
176
+ ])
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ def split_defaults(options, **internal_defaults)
183
+ defaults = options.delete(:super) || {}
184
+ # prefer options set in `defaults`, since they are user overrides
185
+ defaults = internal_defaults.merge(defaults)
186
+
187
+ [options, defaults]
188
+ end
189
+
190
+ def join_classes(*class_lists)
191
+ class_lists.flatten.map(&:presence).compact
192
+ end
193
+
194
+ def error_content_tag(content)
195
+ @template.content_tag(:p, content, class: "text-red-400 text-xs italic pt-1")
196
+ end
197
+
198
+ def compact_join(*parts)
199
+ @template.safe_join(
200
+ parts.flatten.map(&:presence).compact
201
+ )
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end