view_component_storybook 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -9
  3. data/app/controllers/view_component/storybook/stories_controller.rb +3 -2
  4. data/app/views/view_component/storybook/stories/show.html.erb +3 -1
  5. data/config/locales/en.yml +32 -0
  6. data/lib/view_component/storybook.rb +2 -0
  7. data/lib/view_component/storybook/controls.rb +2 -0
  8. data/lib/view_component/storybook/controls/array_config.rb +8 -7
  9. data/lib/view_component/storybook/controls/boolean_config.rb +7 -6
  10. data/lib/view_component/storybook/controls/color_config.rb +4 -5
  11. data/lib/view_component/storybook/controls/control_config.rb +22 -38
  12. data/lib/view_component/storybook/controls/custom_config.rb +54 -0
  13. data/lib/view_component/storybook/controls/date_config.rb +14 -11
  14. data/lib/view_component/storybook/controls/number_config.rb +9 -9
  15. data/lib/view_component/storybook/controls/object_config.rb +11 -5
  16. data/lib/view_component/storybook/controls/options_config.rb +9 -8
  17. data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
  18. data/lib/view_component/storybook/controls/text_config.rb +1 -1
  19. data/lib/view_component/storybook/dsl.rb +1 -0
  20. data/lib/view_component/storybook/dsl/controls_dsl.rb +31 -61
  21. data/lib/view_component/storybook/dsl/legacy_controls_dsl.rb +98 -0
  22. data/lib/view_component/storybook/dsl/story_dsl.rb +17 -5
  23. data/lib/view_component/storybook/method_args.rb +16 -0
  24. data/lib/view_component/storybook/method_args/control_method_args.rb +88 -0
  25. data/lib/view_component/storybook/method_args/method_args.rb +19 -0
  26. data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
  27. data/lib/view_component/storybook/method_args/validatable_method_args.rb +50 -0
  28. data/lib/view_component/storybook/stories.rb +43 -0
  29. data/lib/view_component/storybook/story_config.rb +43 -9
  30. data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
  31. data/lib/view_component/storybook/version.rb +1 -1
  32. metadata +24 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e8c8a3875d64fe89649af8851324d2a07bd40868065abce8b57cf848cbeb5d3
4
- data.tar.gz: 554f43665400373ba30b9bc6e307285843b67d3760e5957e0088fa2ba53b325e
3
+ metadata.gz: f8ab883711eb6c712fcb3f654fdcb80586091f6da753e311f2609347d600391e
4
+ data.tar.gz: 65ba53ff31930882f7c2a30492e5444ed438be268cfbb24291160ff38f712964
5
5
  SHA512:
6
- metadata.gz: 398124fee399213bea34acf3aef434877eb01d0c76d9d6f127f5d5493981ecf5ac1f0ddc94ad9850354f6ec3629f1a9e5ca6005b57208abd39d045026415abbd
7
- data.tar.gz: 31a4a06293f40d19e575243a61cb61e1cff2bd7646da950c01de20f8edd73d1179970a7700064a8ee15cbcd3a73b2060f525bdbf427baeaddc9f9bd9ee6b7ebe
6
+ metadata.gz: 24bf672f2214344c1d5b9e1af7bbe8ef51ddb8ba6065eca4b12b14e0c8b73f69381168b39d3f4339e953a086ec11d69f716f3fce7dfb8acd7efaabd3678cb578
7
+ data.tar.gz: 0df0d064cccf1ed1d625ba1d9cb439d8c3fb65f56a7671e3d65650dc85604ea89990e578ca4550863388d7cfc201b7cd33b7add24f294deb6646b1753062c03a
data/README.md CHANGED
@@ -1,21 +1,34 @@
1
1
  # ViewComponent::Storybook
2
2
 
