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.
- checksums.yaml +4 -4
- data/README.md +22 -11
- data/app/controllers/view_component/storybook/stories_controller.rb +3 -2
- data/app/views/view_component/storybook/stories/show.html.erb +3 -1
- data/config/locales/en.yml +32 -0
- data/lib/view_component/storybook.rb +2 -1
- data/lib/view_component/storybook/controls.rb +5 -1
- data/lib/view_component/storybook/controls/array_config.rb +9 -8
- data/lib/view_component/storybook/controls/boolean_config.rb +31 -0
- data/lib/view_component/storybook/controls/color_config.rb +26 -0
- data/lib/view_component/storybook/controls/control_config.rb +22 -26
- data/lib/view_component/storybook/controls/custom_config.rb +54 -0
- data/lib/view_component/storybook/controls/date_config.rb +14 -13
- data/lib/view_component/storybook/controls/number_config.rb +17 -16
- data/lib/view_component/storybook/controls/object_config.rb +11 -7
- data/lib/view_component/storybook/controls/options_config.rb +29 -7
- data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
- data/lib/view_component/storybook/controls/text_config.rb +13 -0
- data/lib/view_component/storybook/dsl.rb +1 -0
- data/lib/view_component/storybook/dsl/controls_dsl.rb +36 -46
- data/lib/view_component/storybook/dsl/legacy_controls_dsl.rb +98 -0
- data/lib/view_component/storybook/dsl/story_dsl.rb +17 -5
- data/lib/view_component/storybook/method_args.rb +16 -0
- data/lib/view_component/storybook/method_args/control_method_args.rb +88 -0
- data/lib/view_component/storybook/method_args/method_args.rb +19 -0
- data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
- data/lib/view_component/storybook/method_args/validatable_method_args.rb +50 -0
- data/lib/view_component/storybook/stories.rb +44 -7
- data/lib/view_component/storybook/story_config.rb +43 -9
- data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
- data/lib/view_component/storybook/version.rb +1 -1
- metadata +43 -18
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8ab883711eb6c712fcb3f654fdcb80586091f6da753e311f2609347d600391e
|
4
|
+
data.tar.gz: 65ba53ff31930882f7c2a30492e5444ed438be268cfbb24291160ff38f712964
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
###
|
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 "
|
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
|
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
|
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
|
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/
|
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
|
-
|
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(**
|
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?
|
@@ -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 :
|
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 <
|
6
|
+
class ArrayConfig < SimpleControlConfig
|
7
7
|
attr_reader :separator
|
8
8
|
|
9
|
-
validates :
|
9
|
+
validates :separator, presence: true
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
super(
|
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
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
9
|
+
validates :param, presence: true
|
10
10
|
|
11
|
-
|
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
|
-
@
|
18
|
-
@name = name || param.to_s.humanize.titlecase
|
13
|
+
@name = name
|
19
14
|
end
|
20
15
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
30
|
-
param
|
31
|
-
end
|
25
|
+
def param(new_param = nil)
|
26
|
+
return @param if new_param.nil?
|
32
27
|
|
33
|
-
|
34
|
-
|
28
|
+
@param = new_param
|
29
|
+
self
|
35
30
|
end
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
value
|
32
|
+
def to_csf_params
|
33
|
+
# :nocov:
|
34
|
+
raise NotImplementedError
|
35
|
+
# :nocov:
|
42
36
|
end
|
43
37
|
|
44
|
-
def
|
45
|
-
|
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 <
|
7
|
-
|
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
|
18
|
-
|
19
|
-
|
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
|
-
|
20
|
+
params_value
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
24
|
private
|
26
25
|
|
27
26
|
def csf_value
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|