Skip to content

Commit 51f9d49

Browse files
Add experimental support for default slot values (#2063)
* Add support for default slot values Closes #1861 #614 * Fix final line endings * add test case for overriding slot default value * lints * look up slot default method at compile time * fix indent * lints --------- Co-authored-by: GitHub Actions Bot <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 570d46c commit 51f9d49

File tree

9 files changed

+112
-4
lines changed

9 files changed

+112
-4
lines changed

docs/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ nav_order: 5
1010

1111
## main
1212

13+
* Add experimental `SlotableDefault` module, allowing components to define a `default_SLOTNAME` method to provide a default value for slots.
14+
15+
*Joel Hawksley*
16+
1317
* Add documentation on rendering ViewComponents outside of the view context.
1418

1519
*Joel Hawksley*

docs/guide/slots.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,36 @@ end
349349
```
350350

351351
The setters are now `#with_icon_visual` and `#with_avatar_visual` instead of the default `#with_visual_icon` and `#with_visual_avatar`. The slot getter remains `#visual`.
352+
353+
## `#default_SLOT_NAME`
354+
355+
Since 3.14.0
356+
{: .label }
357+
358+
To provide a default value for a slot, include the experimental `SlotableDefault` module and define a `default_SLOT_NAME` method:
359+
360+
```ruby
361+
class SlotableDefaultComponent < ViewComponent::Base
362+
include SlotableDefault
363+
364+
renders_one :header
365+
366+
def default_header
367+
"Hello, World!"
368+
end
369+
end
370+
```
371+
372+
`default_SLOT_NAME` can also return a component instance to be rendered:
373+
374+
```ruby
375+
class SlotableDefaultInstanceComponent < ViewComponent::Base
376+
include SlotableDefault
377+
378+
renders_one :header
379+
380+
def default_header
381+
MyComponent.new
382+
end
383+
end
384+
```

lib/view_component/base.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
require "view_component/inline_template"
1111
require "view_component/preview"
1212
require "view_component/slotable"
13+
require "view_component/slotable_default"
1314
require "view_component/translatable"
1415
require "view_component/with_content_helper"
1516
require "view_component/use_helpers"

lib/view_component/compiler.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ def #{method_name}
8686
define_render_template_for
8787
end
8888

89+
component_class.registered_slots.each do |slot_name, config|
90+
config[:default_method] = component_class.instance_methods.find { |method_name| method_name == :"default_#{slot_name}" }
91+
92+
component_class.registered_slots[slot_name] = config
93+
end
94+
8995
component_class.build_i18n_backend
9096

9197
CompileCache.register(component_class)

lib/view_component/slotable.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,7 @@ def register_slot(slot_name, **kwargs)
266266
end
267267

268268
def define_slot(slot_name, collection:, callable:)
269-
# Setup basic slot data
270-
slot = {
271-
collection: collection
272-
}
269+
slot = {collection: collection}
273270
return slot unless callable
274271

275272
# If callable responds to `render_in`, we set it on the slot as a renderable
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module ViewComponent
2+
module SlotableDefault
3+
def get_slot(slot_name)
4+
@__vc_set_slots ||= {}
5+
6+
return super unless !@__vc_set_slots[slot_name] && (default_method = registered_slots[slot_name][:default_method])
7+
8+
renderable_value = send(default_method)
9+
slot = Slot.new(self)
10+
11+
if renderable_value.respond_to?(:render_in)
12+
slot.__vc_component_instance = renderable_value
13+
else
14+
slot.__vc_content = renderable_value
15+
end
16+
17+
slot
18+
end
19+
end
20+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class SlotableDefaultComponent < ViewComponent::Base
2+
include ViewComponent::SlotableDefault
3+
4+
erb_template <<~ERB
5+
<h1><%= header %></h1>
6+
ERB
7+
8+
renders_one :header
9+
10+
def default_header
11+
"hello,world!"
12+
end
13+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class SlotableDefaultInstanceComponent < ViewComponent::Base
2+
include ViewComponent::SlotableDefault
3+
4+
erb_template <<~ERB
5+
<h1><%= header %></h1>
6+
ERB
7+
8+
renders_one :header
9+
10+
def default_header
11+
MyComponent.new
12+
end
13+
end

test/sandbox/test/slotable_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,4 +764,25 @@ def test_forwarded_slot_renders_correctly
764764

765765
assert_text "Target content", count: 1
766766
end
767+
768+
def test_slotable_default
769+
render_inline(SlotableDefaultComponent.new)
770+
771+
assert_text "hello,world!", count: 1
772+
end
773+
774+
def test_slotable_default_override
775+
component = SlotableDefaultComponent.new
776+
component.with_header_content("foo")
777+
778+
render_inline(component)
779+
780+
assert_text "foo", count: 1
781+
end
782+
783+
def test_slotable_default_instance
784+
render_inline(SlotableDefaultInstanceComponent.new)
785+
786+
assert_text "hello,world!", count: 1
787+
end
767788
end

0 commit comments

Comments
 (0)