view_component 2.11.1 → 2.12.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c63a550faa0964130875d2670dd2d39753acd1ff2fc14ee88168573af6661619
4
- data.tar.gz: 5f54192e46fc3cbde5e0a5f2f3b6c186790f9ad2fe91627f0662457d7434d679
3
+ metadata.gz: 8661a26312dc3649e30ee17fbc94f6fd73be410067f8a7fac8b6aaefc4338d11
4
+ data.tar.gz: 1024034624c49bb65bfe1fc3466886ffa60cd9699a58c7427ea6dbedb9baa03b
5
5
  SHA512:
6
- metadata.gz: b786100a0867df76302b15c726e3dfe4dc2c60786ef34b269d9c7c8cd8fadabb695f3f83ad00a1872e1fdb5d5f112857ca32f2da998c459e2f576cbd9a163d46
7
- data.tar.gz: 4e30cd32923a51d7a29f78fa86bc11d21bec5653b60961b444819b821897ebf7a00284e76557a18c6e61b2baa6c7d78bc72033f465d8d99d271a51e84bd63895
6
+ metadata.gz: 24ddc5373c2fbb7cd81ef4f4077ea66a30ea797fbb9d4c444135d3d8590b5151596fddc18f5bc5e1f18fec65ca87572858513409385489524c11c1929cff3567
7
+ data.tar.gz: cf63eb464e88e02310cb55f9c6c779c31622174777dbd1f39a24d0e9ac40407c555ac51012c5ea8c06bdeccac7aab880475c02c678c3fbff9ef22f5ca6cf797e
@@ -1,5 +1,11 @@
1
1
  # master
2
2
 
3
+ # 2.12.0
4
+
5
+ * Implement Slots as potential successor to Content Areas.
6
+
7
+ *Jens Ljungblad, Brian Bugh, Jon Palmer, Joel Hawksley*
8
+
3
9
  # 2.11.1
4
10
 
5
11
  * 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:
@@ -731,7 +858,8 @@ ViewComponent is far from a novel idea! Popular implementations of view componen
731
858
  ## Resources
732
859
 
733
860
  - [Encapsulating Views, RailsConf 2020](https://youtu.be/YVYRus_2KZM)
734
- - [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/)
861
+ - [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/)
862
+ - [ViewComponents in Action with Andrew Mason, Ruby on Rails Podcast](https://5by5.tv/rubyonrails/320)
735
863
  - [ViewComponent at GitHub with Joel Hawksley](https://the-ruby-blend.fireside.fm/9)
736
864
  - [Components, HAML vs ERB, and Design Systems](https://the-ruby-blend.fireside.fm/4)
737
865
  - [Choosing the Right Tech Stack with Dave Paola](https://5by5.tv/rubyonrails/307)
@@ -784,10 +912,10 @@ ViewComponent is built by:
784
912
  |@simonrand|@fugufish|@cover|@franks921|@fsateler|
785
913
  |Dublin, Ireland|Salt Lake City, Utah|Barcelona|South Africa|Chile|
786
914
 
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|
915
+ |<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" />|
916
+ |:---:|:---:|:---:|:---:|:---:|
917
+ |@maxbeizer|@franco|@tbroad-ramsey|@jensljungblad|@bbugh|
918
+ |Nashville, TN|Switzerland|Spring Hill, TN|New York, NY|Austin, TX|
791
919
 
792
920
  ## License
793
921
 
@@ -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
@@ -181,6 +186,10 @@ module ViewComponent
181
186
  # has been re-defined by the consuming application, likely in ApplicationComponent.
182
187
  child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
183
188
 
189
+ # Clone slot configuration into child class
190
+ # see #test_slots_pollution
191
+ child.slots = self.slots.clone
192
+
184
193
  super
185
194
  end
186
195
 
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ class Slot
5
+ attr_accessor :content
6
+ end
7
+ end
@@ -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
@@ -3,8 +3,8 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 11
7
- PATCH = 1
6
+ MINOR = 12
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.11.1
4
+ version: 2.12.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-06-17 00:00:00.000000000 Z
11
+ date: 2020-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -207,6 +207,8 @@ files:
207
207
  - lib/view_component/render_monkey_patch.rb
208
208
  - lib/view_component/render_to_string_monkey_patch.rb
209
209
  - lib/view_component/rendering_monkey_patch.rb
210
+ - lib/view_component/slot.rb
211
+ - lib/view_component/slotable.rb
210
212
  - lib/view_component/template_error.rb
211
213
  - lib/view_component/test_case.rb
212
214
  - lib/view_component/test_helpers.rb