view_component 2.11.1 → 2.15.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 +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +204 -8
- data/app/controllers/view_components_controller.rb +11 -2
- data/lib/view_component.rb +1 -0
- data/lib/view_component/base.rb +12 -0
- data/lib/view_component/engine.rb +35 -12
- data/lib/view_component/preview.rb +33 -4
- data/lib/view_component/preview_template_error.rb +6 -0
- data/lib/view_component/previewable.rb +4 -1
- data/lib/view_component/render_component_helper.rb +9 -0
- data/lib/view_component/render_component_to_string_helper.rb +9 -0
- data/lib/view_component/rendering_component_helper.rb +9 -0
- data/lib/view_component/slot.rb +7 -0
- data/lib/view_component/slotable.rb +121 -0
- data/lib/view_component/test_helpers.rb +7 -2
- data/lib/view_component/version.rb +2 -2
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e01b3b2899158831379c51c72086d84ca838b829a9638eeaec381aa287fa8c8
|
4
|
+
data.tar.gz: fab254768f2f88651ed2c89e9e09ef5fa7e50524dad061435e5abd4e7de1a4ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1ce90df482ab031bf57b808ec1cd0015774d3061733ae4e934d18a8b569e0aa6d22ac71107016ca0ab73f76acb720c7113d21daba2a74a5d313f53ae245e435
|
7
|
+
data.tar.gz: 96e33d8a65235bf8831ce07aec2f13b5bec2617be30c2a11cca2fa969516d167be4eaf2390a6296db2f1e61f1523115e2fd53aa6af1e39a6d661f85a03baf626
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,50 @@
|
|
1
1
|
# master
|
2
2
|
|
3
|
+
# 2.15.0
|
4
|
+
|
5
|
+
* Add support for templates as ViewComponent::Preview examples.
|
6
|
+
|
7
|
+
*Juan Manuel Ramallo
|
8
|
+
|
9
|
+
# 2.14.1
|
10
|
+
|
11
|
+
* 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.
|
12
|
+
|
13
|
+
*Clément Joubert*
|
14
|
+
|
15
|
+
* Fix kwargs warnings in slotable.
|
16
|
+
|
17
|
+
Fixes:
|
18
|
+
|
19
|
+
```
|
20
|
+
view_component/lib/view_component/slotable.rb:98: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
|
21
|
+
view_component/test/app/components/slots_component.rb:18: warning: The called method `initialize' is defined here
|
22
|
+
```
|
23
|
+
|
24
|
+
*Eileen M. Uchitelle*
|
25
|
+
|
26
|
+
# 2.14.0
|
27
|
+
|
28
|
+
* Add `config.preview_paths` to support multiple locations of component preview files. Deprecate `config.preview_path`.
|
29
|
+
|
30
|
+
*Tomas Celizna*
|
31
|
+
|
32
|
+
* Only print warning about a missing capybara dependency if the `DEBUG` environment variable is set.
|
33
|
+
|
34
|
+
*Richard Macklin*
|
35
|
+
|
36
|
+
# 2.13.0
|
37
|
+
|
38
|
+
* Add the ability to disable the render monkey patch with `config.view_component.render_monkey_patch_enabled`. In versions of Rails < 6.1, add `render_component` and `render_component_to_string` methods which can be used for rendering components instead of `render`.
|
39
|
+
|
40
|
+
*Johannes Engl*
|
41
|
+
|
42
|
+
# 2.12.0
|
43
|
+
|
44
|
+
* Implement Slots as potential successor to Content Areas.
|
45
|
+
|
46
|
+
*Jens Ljungblad, Brian Bugh, Jon Palmer, Joel Hawksley*
|
47
|
+
|
3
48
|
# 2.11.1
|
4
49
|
|
5
50
|
* Fix kwarg warnings in Ruby 2.7.
|
data/README.md
CHANGED
@@ -160,6 +160,133 @@ Returning:
|
|
160
160
|
</div>
|
161
161
|
```
|
162
162
|
|
163
|
+
#### Slots (experimental)
|
164
|
+
|
165
|
+
_Slots are currently under development as a successor to Content Areas. The Slot APIs should be considered unfinished and subject to breaking changes in non-major releases of ViewComponent._
|
166
|
+
|
167
|
+
Slots enable multiple blocks of content to be passed to a single ViewComponent.
|
168
|
+
|
169
|
+
Slots exist in two forms: normal slots and collection slots.
|
170
|
+
|
171
|
+
Normal slots can be rendered once per component. They expose an accessor with the name of the slot that returns an instance of `ViewComponent::Slot`, etc.
|
172
|
+
|
173
|
+
Collection slots can be rendered multiple times. They expose an accessor with the pluralized name of the slot (`#rows`), which is an Array of `ViewComponent::Slot` instances.
|
174
|
+
|
175
|
+
To learn more about the design of the Slots API, see https://github.com/github/view_component/pull/348.
|
176
|
+
|
177
|
+
##### Defining Slots
|
178
|
+
|
179
|
+
Slots are defined by the `with_slot` macro:
|
180
|
+
|
181
|
+
`with_slot :header`
|
182
|
+
|
183
|
+
To define a collection slot, add `collection: true`:
|
184
|
+
|
185
|
+
`with_slot :row, collection: true`
|
186
|
+
|
187
|
+
To define a slot with a custom class, pass `class_name`:
|
188
|
+
|
189
|
+
`with_slot :body, class_name: 'BodySlot`
|
190
|
+
|
191
|
+
Slot classes should be subclasses of `ViewComponent::Slot`.
|
192
|
+
|
193
|
+
##### Example ViewComponent with Slots
|
194
|
+
|
195
|
+
`# box_component.rb`
|
196
|
+
```ruby
|
197
|
+
class BoxComponent < ViewComponent::Base
|
198
|
+
include ViewComponent::Slotable
|
199
|
+
|
200
|
+
with_slot :body, :footer
|
201
|
+
with_slot :header, class_name: "Header"
|
202
|
+
with_slot :row, collection: true, class_name: "Row"
|
203
|
+
|
204
|
+
class Header < ViewComponent::Slot
|
205
|
+
def initialize(class_names: "")
|
206
|
+
@class_names = class_names
|
207
|
+
end
|
208
|
+
|
209
|
+
def class_names
|
210
|
+
"Box-header #{@class_names}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class Row < ViewComponent::Slot
|
215
|
+
def initialize(theme: :gray)
|
216
|
+
@theme = theme
|
217
|
+
end
|
218
|
+
|
219
|
+
def theme_class_name
|
220
|
+
case @theme
|
221
|
+
when :gray
|
222
|
+
"Box-row--gray"
|
223
|
+
when :hover_gray
|
224
|
+
"Box-row--hover-gray"
|
225
|
+
when :yellow
|
226
|
+
"Box-row--yellow"
|
227
|
+
when :blue
|
228
|
+
"Box-row--blue"
|
229
|
+
when :hover_blue
|
230
|
+
"Box-row--hover-blue"
|
231
|
+
else
|
232
|
+
"Box-row--gray"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
`# box_component.html.erb`
|
240
|
+
```erb
|
241
|
+
<div class="Box">
|
242
|
+
<% if header %>
|
243
|
+
<div class="<%= header.class_names %>">
|
244
|
+
<%= header.content %>
|
245
|
+
</div>
|
246
|
+
<% end %>
|
247
|
+
<% if body %>
|
248
|
+
<div class="Box-body">
|
249
|
+
<%= body.content %>
|
250
|
+
</div>
|
251
|
+
<% end %>
|
252
|
+
<% if rows.any? %>
|
253
|
+
<ul>
|
254
|
+
<% rows.each do |row| %>
|
255
|
+
<li class="Box-row <%= row.theme_class_name %>">
|
256
|
+
<%= row.content %>
|
257
|
+
</li>
|
258
|
+
<% end %>
|
259
|
+
</ul>
|
260
|
+
<% end %>
|
261
|
+
<% if footer %>
|
262
|
+
<div class="Box-footer">
|
263
|
+
<%= footer %>
|
264
|
+
</div>
|
265
|
+
<% end %>
|
266
|
+
</div>
|
267
|
+
```
|
268
|
+
|
269
|
+
`# index.html.erb`
|
270
|
+
```erb
|
271
|
+
<%= render(BoxComponent.new) do |component| %>
|
272
|
+
<% component.slot(:header, class_names: "my-class-name") do %>
|
273
|
+
This is my header!
|
274
|
+
<% end %>
|
275
|
+
<% component.slot(:body) do %>
|
276
|
+
This is the body.
|
277
|
+
<% end %>
|
278
|
+
<% component.slot(:row) do %>
|
279
|
+
Row one
|
280
|
+
<% end %>
|
281
|
+
<% component.slot(:row, theme: :yellow) do %>
|
282
|
+
Yellow row
|
283
|
+
<% end %>
|
284
|
+
<% component.slot(:footer) do %>
|
285
|
+
This is the footer.
|
286
|
+
<% end %>
|
287
|
+
<% end %>
|
288
|
+
```
|
289
|
+
|
163
290
|
### Inline Component
|
164
291
|
|
165
292
|
ViewComponents can render without a template file, by defining a `call` method:
|
@@ -537,11 +664,11 @@ class TestComponentPreview < ViewComponent::Preview
|
|
537
664
|
end
|
538
665
|
```
|
539
666
|
|
540
|
-
Preview classes live in `test/components/previews`, which can be configured using the `
|
667
|
+
Preview classes live in `test/components/previews`, which can be configured using the `preview_paths` option:
|
541
668
|
|
542
669
|
`config/application.rb`
|
543
670
|
```ruby
|
544
|
-
config.view_component.
|
671
|
+
config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
545
672
|
```
|
546
673
|
|
547
674
|
Previews are served from <http://localhost:3000/rails/view_components> by default. To use a different endpoint, set the `preview_route` option:
|
@@ -553,6 +680,57 @@ config.view_component.preview_route = "/previews"
|
|
553
680
|
|
554
681
|
This example will make the previews available from <http://localhost:3000/previews>.
|
555
682
|
|
683
|
+
#### Preview templates
|
684
|
+
|
685
|
+
Given a preview `test/components/previews/cell_component_preview.rb`, template files can be defined at `test/components/previews/cell_component_preview/`:
|
686
|
+
|
687
|
+
`test/components/previews/cell_component_preview.rb`
|
688
|
+
```ruby
|
689
|
+
class CellComponentPreview < ViewComponent::Preview
|
690
|
+
def default
|
691
|
+
end
|
692
|
+
end
|
693
|
+
```
|
694
|
+
|
695
|
+
`test/components/previews/cell_component_preview/default.html.erb`
|
696
|
+
```erb
|
697
|
+
<table class="table">
|
698
|
+
<tbody>
|
699
|
+
<tr>
|
700
|
+
<%= render CellComponent.new %>
|
701
|
+
</tr>
|
702
|
+
</tbody>
|
703
|
+
</div>
|
704
|
+
```
|
705
|
+
|
706
|
+
To use a different location for preview templates, pass the `template` argument:
|
707
|
+
(the path should be relative to `config.view_component.preview_path`):
|
708
|
+
|
709
|
+
`test/components/previews/cell_component_preview.rb`
|
710
|
+
```ruby
|
711
|
+
class CellComponentPreview < ViewComponent::Preview
|
712
|
+
def default
|
713
|
+
render_with_template(template: 'custom_cell_component_preview/my_preview_template')
|
714
|
+
end
|
715
|
+
end
|
716
|
+
```
|
717
|
+
|
718
|
+
Values from `params` can be accessed through `locals`:
|
719
|
+
|
720
|
+
`test/components/previews/cell_component_preview.rb`
|
721
|
+
```ruby
|
722
|
+
class CellComponentPreview < ViewComponent::Preview
|
723
|
+
def default(title: "Default title", subtitle: "A subtitle")
|
724
|
+
render_with_template(locals: {
|
725
|
+
title: title,
|
726
|
+
subtitle: subtitle
|
727
|
+
})
|
728
|
+
end
|
729
|
+
end
|
730
|
+
```
|
731
|
+
|
732
|
+
Which enables passing in a value with <http://localhost:3000/rails/components/cell_component/default?title=Custom+title&subtitle=Another+subtitle>.
|
733
|
+
|
556
734
|
#### Configuring TestController
|
557
735
|
|
558
736
|
Component tests and previews assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
|
@@ -581,7 +759,19 @@ To use component previews:
|
|
581
759
|
|
582
760
|
`config/application.rb`
|
583
761
|
```ruby
|
584
|
-
config.view_component.
|
762
|
+
config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
|
763
|
+
```
|
764
|
+
|
765
|
+
### Disabling the render monkey patch (Rails < 6.1)
|
766
|
+
|
767
|
+
In order to [avoid conflicts](https://github.com/github/view_component/issues/288) between ViewComponent and other gems that also monkey patch the `render` method, it is possible to configure ViewComponent to not include the render monkey patch:
|
768
|
+
|
769
|
+
`config.view_component.render_monkey_patch_enabled = false # defaults to true`
|
770
|
+
|
771
|
+
With the monkey patch disabled, use `render_component` (or `render_component_to_string`) instead:
|
772
|
+
|
773
|
+
```
|
774
|
+
<%= render_component Component.new(message: "bar") %>
|
585
775
|
```
|
586
776
|
|
587
777
|
### Sidecar assets (experimental)
|
@@ -731,7 +921,8 @@ ViewComponent is far from a novel idea! Popular implementations of view componen
|
|
731
921
|
## Resources
|
732
922
|
|
733
923
|
- [Encapsulating Views, RailsConf 2020](https://youtu.be/YVYRus_2KZM)
|
734
|
-
- [Rethinking the View Layer with Components
|
924
|
+
- [Rethinking the View Layer with Components, Ruby Rogues Podcast](https://devchat.tv/ruby-rogues/rr-461-rethinking-the-view-layer-with-components-with-joel-hawksley/)
|
925
|
+
- [ViewComponents in Action with Andrew Mason, Ruby on Rails Podcast](https://5by5.tv/rubyonrails/320)
|
735
926
|
- [ViewComponent at GitHub with Joel Hawksley](https://the-ruby-blend.fireside.fm/9)
|
736
927
|
- [Components, HAML vs ERB, and Design Systems](https://the-ruby-blend.fireside.fm/4)
|
737
928
|
- [Choosing the Right Tech Stack with Dave Paola](https://5by5.tv/rubyonrails/307)
|
@@ -784,10 +975,15 @@ ViewComponent is built by:
|
|
784
975
|
|@simonrand|@fugufish|@cover|@franks921|@fsateler|
|
785
976
|
|Dublin, Ireland|Salt Lake City, Utah|Barcelona|South Africa|Chile|
|
786
977
|
|
787
|
-
|<img src="https://avatars.githubusercontent.com/maxbeizer?s=256" alt="maxbeizer" width="128" />|<img src="https://avatars.githubusercontent.com/franco?s=256" alt="franco" width="128" />|<img src="https://avatars.githubusercontent.com/tbroad-ramsey?s=256" alt="tbroad-ramsey" width="128" />|
|
788
|
-
|
789
|
-
|@maxbeizer|@franco|@tbroad-ramsey|
|
790
|
-
|Nashville, TN|Switzerland|Spring Hill, TN|
|
978
|
+
|<img src="https://avatars.githubusercontent.com/maxbeizer?s=256" alt="maxbeizer" width="128" />|<img src="https://avatars.githubusercontent.com/franco?s=256" alt="franco" width="128" />|<img src="https://avatars.githubusercontent.com/tbroad-ramsey?s=256" alt="tbroad-ramsey" width="128" />|<img src="https://avatars.githubusercontent.com/jensljungblad?s=256" alt="jensljungblad" width="128" />|<img src="https://avatars.githubusercontent.com/bbugh?s=256" alt="bbugh" width="128" />|
|
979
|
+
|:---:|:---:|:---:|:---:|:---:|
|
980
|
+
|@maxbeizer|@franco|@tbroad-ramsey|@jensljungblad|@bbugh|
|
981
|
+
|Nashville, TN|Switzerland|Spring Hill, TN|New York, NY|Austin, TX|
|
982
|
+
|
983
|
+
|<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" />|
|
984
|
+
|:---:|:---:|
|
985
|
+
|@johannesengl|@czj|
|
986
|
+
|Berlin, Germany|Paris, France|
|
791
987
|
|
792
988
|
## License
|
793
989
|
|
@@ -24,11 +24,16 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc:
|
|
24
24
|
render "view_components/previews"
|
25
25
|
else
|
26
26
|
prepend_application_view_paths
|
27
|
+
prepend_preview_examples_view_path
|
27
28
|
@example_name = File.basename(params[:path])
|
28
29
|
@render_args = @preview.render_args(@example_name, params: params.permit!)
|
29
30
|
layout = @render_args[:layout]
|
30
|
-
|
31
|
-
|
31
|
+
template = @render_args[:template]
|
32
|
+
locals = @render_args[:locals]
|
33
|
+
opts = {}
|
34
|
+
opts[:layout] = layout if layout.present?
|
35
|
+
opts[:locals] = locals if locals.present?
|
36
|
+
render template, opts # rubocop:disable GitHub/RailsControllerRenderLiteral
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
@@ -59,4 +64,8 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc:
|
|
59
64
|
def prepend_application_view_paths
|
60
65
|
prepend_view_path Rails.root.join("app/views") if defined?(Rails.root)
|
61
66
|
end
|
67
|
+
|
68
|
+
def prepend_preview_examples_view_path
|
69
|
+
prepend_view_path(ViewComponent::Base.preview_paths)
|
70
|
+
end
|
62
71
|
end
|
data/lib/view_component.rb
CHANGED
data/lib/view_component/base.rb
CHANGED
@@ -5,6 +5,7 @@ require "active_support/configurable"
|
|
5
5
|
require "view_component/collection"
|
6
6
|
require "view_component/compile_cache"
|
7
7
|
require "view_component/previewable"
|
8
|
+
require "view_component/slotable"
|
8
9
|
|
9
10
|
module ViewComponent
|
10
11
|
class Base < ActionView::Base
|
@@ -17,6 +18,10 @@ module ViewComponent
|
|
17
18
|
class_attribute :content_areas
|
18
19
|
self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
|
19
20
|
|
21
|
+
# Hash of registered Slots
|
22
|
+
class_attribute :slots
|
23
|
+
self.slots = {}
|
24
|
+
|
20
25
|
# Entrypoint for rendering components.
|
21
26
|
#
|
22
27
|
# view_context: ActionView context from calling view
|
@@ -157,6 +162,9 @@ module ViewComponent
|
|
157
162
|
mattr_accessor :test_controller
|
158
163
|
@@test_controller = "ApplicationController"
|
159
164
|
|
165
|
+
# Configure if render monkey patches should be included or not in Rails <6.1.
|
166
|
+
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
167
|
+
|
160
168
|
class << self
|
161
169
|
attr_accessor :source_location
|
162
170
|
|
@@ -181,6 +189,10 @@ module ViewComponent
|
|
181
189
|
# has been re-defined by the consuming application, likely in ApplicationComponent.
|
182
190
|
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
|
183
191
|
|
192
|
+
# Clone slot configuration into child class
|
193
|
+
# see #test_slots_pollution
|
194
|
+
child.slots = self.slots.clone
|
195
|
+
|
184
196
|
super
|
185
197
|
end
|
186
198
|
|
@@ -6,15 +6,24 @@ require "view_component"
|
|
6
6
|
module ViewComponent
|
7
7
|
class Engine < Rails::Engine # :nodoc:
|
8
8
|
config.view_component = ActiveSupport::OrderedOptions.new
|
9
|
+
config.view_component.preview_paths ||= []
|
9
10
|
|
10
11
|
initializer "view_component.set_configs" do |app|
|
11
12
|
options = app.config.view_component
|
12
13
|
|
14
|
+
options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
|
13
15
|
options.show_previews = Rails.env.development? if options.show_previews.nil?
|
14
16
|
options.preview_route ||= ViewComponent::Base.preview_route
|
15
17
|
|
16
18
|
if options.show_previews
|
17
|
-
options.
|
19
|
+
options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root)
|
20
|
+
|
21
|
+
if options.preview_path.present?
|
22
|
+
ActiveSupport::Deprecation.warn(
|
23
|
+
"`preview_path` will be removed in v3.0.0. Use `preview_paths` instead."
|
24
|
+
)
|
25
|
+
options.preview_paths << options.preview_path
|
26
|
+
end
|
18
27
|
end
|
19
28
|
|
20
29
|
ActiveSupport.on_load(:view_component) do
|
@@ -42,21 +51,35 @@ module ViewComponent
|
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
45
|
-
initializer "view_component.monkey_patch_render" do
|
54
|
+
initializer "view_component.monkey_patch_render" do |app|
|
55
|
+
next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled
|
56
|
+
|
46
57
|
ActiveSupport.on_load(:action_view) do
|
47
|
-
|
48
|
-
|
49
|
-
ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
|
50
|
-
end
|
58
|
+
require "view_component/render_monkey_patch"
|
59
|
+
ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
|
51
60
|
end
|
52
61
|
|
53
62
|
ActiveSupport.on_load(:action_controller) do
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
63
|
+
require "view_component/rendering_monkey_patch"
|
64
|
+
require "view_component/render_to_string_monkey_patch"
|
65
|
+
ActionController::Base.prepend ViewComponent::RenderingMonkeyPatch
|
66
|
+
ActionController::Base.prepend ViewComponent::RenderToStringMonkeyPatch
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
initializer "view_component.include_render_component" do |app|
|
71
|
+
next if Rails.version.to_f >= 6.1
|
72
|
+
|
73
|
+
ActiveSupport.on_load(:action_view) do
|
74
|
+
require "view_component/render_component_helper"
|
75
|
+
ActionView::Base.include ViewComponent::RenderComponentHelper
|
76
|
+
end
|
77
|
+
|
78
|
+
ActiveSupport.on_load(:action_controller) do
|
79
|
+
require "view_component/rendering_component_helper"
|
80
|
+
require "view_component/render_component_to_string_helper"
|
81
|
+
ActionController::Base.include ViewComponent::RenderingComponentHelper
|
82
|
+
ActionController::Base.include ViewComponent::RenderComponentToStringHelper
|
60
83
|
end
|
61
84
|
end
|
62
85
|
|
@@ -8,7 +8,20 @@ module ViewComponent # :nodoc:
|
|
8
8
|
extend ActiveSupport::DescendantsTracker
|
9
9
|
|
10
10
|
def render(component, **args, &block)
|
11
|
-
{
|
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,16 +77,30 @@ 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
|
68
|
-
|
97
|
+
Array(preview_paths).each do |preview_path|
|
69
98
|
Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
|
70
99
|
end
|
71
100
|
end
|
72
101
|
|
73
|
-
def
|
74
|
-
Base.
|
102
|
+
def preview_paths
|
103
|
+
Base.preview_paths
|
75
104
|
end
|
76
105
|
|
77
106
|
def show_previews
|
@@ -9,8 +9,11 @@ module ViewComponent # :nodoc:
|
|
9
9
|
included do
|
10
10
|
# Set the location of component previews through app configuration:
|
11
11
|
#
|
12
|
-
# config.view_component.
|
12
|
+
# config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
13
13
|
#
|
14
|
+
mattr_accessor :preview_paths, instance_writer: false
|
15
|
+
|
16
|
+
# TODO: deprecated, remove in v3.0.0
|
14
17
|
mattr_accessor :preview_path, instance_writer: false
|
15
18
|
|
16
19
|
# Enable or disable component previews through app configuration:
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
require "view_component/slot"
|
6
|
+
|
7
|
+
module ViewComponent
|
8
|
+
module Slotable
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
# support initializing slots as:
|
13
|
+
#
|
14
|
+
# with_slot(
|
15
|
+
# :header,
|
16
|
+
# collection: true|false,
|
17
|
+
# class_name: "Header" # class name string, used to instantiate Slot
|
18
|
+
# )
|
19
|
+
def with_slot(*slot_names, collection: false, class_name: nil)
|
20
|
+
slot_names.each do |slot_name|
|
21
|
+
# Ensure slot_name is not already declared
|
22
|
+
if self.slots.key?(slot_name)
|
23
|
+
raise ArgumentError.new("#{slot_name} slot declared multiple times")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Ensure slot name is not :content
|
27
|
+
if slot_name == :content
|
28
|
+
raise ArgumentError.new ":content is a reserved slot name. Please use another name, such as ':body'"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set the name of the method used to access the Slot(s)
|
32
|
+
accessor_name =
|
33
|
+
if collection
|
34
|
+
# If Slot is a collection, set the accessor
|
35
|
+
# name to the pluralized form of the slot name
|
36
|
+
# For example: :tab => :tabs
|
37
|
+
ActiveSupport::Inflector.pluralize(slot_name)
|
38
|
+
else
|
39
|
+
slot_name
|
40
|
+
end
|
41
|
+
|
42
|
+
instance_variable_name = "@#{accessor_name}"
|
43
|
+
|
44
|
+
# If the slot is a collection, define an accesor that defaults to an empty array
|
45
|
+
if collection
|
46
|
+
class_eval <<-RUBY
|
47
|
+
def #{accessor_name}
|
48
|
+
#{instance_variable_name} ||= []
|
49
|
+
end
|
50
|
+
RUBY
|
51
|
+
else
|
52
|
+
attr_reader accessor_name
|
53
|
+
end
|
54
|
+
|
55
|
+
# Default class_name to ViewComponent::Slot
|
56
|
+
class_name = "ViewComponent::Slot" unless class_name.present?
|
57
|
+
|
58
|
+
# Register the slot on the component
|
59
|
+
self.slots[slot_name] = {
|
60
|
+
class_name: class_name,
|
61
|
+
instance_variable_name: instance_variable_name,
|
62
|
+
collection: collection
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Build a Slot instance on a component,
|
69
|
+
# exposing it for use inside the
|
70
|
+
# component template.
|
71
|
+
#
|
72
|
+
# slot: Name of Slot, in symbol form
|
73
|
+
# **args: Arguments to be passed to Slot initializer
|
74
|
+
#
|
75
|
+
# For example:
|
76
|
+
# <%= render(SlotsComponent.new) do |component| %>
|
77
|
+
# <% component.slot(:footer, class_names: "footer-class") do %>
|
78
|
+
# <p>This is my footer!</p>
|
79
|
+
# <% end %>
|
80
|
+
# <% end %>
|
81
|
+
#
|
82
|
+
def slot(slot_name, **args, &block)
|
83
|
+
# Raise ArgumentError if `slot` does not exist
|
84
|
+
unless slots.keys.include?(slot_name)
|
85
|
+
raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'"
|
86
|
+
end
|
87
|
+
|
88
|
+
slot = slots[slot_name]
|
89
|
+
|
90
|
+
# The class name of the Slot, such as Header
|
91
|
+
slot_class = self.class.const_get(slot[:class_name])
|
92
|
+
|
93
|
+
unless slot_class <= ViewComponent::Slot
|
94
|
+
raise ArgumentError.new "#{slot[:class_name]} must inherit from ViewComponent::Slot"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Instantiate Slot class, accommodating Slots that don't accept arguments
|
98
|
+
slot_instance = args.present? ? slot_class.new(**args) : slot_class.new
|
99
|
+
|
100
|
+
# Capture block and assign to slot_instance#content
|
101
|
+
slot_instance.content = view_context.capture(&block) if block_given?
|
102
|
+
|
103
|
+
if slot[:collection]
|
104
|
+
# Initialize instance variable as an empty array
|
105
|
+
# if slot is a collection and has yet to be initialized
|
106
|
+
unless instance_variable_defined?(slot[:instance_variable_name])
|
107
|
+
instance_variable_set(slot[:instance_variable_name], [])
|
108
|
+
end
|
109
|
+
|
110
|
+
# Append Slot instance to collection accessor Array
|
111
|
+
instance_variable_get(slot[:instance_variable_name]) << slot_instance
|
112
|
+
else
|
113
|
+
# Assign the Slot instance to the slot accessor
|
114
|
+
instance_variable_set(slot[:instance_variable_name], slot_instance)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Return nil, as this method should not output anything to the view itself.
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -14,13 +14,18 @@ module ViewComponent
|
|
14
14
|
assert_no_selector("body")
|
15
15
|
end
|
16
16
|
rescue LoadError
|
17
|
-
warn "WARNING in `ViewComponent::TestHelpers`: You must add `capybara` to your Gemfile to use Capybara assertions."
|
17
|
+
warn "WARNING in `ViewComponent::TestHelpers`: You must add `capybara` to your Gemfile to use Capybara assertions." if ENV["DEBUG"]
|
18
18
|
end
|
19
19
|
|
20
20
|
attr_reader :rendered_component
|
21
21
|
|
22
22
|
def render_inline(component, **args, &block)
|
23
|
-
@rendered_component =
|
23
|
+
@rendered_component =
|
24
|
+
if Rails.version.to_f >= 6.1
|
25
|
+
controller.view_context.render(component, args, &block)
|
26
|
+
else
|
27
|
+
controller.view_context.render_component(component, &block)
|
28
|
+
end
|
24
29
|
|
25
30
|
Nokogiri::HTML.fragment(@rendered_component)
|
26
31
|
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.
|
4
|
+
version: 2.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -203,10 +203,16 @@ 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
|
208
|
+
- lib/view_component/render_component_helper.rb
|
209
|
+
- lib/view_component/render_component_to_string_helper.rb
|
207
210
|
- lib/view_component/render_monkey_patch.rb
|
208
211
|
- lib/view_component/render_to_string_monkey_patch.rb
|
212
|
+
- lib/view_component/rendering_component_helper.rb
|
209
213
|
- lib/view_component/rendering_monkey_patch.rb
|
214
|
+
- lib/view_component/slot.rb
|
215
|
+
- lib/view_component/slotable.rb
|
210
216
|
- lib/view_component/template_error.rb
|
211
217
|
- lib/view_component/test_case.rb
|
212
218
|
- lib/view_component/test_helpers.rb
|