view_component_storybook 0.3.0 → 0.9.0

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