view_component_storybook 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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