view_component 2.62.0 → 2.82.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of view_component might be problematic. Click here for more details.

Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/app/assets/vendor/prism.css +3 -195
  4. data/app/assets/vendor/prism.min.js +11 -11
  5. data/app/controllers/concerns/view_component/preview_actions.rb +6 -10
  6. data/app/controllers/view_components_system_test_controller.rb +7 -0
  7. data/app/helpers/preview_helper.rb +2 -2
  8. data/app/views/view_components/preview.html.erb +2 -2
  9. data/docs/CHANGELOG.md +383 -21
  10. data/lib/rails/generators/abstract_generator.rb +3 -5
  11. data/lib/rails/generators/component/component_generator.rb +5 -4
  12. data/lib/rails/generators/locale/component_generator.rb +1 -1
  13. data/lib/rails/generators/preview/component_generator.rb +10 -3
  14. data/lib/view_component/base.rb +41 -47
  15. data/lib/view_component/compiler.rb +57 -68
  16. data/lib/view_component/config.rb +176 -0
  17. data/lib/view_component/content_areas.rb +2 -3
  18. data/lib/view_component/deprecation.rb +2 -2
  19. data/lib/view_component/docs_builder_component.html.erb +1 -1
  20. data/lib/view_component/docs_builder_component.rb +1 -1
  21. data/lib/view_component/engine.rb +23 -29
  22. data/lib/view_component/polymorphic_slots.rb +11 -5
  23. data/lib/view_component/render_component_helper.rb +1 -0
  24. data/lib/view_component/slotable.rb +2 -3
  25. data/lib/view_component/slotable_v2.rb +48 -8
  26. data/lib/view_component/system_test_case.rb +13 -0
  27. data/lib/view_component/system_test_helpers.rb +27 -0
  28. data/lib/view_component/test_helpers.rb +65 -15
  29. data/lib/view_component/translatable.rb +1 -1
  30. data/lib/view_component/version.rb +1 -1
  31. data/lib/view_component.rb +6 -3
  32. metadata +50 -18
  33. data/lib/view_component/previewable.rb +0 -62
  34. data/lib/view_component/render_preview_helper.rb +0 -50
@@ -4,9 +4,11 @@ require "action_view"
4
4
  require "active_support/configurable"
5
5
  require "view_component/collection"
6
6
  require "view_component/compile_cache"
7
+ require "view_component/compiler"
8
+ require "view_component/config"
7
9
  require "view_component/content_areas"
8
10
  require "view_component/polymorphic_slots"
9
- require "view_component/previewable"
11
+ require "view_component/preview"
10
12
  require "view_component/slotable"
11
13
  require "view_component/slotable_v2"
12
14
  require "view_component/translatable"
@@ -14,10 +16,23 @@ require "view_component/with_content_helper"
14
16
 
15
17
  module ViewComponent
16
18
  class Base < ActionView::Base
17
- include ActiveSupport::Configurable
19
+ class << self
20
+ delegate(*ViewComponent::Config.defaults.keys, to: :config)
21
+
22
+ # Returns the current config.
23
+ #
24
+ # @return [ViewComponent::Config]
25
+ def config
26
+ @config ||= ViewComponent::Config.defaults
27
+ end
28
+
29
+ # Replaces the entire config. You shouldn't need to use this directly
30
+ # unless you're building a `ViewComponent::Config` elsewhere.
31
+ attr_writer :config
32
+ end
33
+
18
34
  include ViewComponent::ContentAreas
19
35
  include ViewComponent::PolymorphicSlots
20
- include ViewComponent::Previewable
21
36
  include ViewComponent::SlotableV2
22
37
  include ViewComponent::Translatable
23
38
  include ViewComponent::WithContentHelper
@@ -51,15 +66,6 @@ module ViewComponent
51
66
  self.__vc_original_view_context = view_context
52
67
  end
53
68
 
54
- # EXPERIMENTAL: This API is experimental and may be removed at any time.
55
- # Hook for allowing components to do work as part of the compilation process.
56
- #
57
- # For example, one might compile component-specific assets at this point.
58
- # @private TODO: add documentation
59
- def self._after_compile
60
- # noop
61
- end
62
-
63
69
  # @!macro [attach] deprecated_generate_mattr_accessor
64
70
  # @method generate_$1
65
71
  # @deprecated Use `#generate.$1` instead. Will be removed in v3.0.0.
@@ -113,11 +119,9 @@ module ViewComponent
113
119
  @current_template = self
114
120
 
