view_component 2.14.1 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '084152c558b71f87f41d620f9cdce42e689e0b4159cdfa68f3cb041a7bbea3ba'
4
- data.tar.gz: 45324159a07873ce92562650fe7c2ad6e8a279c3db216b3460a6075f71033180
3
+ metadata.gz: 6fcbca08ed9ca2f6cf62a7ad154dc297ba4606a62faf0aebeeaa544c2c76a663
4
+ data.tar.gz: 677f442dbcf86d4ca7cfbf4d3ad71a8cb92756ddfeb85afb3b973bd1238a56b2
5
5
  SHA512:
6
- metadata.gz: 58f994aac7efdf81146972a351a12726131658e4ccbd8bf0661eb135d7935ff396c64752d9f9edd6dda50b90a4829aeb506f24edee712279aa1cf4bf4f15886d
7
- data.tar.gz: 430b55482aaafaea6a27e73593861f897620cb635c2a2f14f14b5a772846a8f5a7b42bd973f637cd587816762891bcc432ced92dbac04fe1d391bf7734873a6a
6
+ metadata.gz: bd47863117cc291707ccee9b3bed69daeea0dd432cead045f6d8211ad201bbec34a23132836815751f9a5df64ab92fd3724dc3bf61e03282741d3d468e7c1faa
7
+ data.tar.gz: 150ebb993d9cdca790ffa437540cb454bea5021350a5a5f96b7596cb522b552a8507c5456e3353b6e2c7ee2df6edfb5eb5b92815f81c38996cb47c5e7351c324
@@ -1,5 +1,43 @@
1
1
  # master
2
2
 
3
+ # 2.18.0
4
+
5
+ * Fix auto-loading of previews (changes no longer require a server restart)
6
+
7
+ *Matt Brictson*
8
+
9
+ * Add `default_preview_layout` configuration option to load custom app/views/layouts.
10
+
11
+ *Jared White, Juan Manuel Ramallo*
12
+
13
+ * Calculate virtual_path once for all instances of a component class to improve performance.
14
+
15
+ *Brad Parker*
16
+
17
+ # 2.17.1
18
+
19
+ * Fix bug where rendering Slot with empty block resulted in error.
20
+
21
+ *Joel Hawksley*
22
+
23
+ # 2.17.0
24
+
25
+ * Slots return stripped HTML, removing leading and trailing whitespace.
26
+
27
+ *Jason Long, Joel Hawksley*
28
+
29
+ # 2.16.0
30
+
31
+ * Add `--sidecar` option to the erb, haml and slim generators. Places the generated template in the sidecar directory.
32
+
33
+ *Michael van Rooijen*
34
+
35
+ # 2.15.0
36
+
37
+ * Add support for templates as ViewComponent::Preview examples.
38
+
39
+ *Juan Manuel Ramallo
40
+
3
41
  # 2.14.1
4
42
 
5
43
  * Allow using `render_inline` in test when the render monkey patch is disabled with `config.view_component.render_monkey_patch_enabled = false` in versions of Rails < 6.1.
data/README.md CHANGED
@@ -341,15 +341,25 @@ As an alternative, views and other assets can be placed in a sidecar directory w
341
341
  ```
342
342
  app/components
343
343
  ├── ...
344
- ├── test_component.rb
345
- ├── test_component
346
- | ├── test_component.css
347
- | ├── test_component.html.erb
348
- | └── test_component.js
344
+ ├── example_component.rb
345
+ ├── example_component
346
+ | ├── example_component.css
347
+ | ├── example_component.html.erb
348
+ | └── example_component.js
349
349
  ├── ...
350
350
 
351
351
  ```
352
352
 
353
+ To generate a component with a sidecar directory, use the `--sidecar` flag:
354
+
355
+ ```
356
+ bin/rails generate component Example title content --sidecar
357
+ invoke test_unit
358
+ create test/components/example_component_test.rb
359
+ create app/components/example_component.rb
360
+ create app/components/example_component/example_component.html.erb
361
+ ```
362
+
353
363
  ### Conditional Rendering
