view_component_storybook 0.3.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -11
  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 -1
  7. data/lib/view_component/storybook/controls.rb +5 -1
  8. data/lib/view_component/storybook/controls/array_config.rb +9 -8
  9. data/lib/view_component/storybook/controls/boolean_config.rb +31 -0
  10. data/lib/view_component/storybook/controls/color_config.rb +26 -0
  11. data/lib/view_component/storybook/controls/control_config.rb +22 -26
  12. data/lib/view_component/storybook/controls/custom_config.rb +54 -0
  13. data/lib/view_component/storybook/controls/date_config.rb +14 -13
  14. data/lib/view_component/storybook/controls/number_config.rb +17 -16
  15. data/lib/view_component/storybook/controls/object_config.rb +11 -7
  16. data/lib/view_component/storybook/controls/options_config.rb +29 -7
  17. data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
  18. data/lib/view_component/storybook/controls/text_config.rb +13 -0
  19. data/lib/view_component/storybook/dsl.rb +1 -0
  20. data/lib/view_component/storybook/dsl/controls_dsl.rb +36 -46
  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 +44 -7
  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 +43 -18
  33. data/lib/view_component/storybook/controls/simple_config.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69b5a85793ed8d4ecfe45be21c90a95894f001403ea9a6c086c9822e5b4fca4d
4
- data.tar.gz: 15b8f5b6430c46b5a3cd453602c4705dea94784b574f6d3a1a9b1ab39c40d1bb
3
+ metadata.gz: f8ab883711eb6c712fcb3f654fdcb80586091f6da753e311f2609347d600391e
4
+ data.tar.gz: 65ba53ff31930882f7c2a30492e5444ed438be268cfbb24291160ff38f712964
5
5
  SHA512:
6
- metadata.gz: '091149f57b05dc6bf82b1dac987ab6dbaaa6655e8c0833517eb575d8ce8abb05e75b8919a63799ad4efaa5da1722fbf5bee7f34c4680a12f969c6996532e53f7'
7
- data.tar.gz: bf0f10bc68bc35197ca9485efbc3fe18bcad98b110df969f3219daeaeb4797f1c1e7c6eb4ce3aeb737246571227c91c466b4d7c63dd4ff141c0a3962b47caf29
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
- 3. Add `require "view_component_storybook/engine"` to `config/application.rb`
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
@@ -29,7 +42,7 @@ The ViewComponent::Storybook gem provides Ruby api for writing stories describin
29
42
  "storybook": "start-storybook"
30
43
  }
31
44
  }
32
- ```
45
+ ```
33
46
  3. Create the .storybook/main.js file to configure Storybook to find the json stories the gem creates. Also configure the Controls addon:
34
47
  ```javascript
35
48
  module.exports = {
@@ -48,9 +61,7 @@ The ViewComponent::Storybook gem provides Ruby api for writing stories describin
48
61
  },
49
62
  };
50
63
  ```
51
-
52
64
 
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
65
 
55
66
  ## Usage
56
67
 
@@ -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
+
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "active_model"
4
4
  require "active_support/dependencies/autoload"
5
- require "view_component/storybook/engine"
6
5
 
7
6
  module ViewComponent
8
7
  module Storybook
@@ -11,7 +10,9 @@ module ViewComponent
11
10
  autoload :Controls
12
11
  autoload :Stories
13
12
  autoload :StoryConfig
13
+ autoload :ControlMethodArgs
14
14
  autoload :Dsl
15
+ autoload :MethodArgs
15
16
 
16
17
  include ActiveSupport::Configurable
17
18
  # Set the location of component previews through app configuration:
@@ -8,12 +8,16 @@ module ViewComponent
8
8
  extend ActiveSupport::Autoload
9
9
 
10
10
  autoload :ControlConfig
11
- autoload :SimpleConfig
11
+ autoload :SimpleControlConfig
12
+ autoload :TextConfig
13
+ autoload :BooleanConfig
14
+ autoload :ColorConfig
12
15
  autoload :NumberConfig
13
16
  autoload :OptionsConfig
14
17
  autoload :ArrayConfig
15
18
  autoload :DateConfig
16
19
  autoload :ObjectConfig
20
+ autoload :CustomConfig
17
21
  end
18
22
  end
19
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
- validates :value, :separator, presence: true
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
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ class BooleanConfig < SimpleControlConfig
7
+ BOOLEAN_VALUES = [true, false].freeze
8
+
9
+ validates :default_value, inclusion: { in: BOOLEAN_VALUES }, unless: -> { default_value.nil? }
10
+
11
+ def type
12
+ :boolean
13
+ end
14
+
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
19
+ when "true"
20
+ true
21
+ when "false"
22
+ false
23
+ end
24
+ else
25
+ params_value
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ class ColorConfig < SimpleControlConfig
7
+ attr_reader :preset_colors
8
+
9
+ def initialize(default_value, preset_colors: nil, param: nil, name: nil)
10
+ super(default_value, param: param, name: name)
11
+ @preset_colors = preset_colors
12
+ end
13
+
14
+ def type
15
+ :color
16
+ end
17
+
18
+ private
19
+
20
+ def csf_control_params
21
+ super.merge(presetColors: preset_colors).compact
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -6,43 +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: ->(knob_config) { knob_config.component_params } }, unless: -> { component.nil? }
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
13
+ @name = name
19
14
  end
20
15
 
21
- def to_csf_params
22
- validate!
23
- {
24
- args: { param => csf_value },
25
- argTypes: { param => { control: csf_control_params, name: name } }
26
- }
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
27
23
  end
28
24
 
29
- def value_from_param(param)
30
- param
31
- end
25
+ def param(new_param = nil)
26
+ return @param if new_param.nil?
32
27
 
33
- def component_params
34
- @component_params ||= component && component.instance_method(:initialize).parameters.map(&:last)
28
+ @param = new_param
29
+ self
35
30
  end
36
31
 
37
- private
38
-
39
- # provide extension points for subclasses to vary the value
40
- def csf_value
41
- value
32
+ def to_csf_params
33
+ # :nocov:
34
+ raise NotImplementedError
35
+ # :nocov:
42
36
  end
43
37
 
44
- def csf_control_params
45
- { type: type }
38
+ def value_from_params(params)
39
+ # :nocov:
40
+ raise NotImplementedError
41
+ # :nocov:
46
42
  end
47
43
  end
48
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,32 +3,33 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class DateConfig < ControlConfig
7
- validates :value, presence: true
8
-
9
- def initialize(component, param, value, name: nil)
10
- 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)
11
9
  end
12
10
 
13
11
  def type
14
12
  :date
15
13
  end
16
14
 
17
- def value_from_param(param)
18
- if param.is_a?(String)
19
- 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)
20
19
  else
21
- super(param)
20
+ params_value
22
21
  end
23
22
  end
24
23
 
25
24
  private
26
25
 
27
26
  def csf_value
28
- params_value = value
29
- params_value = params_value.in_time_zone if params_value.is_a?(Date)
30
- params_value = params_value.iso8601 if params_value.is_a?(Time)
31
- params_value
27
+ case default_value
28
+ when Date
29
+ default_value.in_time_zone
30
+ when Time
31
+ default_value.iso8601
32
+ end
32
33
  end
33
34
  end
34
35
  end