115
121
  if block && defined?(@__vc_content_set_by_with_content)
116
- raise ArgumentError.new(
117
- "It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
122
+ raise ArgumentError, "It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
118
123
  "which means that ViewComponent doesn't know which content to use.\n\n" \
119
124
  "To fix this issue, use either `with_content` or a block."
120
- )
121
125
  end
122
126
 
123
127
  @__vc_content_evaluated = false
@@ -126,7 +130,7 @@ module ViewComponent
126
130
  before_render
127
131
 
128
132
  if render?
129
- render_template_for(@__vc_variant).to_s + _output_postamble
133
+ render_template_for(@__vc_variant).to_s + output_postamble
130
134
  else
131
135
  ""
132
136
  end
@@ -149,10 +153,10 @@ module ViewComponent
149
153
  nil
150
154
  end
151
155
 
152
- # EXPERIMENTAL: Optional content to be returned after the rendered template.
156
+ # Optional content to be returned after the rendered template.
153
157
  #
154
158
  # @return [String]
155
- def _output_postamble
159
+ def output_postamble
156
160
  ""
157
161
  end
158
162
 
@@ -262,9 +266,7 @@ module ViewComponent
262
266
  # @private
263
267
  def format
264
268
  # Ruby 2.6 throws a warning without checking `defined?`, 2.7 doesn't
265
- if defined?(@__vc_variant)
266
- @__vc_variant
267
- end
269
+ @__vc_variant if defined?(@__vc_variant)
268
270
  end
269
271
 
270
272
  # Use the provided variant instead of the one determined by the current request.
@@ -313,11 +315,9 @@ module ViewComponent
313
315
  # config.view_component.test_controller = "MyTestController"
314
316
  # ```
315
317
  #
316
- # Defaults to ApplicationController. Can also be configured on a per-test
317
- # basis using `with_controller_class`.
318
+ # Defaults to `nil`. If this is falsy, `"ApplicationController"` is used. Can also be
319
+ # configured on a per-test basis using `with_controller_class`.
318
320
  #
319
- mattr_accessor :test_controller
320
- @@test_controller = "ApplicationController"
321
321
 
322
322
  # Set if render monkey patches should be included or not in Rails <6.1:
323
323
  #
@@ -325,7 +325,6 @@ module ViewComponent
325
325
  # config.view_component.render_monkey_patch_enabled = false
326
326
  # ```
327
327
  #
328
- mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
329
328
 
330
329
  # Path for component files
331
330
  #
@@ -333,9 +332,8 @@ module ViewComponent
333
332
  # config.view_component.view_component_path = "app/my_components"
334
333
  # ```
335
334
  #
336
- # Defaults to `app/components`.
335
+ # Defaults to `nil`. If this is falsy, `app/components` is used.
337
336
  #
338
- mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
339
337
 
340
338
  # Parent class for generated components
341
339
  #
@@ -346,7 +344,6 @@ module ViewComponent
346
344
  # Defaults to nil. If this is falsy, generators will use
347
345
  # "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
348
346
  #
349
- mattr_accessor :component_parent_class, instance_writer: false
350
347
 
351
348
  # Configuration for generators.
352
349
  #
@@ -397,21 +394,19 @@ module ViewComponent
397
394
  # ```
398
395
  #
399
396
  # Defaults to `false`.
400
- mattr_accessor :generate, instance_writer: false, default: ActiveSupport::OrderedOptions.new(false)
401
397
 
402
398
  class << self
403
399
  # @private
404
400
  attr_accessor :source_location, :virtual_path
405
401
 
406
- # EXPERIMENTAL: This API is experimental and may be removed at any time.
407
402
  # Find sidecar files for the given extensions.
408
403
  #
409
404
  # The provided array of extensions is expected to contain
410
- # strings starting without the "dot", example: `["erb", "haml"]`.
405
+ # strings starting without the dot, example: `["erb", "haml"]`.
411
406
  #
412
407
  # For example, one might collect sidecar CSS files that need to be compiled.
413
- # @private TODO: add documentation
414
- def _sidecar_files(extensions)
408
+ # @param extensions [Array<String>] Extensions of which to return matching sidecar files.
409
+ def sidecar_files(extensions)
415
410
  return [] unless source_location
416
411
 
417
412
  extensions = extensions.join(",")
@@ -489,8 +484,8 @@ module ViewComponent
489
484
 
490
485
  # If Rails application is loaded, add application url_helpers to the component context
491
486
  # we need to check this to use this gem as a dependency
