View Component sidecar generator

Curated ago by @pedroaugustorduarte

Description

rails g view_component NomeDoComponente

with stimulus:

rails g view_component NomeDoComponente –stimulus

File:
- component.html.erb html for the component
- component.rb ruby code for internal component logic
- preview.rb preview for lookbook
- /spec/component_spec.rb spec for component with rspec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# frozen_string_literal: true

# Based on https://github.com/github/view_component/blob/master/lib/rails/generators/component/component_generator.rb
class ViewComponentGenerator < Rails::Generators::NamedBase
  source_root File.expand_path("templates", __dir__)

  class_option :skip_test, type: :boolean, default: false
  class_option :skip_preview, type: :boolean, default: false
  class_option :stimulus, type: :boolean, default: false

  argument :attributes, type: :array, default: [], banner: "attribute"

  DEFAULT_PATH = "components"
  DEFAULT_COMPONENT_ROOT = "app/#{DEFAULT_PATH}"

  def create_component_file
    template "component.rb", File.join(DEFAULT_COMPONENT_ROOT, class_path, file_name, "component.rb")
  end

  def create_template_file
    template "component.html.erb", File.join(DEFAULT_COMPONENT_ROOT, class_path, file_name, "component.html.erb")
  end

  def create_stimulus_controller
    return unless options[:stimulus]

    template "controller.js", File.join(DEFAULT_COMPONENT_ROOT, class_path, file_name, "controller.js")
  end

  def create_test_file
    return if options[:skip_test]

    template "component_spec.rb", File.join("spec/#{DEFAULT_PATH}", class_path, file_name, "component_spec.rb")
  end

  def create_preview_file
    return if options[:skip_preview]

    template "preview.rb", File.join(DEFAULT_COMPONENT_ROOT, class_path, file_name, "preview.rb")
  end

  private

  def render_signature
    return if attributes.blank?

    attributes.map { |attr| %(#{attr.name}: "#{attr.name}") }.join(", ")
  end

  def parent_class
    "ApplicationViewComponent"
  end

  def preview_parent_class
    "ApplicationViewComponentPreview"
  end

  def data_attributes
    " data-controller=\"#{stimulus_controller}\"" if options["stimulus"]
  end

  def stimulus_controller
    if options["stimulus"]
      File.join(DEFAULT_COMPONENT_ROOT, class_path, file_name, "controller.js")
          .sub("#{DEFAULT_COMPONENT_ROOT}/", "")
          .tr("_", "-")
          .gsub(".js", "")
          .gsub("/", "--")
    end
  end

  def initialize_signature
    return if attributes.blank?

    attributes.map { |attr| "#{attr.name}:" }.join(", ")
  end

  def initialize_body
    attributes.map { |attr| "@#{attr.name} = #{attr.name}" }.join("\n    ")
  end
end