354
364
 
355
365
  Components can implement a `#render?` method to be called after initialization to determine if the component should render.
@@ -664,6 +674,14 @@ class TestComponentPreview < ViewComponent::Preview
664
674
  end
665
675
  ```
666
676
 
677
+ You can also set a custom layout to be used by default for previews as well as the preview index pages via the `default_preview_layout` configuration option:
678
+
679
+ `config/application.rb`
680
+ ```ruby
681
+ # Set the default layout to app/views/layouts/component_preview.html.erb
682
+ config.default_preview_layout = "component_preview"
683
+ ```
684
+
667
685
  Preview classes live in `test/components/previews`, which can be configured using the `preview_paths` option:
668
686
 
669
687
  `config/application.rb`
@@ -680,6 +698,57 @@ config.view_component.preview_route = "/previews"
680
698
 
681
699
  This example will make the previews available from <http://localhost:3000/previews>.
682
700
 
701
+ #### Preview templates
702
+
703
+ Given a preview `test/components/previews/cell_component_preview.rb`, template files can be defined at `test/components/previews/cell_component_preview/`:
704
+
705
+ `test/components/previews/cell_component_preview.rb`
706
+ ```ruby
707
+ class CellComponentPreview < ViewComponent::Preview
708
+ def default
709
+ end
710
+ end
711
+ ```
712
+
713
+ `test/components/previews/cell_component_preview/default.html.erb`
714
+ ```erb
715
+ <table class="table">
716
+ <tbody>
717
+ <tr>
718
+ <%= render CellComponent.new %>
719
+ </tr>
720
+ </tbody>
721
+ </div>
722
+ ```
723
+
724
+ To use a different location for preview templates, pass the `template` argument:
725
+ (the path should be relative to `config.view_component.preview_path`):
726
+
727
+ `test/components/previews/cell_component_preview.rb`
728
+ ```ruby
729
+ class CellComponentPreview < ViewComponent::Preview
730
+ def default
731
+ render_with_template(template: 'custom_cell_component_preview/my_preview_template')
732
+ end
733
+ end
734
+ ```
735
+
736
+ Values from `params` can be accessed through `locals`:
737
+
738
+ `test/components/previews/cell_component_preview.rb`
739
+ ```ruby
740
+ class CellComponentPreview < ViewComponent::Preview
741
+ def default(title: "Default title", subtitle: "A subtitle")
742
+ render_with_template(locals: {
743
+ title: title,
744
+ subtitle: subtitle
745
+ })
746
+ end
747
+ end
748
+ ```
749
+
750
+ Which enables passing in a value with <http://localhost:3000/rails/components/cell_component/default?title=Custom+title&subtitle=Another+subtitle>.
751
+
683
752
  #### Configuring TestController
684
753
 
685
754
  Component tests and previews assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
@@ -929,10 +998,10 @@ ViewComponent is built by:
929
998
  |@maxbeizer|@franco|@tbroad-ramsey|@jensljungblad|@bbugh|
930
999
  |Nashville, TN|Switzerland|Spring Hill, TN|New York, NY|Austin, TX|
931
1000
 
932
- |<img src="https://avatars.githubusercontent.com/johannesengl?s=256" alt="johannesengl" width="128" />|<img src="https://avatars.githubusercontent.com/czj?s=256" alt="czj" width="128" />|
933
- |:---:|:---:|
934
- |@johannesengl|@czj|
935
- |Berlin, Germany|Paris, France|
1001
+ |<img src="https://avatars.githubusercontent.com/johannesengl?s=256" alt="johannesengl" width="128" />|<img src="https://avatars.githubusercontent.com/czj?s=256" alt="czj" width="128" />|<img src="https://avatars.githubusercontent.com/mrrooijen?s=256" alt="mrrooijen" width="128" />|<img src="https://avatars.githubusercontent.com/bradparker?s=256" alt="bradparker" width="128" />|<img src="https://avatars.githubusercontent.com/mattbrictson?s=256" alt="mattbrictson" width="128" />|
1002
+ |:---:|:---:|:---:|:---:|:---:|
1003
+ |@johannesengl|@czj|@mrrooijen|@bradparker|@mattbrictson|
1004
+ |Berlin, Germany|Paris, France|The Netherlands|Brisbane, Australia|San Francisco|
936
1005
 
937
1006
  ## License
938
1007
 
@@ -16,24 +16,34 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc:
16
16
  def index
17
17
  @previews = ViewComponent::Preview.all
18
18
  @page_title = "Component Previews"
19
+ render "view_components/index", **determine_layout
19
20
  end
20
21
 
21
22
  def previews
22
23
  if params[:path] == @preview.preview_name
23
24
  @page_title = "Component Previews for #{@preview.preview_name}"
24
- render "view_components/previews"
25
+ render "view_components/previews", **determine_layout
25
26
  else
26
27
  prepend_application_view_paths
28
+ prepend_preview_examples_view_path
27
29
  @example_name = File.basename(params[:path])
28
30
  @render_args = @preview.render_args(@example_name, params: params.permit!)
29
- layout = @render_args[:layout]
30
- opts = layout.nil? ? {} : { layout: layout }
31
- render "view_components/preview", opts
31
+ layout = determine_layout(@render_args[:layout], prepend_views: false)[:layout]
32
+ template = @render_args[:template]
33
+ locals = @render_args[:locals]
34
+ opts = {}
35
+ opts[:layout] = layout if layout.present? || layout == false
36
+ opts[:locals] = locals if locals.present?
37
+ render template, opts # rubocop:disable GitHub/RailsControllerRenderLiteral
32
38
  end
33
39
  end
34
40
 
35
41
  private
36
42
 
43
+ def default_preview_layout # :doc:
44
+ ViewComponent::Base.default_preview_layout
45
+ end
46
+
37
47
  def show_previews? # :doc:
38
48
  ViewComponent::Base.show_previews
39
49
  end
@@ -56,7 +66,29 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc:
56
66
  end
57
67
  end
58
68
 
69
+ # Returns either {} or {layout: value} depending on configuration
70
+ def determine_layout(layout_override = nil, prepend_views: true)
71
+ return {} unless defined?(Rails.root)
72
+
73
+ layout_declaration = {}
74
+
75
+ if !layout_override.nil?
76
+ # Allow component-level override, even if false (thus no layout rendered)
77
+ layout_declaration[:layout] = layout_override
78
+ elsif default_preview_layout.present?
79
+ layout_declaration[:layout] = default_preview_layout
80
+ end
81
+
82
+ prepend_application_view_paths if layout_declaration[:layout].present? && prepend_views
83
+
84
+ layout_declaration
85
+ end
86
+
59
87
  def prepend_application_view_paths
60
88
  prepend_view_path Rails.root.join("app/views") if defined?(Rails.root)
61
89
  end
90
+
91
+ def prepend_preview_examples_view_path
92
+ prepend_view_path(ViewComponent::Base.preview_paths)
93
+ end
62
94
  end
@@ -6,13 +6,22 @@ module Erb
6
6
  module Generators
7
7
  class ComponentGenerator < Base
8
8
  source_root File.expand_path("templates", __dir__)
9
+ class_option :sidecar, type: :boolean, default: false
9
10
 
10
11
  def copy_view_file
11
- template "component.html.erb", File.join("app/components", class_path, "#{file_name}_component.html.erb")
12
+ template "component.html.erb", destination
12
13
  end
13
14
 
14
15
  private
15
16
 
17
+ def destination
18
+ if options["sidecar"]
19
+ File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.erb")
20
+ else
21
+ File.join("app/components", class_path, "#{file_name}_component.html.erb")
22
+ end
23
+ end
24
+
16
25
  def file_name
17
26
  @_file_name ||= super.sub(/_component\z/i, "")
18
27
  end
@@ -6,13 +6,22 @@ module Haml
6
6
  module Generators
7
7
  class ComponentGenerator < Erb::Generators::ComponentGenerator
8
8
  source_root File.expand_path("templates", __dir__)
9
+ class_option :sidecar, type: :boolean, default: false
9
10
 
10
11
  def copy_view_file
11
- template "component.html.haml", File.join("app/components", class_path, "#{file_name}_component.html.haml")
12
+ template "component.html.haml", destination
12
13
  end
13
14
 
14
15
  private
15
16
 
17
+ def destination
18
+ if options["sidecar"]
19
+ File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.haml")
20
+ else
21
+ File.join("app/components", class_path, "#{file_name}_component.html.haml")
22
+ end
23
+ end
24
+
16
25
  def file_name
17
26
  @_file_name ||= super.sub(/_component\z/i, "")
18
27
  end
@@ -6,13 +6,22 @@ module Slim
6
6
  module Generators
7
7
  class ComponentGenerator < Erb::Generators::ComponentGenerator
8
8
  source_root File.expand_path("templates", __dir__)
9
+ class_option :sidecar, type: :boolean, default: false
9
10
 
10
11
  def copy_view_file
11
- template "component.html.slim", File.join("app/components", class_path, "#{file_name}_component.html.slim")
12
+ template "component.html.slim", destination
12
13
  end
13
14
 
14
15
  private
15
16
 
17
+ def destination
18
+ if options["sidecar"]
19
+ File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.slim")
20
+ else
21
+ File.join("app/components", class_path, "#{file_name}_component.html.slim")
22
+ end
23
+ end
24
+
16
25
  def file_name
17
26
  @_file_name ||= super.sub(/_component\z/i, "")
18
27
  end
@@ -7,6 +7,7 @@ module ViewComponent
7
7
 
8
8
  autoload :Base
9
9
  autoload :Preview
10
+ autoload :PreviewTemplateError
10
11
  autoload :TestHelpers
11
12
  autoload :TestCase
12
13
  autoload :TemplateError
@@ -116,9 +116,9 @@ module ViewComponent
116
116
  @helpers ||= controller.view_context
117
117
  end
118
118
 
119
- # Removes the first part of the path and the extension.
119
+ # Exposes .virutal_path as an instance method
120
120
  def virtual_path
121
- self.class.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
121
+ self.class.virtual_path
122
122
  end
123
123
 
124
124
  # For caching, such as #cache_if
@@ -166,7 +166,7 @@ module ViewComponent
166
166
  mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
167
167
 
168
168
  class << self
169
- attr_accessor :source_location
169
+ attr_accessor :source_location, :virtual_path
170
170
 
171
171
  # Render a component collection.
172
172
  def with_collection(collection, **args)
@@ -189,6 +189,9 @@ module ViewComponent
189
189
  # has been re-defined by the consuming application, likely in ApplicationComponent.
190
190
  child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
191
191
 
192
+ # Removes the first part of the path and the extension.
193
+ child.virtual_path = child.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
194
+
192
195
  # Clone slot configuration into child class
193
196
  # see #test_slots_pollution
194
197
  child.slots = self.slots.clone
@@ -34,8 +34,8 @@ module ViewComponent
34
34
  initializer "view_component.set_autoload_paths" do |app|
35
35
  options = app.config.view_component
36
36
 
37
- if options.show_previews && options.preview_path
38
- ActiveSupport::Dependencies.autoload_paths << options.preview_path
37
+ if options.show_previews && !options.preview_paths.empty?
38
+ ActiveSupport::Dependencies.autoload_paths.concat(options.preview_paths)
39
39
  end
40
40
  end
41
41
 
@@ -8,7 +8,20 @@ module ViewComponent # :nodoc:
8
8
  extend ActiveSupport::DescendantsTracker
9
9
 
10
10
  def render(component, **args, &block)
11
- { component: component, args: args, block: block }
11
+ {
12
+ args: args,
13
+ block: block,
14
+ component: component,
15
+ locals: {},
16
+ template: "view_components/preview",
17
+ }
18
+ end
19
+
20
+ def render_with_template(template: nil, locals: {})
21
+ {
22
+ template: template,
23
+ locals: locals
24
+ }
12
25
  end
13
26
 
14
27
  class << self
@@ -23,6 +36,8 @@ module ViewComponent # :nodoc:
23
36
  example_params_names = instance_method(example).parameters.map(&:last)
24
37
  provided_params = params.slice(*example_params_names).to_h.symbolize_keys
25
38
  result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)
39
+ result ||= {}
40
+ result[:template] = preview_example_template_path(example) if result[:template].nil?
26
41
  @layout = nil unless defined?(@layout)
27
42
  result.merge(layout: @layout)
28
43
  end
@@ -62,6 +77,20 @@ module ViewComponent # :nodoc:
62
77
  @layout = layout_name
63
78
  end
64
79
 
80
+ # Returns the relative path (from preview_path) to the preview example template if the template exists
81
+ def preview_example_template_path(example)
82
+ preview_path = Array(preview_paths).detect do |preview_path|
83
+ Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
84
+ end
85
+
86
+ if preview_path.nil?
87
+ raise PreviewTemplateError, "preview template for example #{example} does not exist"
88
+ end
89
+
90
+ path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
91
+ Pathname.new(path).relative_path_from(Pathname.new(preview_path)).to_s
92
+ end
93
+
65
94
  private
66
95
 
67
96
  def load_previews
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ class PreviewTemplateError < StandardError
5
+ end
6
+ end
@@ -7,6 +7,14 @@ module ViewComponent # :nodoc:
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
+ # Set a custom default preview layout through app configuration:
11
+ #
12
+ # config.view_component.default_preview_layout = "component_preview"
13
+ #
14
+ # This affects preview index pages as well as individual component previews
15
+ #
16
+ mattr_accessor :default_preview_layout, instance_writer: false
17
+
10
18
  # Set the location of component previews through app configuration:
11
19
  #
12
20
  # config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
@@ -98,7 +98,7 @@ module ViewComponent
98
98
  slot_instance = args.present? ? slot_class.new(**args) : slot_class.new
99
99
 
100
100
  # Capture block and assign to slot_instance#content
101
- slot_instance.content = view_context.capture(&block) if block_given?
101
+ slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block_given?
102
102
 
103
103
  if slot[:collection]
104
104
  # Initialize instance variable as an empty array
@@ -3,8 +3,8 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 14
7
- PATCH = 1
6
+ MINOR = 18
7
+ PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.14.1
4
+ version: 2.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-10 00:00:00.000000000 Z
11
+ date: 2020-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -170,7 +170,7 @@ dependencies:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
172
  version: '0.1'
173
- description:
173
+ description:
174
174
  email:
175
175
  - opensource+view_component@github.com
176
176
  executables: []
@@ -203,6 +203,7 @@ files:
203
203
  - lib/view_component/compile_cache.rb
204
204
  - lib/view_component/engine.rb
205
205
  - lib/view_component/preview.rb
206
+ - lib/view_component/preview_template_error.rb
206
207
  - lib/view_component/previewable.rb
207
208
  - lib/view_component/render_component_helper.rb
208
209
  - lib/view_component/render_component_to_string_helper.rb
@@ -221,7 +222,7 @@ licenses:
221
222
  - MIT
222
223
  metadata:
223
224
  allowed_push_host: https://rubygems.org
224
- post_install_message:
225
+ post_install_message:
225
226
  rdoc_options: []
226
227
  require_paths:
227
228
  - lib
@@ -237,7 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
238
  version: '0'
238
239
  requirements: []
239
240
  rubygems_version: 3.1.2
240
- signing_key:
241
+ signing_key:
241
242
  specification_version: 4
242
243
  summary: View components for Rails
243
244
  test_files: []