492
- if defined?(Rails) && Rails.application
493
- child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
487
+ if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers)
488
+ child.include Rails.application.routes.url_helpers
494
489
  end
495
490
 
496
491
  # Derive the source location of the component Ruby file from the call stack.
@@ -500,7 +495,7 @@ module ViewComponent
500
495
 
501
496
  # Removes the first part of the path and the extension.
502
497
  child.virtual_path = child.source_location.gsub(
503
- %r{(.*#{Regexp.quote(ViewComponent::Base.view_component_path)})|(\.rb)}, ""
498
+ /(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, ""
504
499
  )
505
500
 
506
501
  # Set collection parameter to the extended component
@@ -585,26 +580,22 @@ module ViewComponent
585
580
  parameter = validate_default ? collection_parameter : provided_collection_parameter
586
581
 
587
582
  return unless parameter
588
- return if initialize_parameter_names.include?(parameter)
583
+ return if initialize_parameter_names.include?(parameter) || splatted_keyword_argument_present?
589
584
 
590
585
  # If Ruby can't parse the component class, then the initalize
591
586
  # parameters will be empty and ViewComponent will not be able to render
592
587
  # the component.
593
588
  if initialize_parameters.empty?
594
- raise ArgumentError.new(
595
- "The #{self} initializer is empty or invalid." \
589
+ raise ArgumentError, "The #{self} initializer is empty or invalid." \
596
590
  "It must accept the parameter `#{parameter}` to render it as a collection.\n\n" \
597
591
  "To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
598
592
  "See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
599
- )
600
593
  end
601
594
 
602
- raise ArgumentError.new(
603
- "The initializer for #{self} doesn't accept the parameter `#{parameter}`, " \
595
+ raise ArgumentError, "The initializer for #{self} doesn't accept the parameter `#{parameter}`, " \
604
596
  "which is required in order to render it as a collection.\n\n" \
605
597
  "To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
606
598
  "See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
607
- )
608
599
  end
609
600
 
610
601
  # Ensure the component initializer doesn't define
@@ -614,10 +605,8 @@ module ViewComponent
614
605
  def validate_initialization_parameters!
615
606
  return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
616
607
 
617
- raise ViewComponent::ComponentError.new(
618
- "#{self} initializer can't accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
608
+ raise ViewComponent::ComponentError, "#{self} initializer can't accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
619
609
  "public ViewComponent method. To fix this issue, rename the parameter."
620
- )
621
610
  end
622
611
 
623
612
  # @private
@@ -647,6 +636,11 @@ module ViewComponent
647
636
 
648
637
  private
649
638
 
639
+ def splatted_keyword_argument_present?
640
+ initialize_parameters.flatten.include?(:keyrest) &&
641
+ !initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count!
642
+ end
643
+
650
644
  def initialize_parameter_names
651
645
  return attribute_names.map(&:to_sym) if respond_to?(:attribute_names)
652
646
 
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent-ruby"
4
+
3
5
  module ViewComponent
4
6
  class Compiler
5
- # Lock required to be obtained before compiling the component
6
- attr_reader :__vc_compiler_lock
7
-
8
7
  # Compiler mode. Can be either:
9
8
  # * development (a blocking mode which ensures thread safety when redefining the `call` method for components,
10
9
  # default in Rails development and test mode)
@@ -16,7 +15,7 @@ module ViewComponent
16
15
 
17
16
  def initialize(component_class)
18
17
  @component_class = component_class
19
- @__vc_compiler_lock = Monitor.new
18
+ @redefinition_lock = Mutex.new
20
19
  end
21
20
 
22
21
  def compiled?
@@ -32,46 +31,39 @@ module ViewComponent
32
31
  return if component_class == ViewComponent::Base
33
32
 
34
33
  component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass?
34
+ subclass_instance_methods = component_class.instance_methods(false)
35
35
 
36
- with_lock do
37
- CompileCache.invalidate_class!(component_class)
38
-
39
- subclass_instance_methods = component_class.instance_methods(false)
40
-
41
- if subclass_instance_methods.include?(:with_content) && raise_errors
42
- raise ViewComponent::ComponentError.new(
43
- "#{component_class} implements a reserved method, `#with_content`.\n\n" \
44
- "To fix this issue, change the name of the method."
45
- )
46
- end
47
-
48
- if template_errors.present?
49
- raise ViewComponent::TemplateError.new(template_errors) if raise_errors
36
+ if subclass_instance_methods.include?(:with_content) && raise_errors
37
+ raise ViewComponent::ComponentError.new(
38
+ "#{component_class} implements a reserved method, `#with_content`.\n\n" \
39
+ "To fix this issue, change the name of the method."
40
+ )
41
+ end
50
42
 
51
- return false
52
- end
43
+ if template_errors.present?
44
+ raise ViewComponent::TemplateError.new(template_errors) if raise_errors
53
45
 
54
- if subclass_instance_methods.include?(:before_render_check)
55
- ViewComponent::Deprecation.warn(
56
- "`#before_render_check` will be removed in v3.0.0.\n\n" \
57
- "To fix this issue, use `#before_render` instead."
58
- )
59
- end
46
+ return false
47
+ end
60
48
 
61
- if raise_errors
62
- component_class.validate_initialization_parameters!
63
- component_class.validate_collection_parameter!
64
- end
49
+ if subclass_instance_methods.include?(:before_render_check)
50
+ ViewComponent::Deprecation.deprecation_warning(
51
+ "`before_render_check`", :"`before_render`"
52
+ )
53
+ end
65
54
 
66
- templates.each do |template|
67
- # Remove existing compiled template methods,
68
- # as Ruby warns when redefining a method.
69
- method_name = call_method_name(template[:variant])
55
+ if raise_errors
56
+ component_class.validate_initialization_parameters!
57
+ component_class.validate_collection_parameter!
58
+ end
70
59
 
71
- if component_class.instance_methods.include?(method_name.to_sym)
72
- component_class.send(:undef_method, method_name.to_sym)
73
- end
60
+ templates.each do |template|
61
+ # Remove existing compiled template methods,
62
+ # as Ruby warns when redefining a method.
63
+ method_name = call_method_name(template[:variant])
74
64
 
65
+ redefinition_lock.synchronize do
66
+ component_class.silence_redefinition_of_method(method_name)
75
67
  # rubocop:disable Style/EvalWithLocation
76
68
  component_class.class_eval <<-RUBY, template[:path], 0
77
69
  def #{method_name}
@@ -80,35 +72,22 @@ module ViewComponent
80
72
  RUBY
81
73
  # rubocop:enable Style/EvalWithLocation
82
74
  end
75
+ end
83
76
 
84
- define_render_template_for
85
-
86
- component_class.build_i18n_backend
87
- component_class._after_compile
77
+ define_render_template_for
88
78
 
89
- CompileCache.register(component_class)
90
- end
91
- end
79
+ component_class.build_i18n_backend
92
80
 
93
- def with_lock(&block)
94
- if development?
95
- __vc_compiler_lock.synchronize(&block)
96
- else
97
- block.call
98
- end
81
+ CompileCache.register(component_class)
99
82
  end
100
83
 
101
84
  private
102
85
 
103
- attr_reader :component_class
86
+ attr_reader :component_class, :redefinition_lock
104
87
 
105
88
  def define_render_template_for
106
- if component_class.instance_methods.include?(:render_template_for)
107
- component_class.send(:undef_method, :render_template_for)
108
- end
109
-
110
89
  variant_elsifs = variants.compact.uniq.map do |variant|
111
- "elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
90
+ "elsif variant.to_sym == :'#{variant}'\n #{call_method_name(variant)}"
112
91
  end.join("\n")
113
92
 
114
93
  body = <<-RUBY
@@ -120,15 +99,8 @@ module ViewComponent
120
99
  end
121
100
  RUBY
122
101
 
123
- if development?
124
- component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
125
- def render_template_for(variant = nil)
126
- self.class.compiler.with_lock do
127
- #{body}
128
- end
129
- end
130
- RUBY
131
- else
102
+ redefinition_lock.synchronize do
103
+ component_class.silence_redefinition_of_method(:render_template_for)
132
104
  component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
133
105
  def render_template_for(variant = nil)
134
106
  #{body}
@@ -186,6 +158,19 @@ module ViewComponent
186
158
  "There can only be a template file or inline render method per variant."
187
159
  end
188
160
 
161
+ uniq_variants = variants.compact.uniq
162
+ normalized_variants = uniq_variants.map { |variant| normalized_variant_name(variant) }
163
+
164
+ colliding_variants = uniq_variants.select do |variant|
165
+ normalized_variants.count(normalized_variant_name(variant)) > 1
166
+ end
167
+
168
+ unless colliding_variants.empty?
169
+ errors <<
170
+ "Colliding templates #{colliding_variants.sort.map { |v| "'#{v}'" }.to_sentence} " \
171
+ "found in #{component_class}."
172
+ end
173
+
189
174
  errors
190
175
  end
191
176
  end
@@ -195,11 +180,11 @@ module ViewComponent
195
180
  begin
196
181
  extensions = ActionView::Template.template_handler_extensions
197
182
 
198
- component_class._sidecar_files(extensions).each_with_object([]) do |path, memo|
183
+ component_class.sidecar_files(extensions).each_with_object([]) do |path, memo|
199
184
  pieces = File.basename(path).split(".")
200
185
  memo << {
201
186
  path: path,
202
- variant: pieces.second.split("+").second&.to_sym,
187
+ variant: pieces[1..-2].join(".").split("+").second&.to_sym,
203
188
  handler: pieces.last
204
189
  }
205
190
  end
@@ -257,12 +242,16 @@ module ViewComponent
257
242
 
258
243
  def call_method_name(variant)
259
244
  if variant.present? && variants.include?(variant)
260
- "call_#{variant}"
245
+ "call_#{normalized_variant_name(variant)}"
261
246
  else
262
247
  "call"
263
248
  end
264
249
  end
265
250
 
251
+ def normalized_variant_name(variant)
252
+ variant.to_s.gsub("-", "__").gsub(".", "___")
253
+ end
254
+
266
255
  def should_compile_superclass?
267
256
  development? &&
268
257
  templates.empty? &&
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "view_component/deprecation"
4
+
5
+ module ViewComponent
6
+ class Config
7
+ class << self
8
+ # `new` without any arguments initializes the default configuration, but
9
+ # it's important to differentiate in case that's no longer the case in
10
+ # future.
11
+ alias_method :default, :new
12
+
13
+ def defaults
14
+ ActiveSupport::OrderedOptions.new.merge!({
15
+ generate: default_generate_options,
16
+ preview_controller: "ViewComponentsController",
17
+ preview_route: "/rails/view_components",
18
+ show_previews_source: false,
19
+ instrumentation_enabled: false,
20
+ render_monkey_patch_enabled: true,
21
+ view_component_path: "app/components",
22
+ component_parent_class: nil,
23
+ show_previews: Rails.env.development? || Rails.env.test?,
24
+ preview_paths: default_preview_paths,
25
+ test_controller: "ApplicationController",
26
+ default_preview_layout: nil
27
+ })
28
+ end
29
+
30
+ # @!attribute generate
31
+ # @return [ActiveSupport::OrderedOptions]
32
+ # The subset of configuration options relating to generators.
33
+ #
34
+ # All options under this namespace default to `false` unless otherwise
35
+ # stated.
36
+ #
37
+ # #### `#sidecar`
38
+ #
39
+ # Always generate a component with a sidecar directory:
40
+ #
41
+ # config.view_component.generate.sidecar = true
42
+ #
43
+ # #### `#stimulus_controller`
44
+ #
45
+ # Always generate a Stimulus controller alongside the component:
46
+ #
47
+ # config.view_component.generate.stimulus_controller = true
48
+ #
49
+ # #### `#locale`
50
+ #
51
+ # Always generate translations file alongside the component:
52
+ #
53
+ # config.view_component.generate.locale = true
54
+ #
55
+ # #### `#distinct_locale_files`
56
+ #
57
+ # Always generate as many translations files as available locales:
58
+ #
59
+ # config.view_component.generate.distinct_locale_files = true
60
+ #
61
+ # One file will be generated for each configured `I18n.available_locales`,
62
+ # falling back to `[:en]` when no `available_locales` is defined.
63
+ #
64
+ # #### `#preview`
65
+ #
66
+ # Always generate a preview alongside the component:
67
+ #
68
+ # config.view_component.generate.preview = true
69
+ #
70
+ # #### #preview_path
71
+ #
72
+ # Path to generate preview:
73
+ #
74
+ # config.view_component.generate.preview_path = "test/components/previews"
75
+ #
76
+ # Required when there is more than one path defined in preview_paths.
77
+ # Defaults to `""`. If this is blank, the generator will use
78
+ # `ViewComponent.config.preview_paths` if defined,
79
+ # `"test/components/previews"` otherwise
80
+
81
+ # @!attribute preview_controller
82
+ # @return [String]
83
+ # The controller used for previewing components.
84
+ # Defaults to `ViewComponentsController`.
85
+
86
+ # @!attribute preview_route
87
+ # @return [String]
88
+ # The entry route for component previews.
89
+ # Defaults to `"/rails/view_components"`.
90
+
91
+ # @!attribute show_previews_source
92
+ # @return [Boolean]
93
+ # Whether to display source code previews in component previews.
94
+ # Defaults to `false`.
95
+
96
+ # @!attribute instrumentation_enabled
97
+ # @return [Boolean]
98
+ # Whether ActiveSupport notifications are enabled.
99
+ # Defaults to `false`.
100
+
101
+ # @!attribute render_monkey_patch_enabled
102
+ # @return [Boolean] Whether the #render method should be monkey patched.
103
+ # If this is disabled, use `#render_component` or
104
+ # `#render_component_to_string` instead.
105
+ # Defaults to `true`.
106
+
107
+ # @!attribute view_component_path
108
+ # @return [String]
109
+ # The path in which components, their templates, and their sidecars should
110
+ # be stored.
111
+ # Defaults to `"app/components"`.
112
+
113
+ # @!attribute component_parent_class
114
+ # @return [String]
115
+ # The parent class from which generated components will inherit.
116
+ # Defaults to `nil`. If this is falsy, generators will use
117
+ # `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise.
118
+
119
+ # @!attribute show_previews
120
+ # @return [Boolean]
121
+ # Whether component previews are enabled.
122
+ # Defaults to `true` in development and test environments.
123
+
124
+ # @!attribute preview_paths
125
+ # @return [Array<String>]
126
+ # The locations in which component previews will be looked up.
127
+ # Defaults to `['test/component/previews']` relative to your Rails root.
128
+
129
+ # @!attribute preview_path
130
+ # @deprecated Use #preview_paths instead. Will be removed in v3.0.0.
131
+
132
+ # @!attribute test_controller
133
+ # @return [String]
134
+ # The controller used for testing components.
135
+ # Can also be configured on a per-test basis using `#with_controller_class`.
136
+ # Defaults to `ApplicationController`.
137
+
138
+ # @!attribute default_preview_layout
139
+ # @return [String]
140
+ # A custom default layout used for the previews index page and individual
141
+ # previews.
142
+ # Defaults to `nil`. If this is falsy, `"component_preview"` is used.
143
+
144
+ def default_preview_paths
145
+ return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
146
+
147
+ ["#{Rails.root}/test/components/previews"]
148
+ end
149
+
150
+ def default_generate_options
151
+ options = ActiveSupport::OrderedOptions.new(false)
152
+ options.preview_path = ""
153
+ options
154
+ end
155
+ end
156
+
157
+ def initialize
158
+ @config = self.class.defaults
159
+ end
160
+
161
+ def preview_path
162
+ preview_paths
163
+ end
164
+
165
+ def preview_path=(new_value)
166
+ ViewComponent::Deprecation.deprecation_warning("`preview_path`", :"`preview_paths`")
167
+ self.preview_paths = Array.wrap(new_value)
168
+ end
169
+
170
+ delegate_missing_to :config
171
+
172
+ private
173
+
174
+ attr_reader :config
175
+ end
176
+ end
@@ -31,9 +31,8 @@ module ViewComponent
31
31
 
32
32
  class_methods do
33
33
  def with_content_areas(*areas)
34
- ViewComponent::Deprecation.warn(
35
- "`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n\n" \
36
- "Use slots (https://viewcomponent.org/guide/slots.html) instead."
34
+ ViewComponent::Deprecation.deprecation_warning(
35
+ "`with_content_areas`", "use slots (https://viewcomponent.org/guide/slots.html) instead"
37
36
  )
38
37
 
39
38
  if areas.include?(:content)
@@ -3,6 +3,6 @@
3
3
  require "active_support/deprecation"
4
4
 
5
5
  module ViewComponent
6
- DEPRECATION_HORIZON = 3
7
- Deprecation = ActiveSupport::Deprecation.new(DEPRECATION_HORIZON.to_s, "ViewComponent")
6
+ DEPRECATION_HORIZON = "3.0.0"
7
+ Deprecation = ActiveSupport::Deprecation.new(DEPRECATION_HORIZON, "ViewComponent")
8
8
  end
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  layout: default
3
- title: API
3
+ title: API reference
4
4
  nav_order: 3
5
5
  ---
6
6
 
@@ -57,7 +57,7 @@ module ViewComponent
57
57
 
58
58
  def call
59
59
  <<~DOCS.chomp
60
- #{separator}#{signature_or_name}#{types}#{suffix}
60
+ `#{separator}#{signature_or_name}`#{types}#{suffix}
61
61
 
62
62
  #{docstring_and_deprecation_text}
63
63
  DOCS