diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 9996807e2..88e6db55d 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -103,6 +103,13 @@ def render_in(view_context, &block) @__vc_content_evaluated = false @__vc_render_in_block = block + if self.class.send(:__vc_content_is_a_slot?) + @__vc_content_evaluated = true + if __vc_render_in_block_provided? + view_context.capture(self, &@__vc_render_in_block) + end + end + before_render if render? @@ -549,6 +556,10 @@ def render_template_for(variant = nil, format = nil) child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls) end + if defined?(@__vc_content_is_a_slot) + child.instance_variable_set(:@__vc_content_is_a_slot, @__vc_content_is_a_slot) + end + super end @@ -687,6 +698,19 @@ def initialize_parameters def provided_collection_parameter @provided_collection_parameter ||= nil end + + def __vc_content_is_a_slot? + defined?(@__vc_content_is_a_slot) && @__vc_content_is_a_slot + end + + def content_is_a_slot! + @__vc_content_is_a_slot = true + renders_one :content + end + + def do_not_use_content_as_a_slot! + @__vc_content_is_a_slot = false + end end ActiveSupport.run_load_hooks(:view_component, self) diff --git a/lib/view_component/slotable.rb b/lib/view_component/slotable.rb index c537d4828..320d0a4ff 100644 --- a/lib/view_component/slotable.rb +++ b/lib/view_component/slotable.rb @@ -298,8 +298,10 @@ def define_slot(slot_name, collection:, callable:) end def validate_plural_slot_name(slot_name) - if RESERVED_NAMES[:plural].include?(slot_name.to_sym) - raise ReservedPluralSlotNameError.new(name, slot_name) + if slot_name.to_sym == :contents && !__vc_content_is_a_slot? + if RESERVED_NAMES[:plural].include?(slot_name.to_sym) + raise ReservedPluralSlotNameError.new(name, slot_name) + end end raise_if_slot_name_uncountable(slot_name) @@ -309,12 +311,12 @@ def validate_plural_slot_name(slot_name) end def validate_singular_slot_name(slot_name) - if slot_name.to_sym == :content + if slot_name.to_sym == :content && !__vc_content_is_a_slot? raise ContentSlotNameError.new(name) - end - - if RESERVED_NAMES[:singular].include?(slot_name.to_sym) - raise ReservedSingularSlotNameError.new(name, slot_name) + elsif !__vc_content_is_a_slot? + if RESERVED_NAMES[:singular].include?(slot_name.to_sym) + raise ReservedSingularSlotNameError.new(name, slot_name) + end end raise_if_slot_conflicts_with_call(slot_name) diff --git a/test/sandbox/app/components/content_as_slot_component.html.erb b/test/sandbox/app/components/content_as_slot_component.html.erb new file mode 100644 index 000000000..b784aa225 --- /dev/null +++ b/test/sandbox/app/components/content_as_slot_component.html.erb @@ -0,0 +1,5 @@ +
+ <% if content? %> + <%= content %> + <% end %> +
diff --git a/test/sandbox/app/components/content_as_slot_component.rb b/test/sandbox/app/components/content_as_slot_component.rb new file mode 100644 index 000000000..dead53d50 --- /dev/null +++ b/test/sandbox/app/components/content_as_slot_component.rb @@ -0,0 +1,3 @@ +class ContentAsSlotComponent < ViewComponent::Base + content_is_a_slot! +end diff --git a/test/sandbox/app/components/content_not_a_slot_component.rb b/test/sandbox/app/components/content_not_a_slot_component.rb new file mode 100644 index 000000000..1b3c5af1a --- /dev/null +++ b/test/sandbox/app/components/content_not_a_slot_component.rb @@ -0,0 +1,3 @@ +class ContentNotASlotComponent < ViewComponent::Base + do_not_use_content_as_a_slot! +end diff --git a/test/sandbox/test/slotable_test.rb b/test/sandbox/test/slotable_test.rb index 83f44a8ce..60b271f78 100644 --- a/test/sandbox/test/slotable_test.rb +++ b/test/sandbox/test/slotable_test.rb @@ -819,4 +819,62 @@ def test_overridden_slot_name_can_be_inherited def test_slot_name_methods_are_not_shared_accross_components assert_not_equal SlotsComponent.instance_method(:title).owner, SlotNameOverrideComponent::OtherComponent.instance_method(:title).owner end + + def test_content_as_a_slot_component + component = ContentAsSlotComponent.new + render_inline(component) do |c| + c.with_content do + "The truth is out there" + end + end + + assert component.content? + assert_selector "div", text: "The truth is out there" + end + + def test_content_as_a_slot_component_with_content + component = ContentAsSlotComponent.new + component.with_content do + "The truth is out there" + end + render_inline(component) + + assert component.content? + assert_selector "div", text: "The truth is out there" + end + + def test_content_as_a_slot_inheritance + new_component_class = Class.new(ContentAsSlotComponent) + assert new_component_class.send(:__vc_content_is_a_slot?) + end + + def test_content_is_not_a_slot + new_component_class = Class.new(SlotsComponent) do + do_not_use_content_as_a_slot! + end + refute new_component_class.send(:__vc_content_is_a_slot?) + + render_inline(SlotsComponent.new) do |component| + component.with_title do + "This is my title!" + end + + component.with_subtitle do + "This is my subtitle!" + end + + component.with_footer do + "This is the footer" + end + end + + assert_text "No tabs provided" + assert_text "No items provided" + end + + def test_content_is_not_a_slot_inheritance + refute ContentNotASlotComponent.send(:__vc_content_is_a_slot?) + new_component_class = Class.new(ContentNotASlotComponent) + refute new_component_class.send(:__vc_content_is_a_slot?) + end end