3
- The ViewComponent::Storybook gem provides Ruby api for writing stories describing [View Components](https://github.com/github/view_component) and allowing them to be previewed and tested in [Storybook](https://github.com/storybookjs/storybook/)
3
+ The ViewComponent::Storybook gem provides Ruby api for writing stories describing [View Components](https://github.com/github/view_component) and allowing them to be previewed and tested in [Storybook](https://github.com/storybookjs/storybook/) via its [Server](https://github.com/storybookjs/storybook/tree/next/app/server) support.
4
4
 
5
5
  ## Features
6
6
  * A Ruby DSL for writing Stories describing View Components
7
- * A Rails controller backend for Storybook Server compatible with Strobook Controls Addon parameters
7
+ * A Rails controller backend for Storybook Server compatible with Storybook Controls Addon parameters
8
8
  * Coming Soon: Rake tasks to watch View Components and Stories and trigger Storybook hot reloading
9
9
 
10
10
  ## Installation
11
11
 
12
- ### Gem Installation
12
+ ### Rails Installation
13
13
 
14
14
  1. Add the `view_component_storybook` gem, to your Gemfile: `gem 'view_component_storybook'`
15
15
  2. Run `bundle install`.
16
16
  3. Add `require "view_component/storybook/engine"` to `config/application.rb`
17
17
  4. Add `**/*.stories.json` to `.gitignore`
18
18
 
19
+ #### Configure Asset Hosts
20
+
21
+ If your view components depend on Javascript, CSS or other assets served by the Rails application you will need to configure `asset_hosts`
22
+ apporpriately for your various environments. For local development this is a simple as adding to `config/development.rb`:
23
+ ```ruby
24
+ Rails.application.configure do
25
+ ...
26
+ config.action_controller.asset_host = 'http://localhost:3000'
27
+ ...
28
+ end
29
+ ```
30
+ Equivalent configuration will be necessary in `config/production.rb` or `application.rb` based you your deployment environments.
31
+
19
32
  ### Storybook Installation
20
33
 
21
34
  1. Add Storybook server as a dev dependedncy. The Storybook Controls addon isn't needed but is strongly recommended
@@ -50,8 +63,6 @@ The ViewComponent::Storybook gem provides Ruby api for writing stories describin
50
63
  ```
51
64
 
52
65
 
53
- Note: `@storybook/server` will be part of the upcoming Storybook 6.0 release. Until that is released you'll need to use an [rc release](https://github.com/storybookjs/storybook/releases/tag/v6.0.0-rc.14)
54
-
55
66
  ## Usage
56
67
 
57
68
  ### Writing Stories
@@ -68,7 +79,7 @@ class ButtonComponent < ViewComponent::Base
68
79
  end
69
80
  ```
70
81
 
71
- We can write a stories desecibing the `ButtonComponent`
82
+ We can write a stories describing the `ButtonComponent`
72
83
 
73
84
  ```ruby
74
85
  class ButtonComponentStories < ViewComponent::Storybook::Stories
@@ -88,7 +99,7 @@ end
88
99
 
89
100
  ### Generating Storybook Stories JSON
90
101
 
91
- Generate the Storybook JSON stories by tunning the rake task:
102
+ Generate the Storybook JSON stories by running the rake task:
92
103
  ```sh
93
104
  rake view_component_storybook:write_stories_json
94
105
  ```
@@ -108,7 +119,7 @@ Alternatively you can use tools like [Foreman](https://github.com/ddollar/forema
108
119
 
109
120
  ### Configuration
110
121
 
111
- By Default ViewComponent::Storybook expects to find stories in the folder `test/components/stories`. This can be configured but setting `stories_path` in `config/applicaion.rb`. For example is you're using RSpec you might set the following configuration:
122
+ By Default ViewComponent::Storybook expects to find stories in the folder `test/components/stories`. This can be configured by setting `stories_path` in `config/application.rb`. For example if you're using RSpec you might set the following configuration:
112
123
 
113
124
  ```ruby
114
125
  config.view_component_storybook.stories_path = Rails.root.join("spec/components/stories")
@@ -131,7 +142,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
131
142
 
132
143
  ## Contributing
133
144
 
134
- Bug reports and pull requests are welcome on GitHub at https://github.com/jonspalmer/actionview-component-storybook. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
145
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jonspalmer/view_component_storybook. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
135
146
 
136
147
  ## License
137
148
 
@@ -14,11 +14,12 @@ module ViewComponent
14
14
  content_security_policy(false) if respond_to?(:content_security_policy)
15
15
 
16
16
  def show
17
- component_args = @story.values_from_params(params.permit!.to_h)
17
+ params_hash = params.permit!.to_h
18
+ method_args = @story.constructor_args.resolve_method_args(params_hash)
18
19
 
19
20
  @content_block = @story.content_block
20
21
 
21
- @component = @story.component.new(**component_args)
22
+ @component = @story.component.new(*method_args.args, **method_args.kwargs)
22
23
 
23
24
  layout = @story.layout
24
25
  render layout: layout unless layout.nil?
@@ -1 +1,3 @@
1
- <%= render(@component, &@content_block)%>
1
+ <%= render(@component) do -%>
2
+ <%= instance_eval(&@content_block) -%>
3
+ <% end %>
@@ -0,0 +1,32 @@
1
+ en:
2
+ activemodel:
3
+ errors:
4
+ models:
5
+ view_component/storybook/method_args/validatable_method_args:
6
+ attributes:
7
+ args:
8
+ too_few: expected at least %{min} but found %{count}
9
+ too_many: expected no more than %{max} but found %{count}
10
+ kwargs:
11
+ invalid_arg: "'%{kwarg}' is invalid"
12
+ invalid: expected keys [%{expected_keys}] but found [%{actual_keys}]
13
+ view_component/storybook/method_args/control_method_args:
14
+ attributes:
15
+ controls:
16
+ invalid_control: "'%{control_name}' is invalid: (%{control_errors})"
17
+ view_component/storybook/stories:
18
+ attributes:
19
+ story_configs:
20
+ invalid_story: "'%{story_name}' is invalid: (%{story_errors})"
21
+ duplicate_stories:
22
+ one: duplicate story name %{duplicate_names}
23
+ other: duplicate story names %{duplicate_names}
24
+ view_component/storybook/story_config:
25
+ attributes:
26
+ constructor_args:
27
+ invalid: "invalid: (%{errors})"
28
+ view_component/storybook/controls/custom_config:
29
+ attributes:
30
+ value_method_args:
31
+ invalid: "invalid: (%{errors})"
32
+
@@ -10,7 +10,9 @@ module ViewComponent
10
10
  autoload :Controls
11
11
  autoload :Stories
12
12
  autoload :StoryConfig
13
+ autoload :ControlMethodArgs
13
14
  autoload :Dsl
15
+ autoload :MethodArgs
14
16
 
15
17
  include ActiveSupport::Configurable
16
18
  # Set the location of component previews through app configuration:
@@ -8,6 +8,7 @@ module ViewComponent
8
8
  extend ActiveSupport::Autoload
9
9
 
10
10
  autoload :ControlConfig
11
+ autoload :SimpleControlConfig
11
12
  autoload :TextConfig
12
13
  autoload :BooleanConfig
13
14
  autoload :ColorConfig
@@ -16,6 +17,7 @@ module ViewComponent
16
17
  autoload :ArrayConfig
17
18
  autoload :DateConfig
18
19
  autoload :ObjectConfig
20
+ autoload :CustomConfig
19
21
  end
20
22
  end
21
23
  end
@@ -3,13 +3,13 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class ArrayConfig < ControlConfig
6
+ class ArrayConfig < SimpleControlConfig
7
7
  attr_reader :separator
8
8
 
9
9
  validates :separator, presence: true
10
10
 
11
- def initialize(component, param, value, separator = ",", name: nil)
12
- super(component, param, value, name: name)
11
+ def initialize(default_value, separator = ",", param: nil, name: nil)
12
+ super(default_value, param: param, name: name)
13
13
  @separator = separator
14
14
  end
15
15
 
@@ -17,11 +17,12 @@ module ViewComponent
17
17
  :array
18
18
  end
19
19
 
20
- def value_from_param(param)
21
- if param.is_a?(String)
22
- param.split(separator)
20
+ def value_from_params(params)
21
+ params_value = super(params)
22
+ if params_value.is_a?(String)
23
+ params_value.split(separator)
23
24
  else
24
- super(param)
25
+ params_value
25
26
  end
26
27
  end
27
28
 
@@ -3,25 +3,26 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class BooleanConfig < ControlConfig
6
+ class BooleanConfig < SimpleControlConfig
7
7
  BOOLEAN_VALUES = [true, false].freeze
8
8
 
9
- validates :value, inclusion: { in: BOOLEAN_VALUES }, unless: -> { value.nil? }
9
+ validates :default_value, inclusion: { in: BOOLEAN_VALUES }, unless: -> { default_value.nil? }
10
10
 
11
11
  def type
12
12
  :boolean
13
13
  end
14
14
 
15
- def value_from_param(param)
16
- if param.is_a?(String) && param.present?
17
- case param
15
+ def value_from_params(params)
16
+ params_value = super(params)
17
+ if params_value.is_a?(String) && params_value.present?
18
+ case params_value
18
19
  when "true"
19
20
  true
20
21
  when "false"
21
22
  false
22
23
  end
23
24
  else
24
- super(param)
25
+ params_value
25
26
  end
26
27
  end
27
28
  end
@@ -3,11 +3,11 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class ColorConfig < ControlConfig
6
+ class ColorConfig < SimpleControlConfig
7
7
  attr_reader :preset_colors
8
8
 
9
- def initialize(component, param, value, name: nil, preset_colors: nil)
10
- super(component, param, value, name: name)
9
+ def initialize(default_value, preset_colors: nil, param: nil, name: nil)
10
+ super(default_value, param: param, name: name)
11
11
  @preset_colors = preset_colors
12
12
  end
13
13
 
@@ -18,8 +18,7 @@ module ViewComponent
18
18
  private
19
19
 
20
20
  def csf_control_params
21
- params = super
22
- params.merge(presetColors: preset_colors).compact
21
+ super.merge(presetColors: preset_colors).compact
23
22
  end
24
23
  end
25
24
  end
@@ -6,55 +6,39 @@ module ViewComponent
6
6
  class ControlConfig
7
7
  include ActiveModel::Validations
8
8
 
9
- attr_reader :component, :param, :value, :name
9
+ validates :param, presence: true
10
10
 
11
- validates :component, :param, presence: true
12
- validates :param, inclusion: { in: ->(control_config) { control_config.component_param_names } }, if: :should_validate_params?
13
-
14
- def initialize(component, param, value, name: nil)
15
- @component = component
11
+ def initialize(param: nil, name: nil)
16
12
  @param = param
17
- @value = value
18
- @name = name || param.to_s.humanize.titlecase
19
- end
20
-
21
- def to_csf_params
22
- validate!
23
- {
24
- args: { param => csf_value },
25
- argTypes: { param => { control: csf_control_params, name: name } }
26
- }
27
- end
28
-
29
- def value_from_param(param)
30
- param
13
+ @name = name
31
14
  end
32
15
 
33
- def component_param_names
34
- @component_param_names ||= component_params&.map(&:last)
16
+ def name(new_name = nil)
17
+ if new_name.nil?
18
+ @name ||= param.to_s.humanize.titlecase
19
+ else
20
+ @name = new_name
21
+ self
22
+ end
35
23
  end
36
24
 
37
- private
25
+ def param(new_param = nil)
26
+ return @param if new_param.nil?
38
27
 
39
- # provide extension points for subclasses to vary the value
40
- def csf_value
41
- value
28
+ @param = new_param
29
+ self
42
30
  end
43
31
 
44
- def csf_control_params
45
- { type: type }
46
- end
47
-
48
- def component_accepts_kwargs?
49
- component_params.map(&:first).include?(:keyrest)
50
- end
51
-
52
- def component_params
53
- @component_params ||= component.instance_method(:initialize).parameters
32
+ def to_csf_params
33
+ # :nocov:
34
+ raise NotImplementedError
35
+ # :nocov:
54
36
  end
55
37
 
56
- def should_validate_params?
57
- component.present? && !component_accepts_kwargs?
38
+ def value_from_params(params)
39
+ # :nocov:
40
+ raise NotImplementedError
41
+ # :nocov:
58
42
  end
59
43
  end
60
44
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ class CustomConfig < ControlConfig
7
+ attr_reader :value_method_args
8
+
9
+ validate :validate_value_method_args
10
+
11
+ def with_value(*args, **kwargs, &block)
12
+ @value_method_args = ViewComponent::Storybook::MethodArgs::ControlMethodArgs.new(block, *args, **kwargs, &block)
13
+ @value_method_args.with_param_prefix(param)
14
+ self
15
+ end
16
+
17
+ def param(new_param = nil)
18
+ value_method_args.with_param_prefix(new_param) unless new_param.nil?
19
+ super(new_param)
20
+ end
21
+
22
+ def to_csf_params
23
+ validate!
24
+ # TODO: Figure out if we can use 'category' with the args table
25
+ # export default {
26
+ # argTypes: {
27
+ # foo: {
28
+ # table: { category: 'cat', subcategory: 'sub' }
29
+ # }
30
+ # }
31
+ # }
32
+ value_method_args.controls.reduce({}) do |csf_params, control|
33
+ csf_params.deep_merge(control.to_csf_params)
34
+ end
35
+ end
36
+
37
+ def value_from_params(params)
38
+ method_args = value_method_args.resolve_method_args(params)
39
+
40
+ method_args.block.call(*method_args.args, **method_args.kwargs)
41
+ end
42
+
43
+ private
44
+
45
+ def validate_value_method_args
46
+ return if value_method_args.valid?
47
+
48
+ value_method_args_errors = value_method_args.errors.full_messages.join(', ')
49
+ errors.add(:value_method_args, :invalid, errors: value_method_args_errors)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -3,30 +3,33 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class DateConfig < ControlConfig
7
- def initialize(component, param, value, name: nil)
8
- super(component, param, value, name: name)
6
+ class DateConfig < SimpleControlConfig
7
+ def initialize(default_value, param: nil, name: nil)
8
+ super(default_value, param: param, name: name)
9
9
  end
10
10
 
11
11
  def type
12
12
  :date
13
13
  end
14
14
 
15
- def value_from_param(param)
16
- if param.is_a?(String)
17
- DateTime.iso8601(param)
15
+ def value_from_params(params)
16
+ params_value = super(params)
17
+ if params_value.is_a?(String)
18
+ DateTime.iso8601(params_value)
18
19
  else
19
- super(param)
20
+ params_value
20
21
  end
21
22
  end
22
23
 
23
24
  private
24
25
 
25
26
  def csf_value
26
- params_value = value
27
- params_value = params_value.in_time_zone if params_value.is_a?(Date)
28
- params_value = params_value.iso8601 if params_value.is_a?(Time)
29
- params_value
27
+ case default_value
28
+ when Date
29
+ default_value.in_time_zone
30
+ when Time
31
+ default_value.iso8601
32
+ end
30
33
  end
31
34
  end
32
35
  end