lib/view_component/preview.rb
# frozen_string_literal: true require "active_support/descendants_tracker" module ViewComponent # :nodoc: class Preview if defined?(Rails.application.routes.url_helpers) # Workaround from https://stackoverflow.com/questions/20853526/make-yard-ignore-certain-class-extensions to appease YARD send(:include, Rails.application.routes.url_helpers) end include ActionView::Helpers::TagHelper include ActionView::Helpers::AssetTagHelper extend ActiveSupport::DescendantsTracker def render(component, **args, &block) { args: args, block: block, component: component, locals: {}, template: "view_components/preview" } end def render_with_template(template: nil, locals: {}) { template: template, locals: locals } end alias_method :render_component, :render class << self # Returns all component preview classes. def all load_previews descendants end # Returns the arguments for rendering of the component in its layout def render_args(example, params: {}) example_params_names = instance_method(example).parameters.map(&:last) provided_params = params.slice(*example_params_names).to_h.symbolize_keys result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params) result ||= {} result[:template] = preview_example_template_path(example) if result[:template].nil? @layout = nil unless defined?(@layout) result.merge(layout: @layout) end # Returns all of the available examples for the component preview. def examples public_instance_methods(false).map(&:to_s).sort end # Returns +true+ if the preview exists. def exists?(preview) all.any? { |p| p.preview_name == preview } end # Find a component preview by its underscored class name. def find(preview) all.find { |p| p.preview_name == preview } end # Returns the underscored name of the component preview without the suffix. def preview_name name.chomp("Preview").underscore end # rubocop:disable Style/TrivialAccessors # Setter for layout name. def layout(layout_name) @layout = layout_name end # rubocop:enable Style/TrivialAccessors # Returns the relative path (from preview_path) to the preview example template if the template exists def preview_example_template_path(example) preview_path = Array(preview_paths).detect do |path| Dir["#{path}/#{preview_name}_preview/#{example}.html.*"].first end raise MissingPreviewTemplateError.new(example) if preview_path.nil? path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first Pathname.new(path) .relative_path_from(Pathname.new(preview_path)) .to_s .sub(/\..*$/, "") end # Returns the method body for the example from the preview file. def preview_source(example) source = instance_method(example.to_sym).source.split("\n") source[1...(source.size - 1)].join("\n") end def load_previews Array(preview_paths).each do |preview_path| Dir["#{preview_path}/**/*preview.rb"].sort.each { |file| require_dependency file } end end private def preview_paths Base.preview_paths end end end end