lib/view_component_contrib/preview/base.rb
# frozen_string_literal: true module ViewComponentContrib module Preview # First, enable abstract classes (if not already extended) unless ViewComponent::Preview.singleton_class.is_a?(ViewComponentContrib::Preview::Abstract) ViewComponent::Preview.extend ViewComponentContrib::Preview::Abstract end # Base view component class with extensions already included class Base < ViewComponent::Preview self.abstract_class = true include DefaultTemplate DEFAULT_CONTAINER_CLASS = "" class << self # Support layout inheritance def inherited(child) child.layout(@layout) if defined?(@layout) super end attr_writer :container_class def container_class return @container_class if defined?(@container_class) @container_class = if superclass.respond_to?(:container_class) superclass.container_class else DEFAULT_CONTAINER_CLASS end end def render_args(...) super.tap do |res| res[:locals] ||= {} build_component_instance(res[:locals]) res[:locals][:container_class] ||= container_class end end # Infer component class name from preview class name: # - Namespace::ButtonPreview => Namespace::Button::Component | Namespace::ButtonComponent | Namespace::Button # - Button::Preview => Button::Component | ButtonComponent | Button def component_class_name @component_class_name ||= begin component_name = name.sub(/(::Preview|Preview)$/, "") [ "#{component_name}::Component", "#{component_name}Component", component_name ].find do _1.safe_constantize end end end attr_writer :component_class_name private def build_component_instance(locals) return locals unless locals[:component].nil? locals[:component] = component_class_name.safe_constantize&.new rescue => e locals[:component] = nil locals[:error] = e.message end end # Shortcut for render_with_template(locals: ...) def render_with(**locals) render_with_template(locals: locals) end # Shortcut for render_with_template(locals: {component: ...}) def render_component(component_or_props = nil, &block) component = if component_or_props.is_a?(::ViewComponent::Base) component_or_props else self.class.component_class_name.constantize.new(**(component_or_props || {})) end render_with(component: component, content_block: block) end end end end