view_component_storybook 0.9.0 → 0.10.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 +6 -138
- data/app/controllers/view_component/storybook/stories_controller.rb +9 -11
- data/app/views/view_component/storybook/stories/show.html.erb +8 -3
- data/config/locales/en.yml +1 -1
- data/lib/view_component/storybook.rb +14 -2
- data/lib/view_component/storybook/content_concern.rb +42 -0
- data/lib/view_component/storybook/controls.rb +3 -1
- data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
- data/lib/view_component/storybook/controls/control_config.rb +4 -0
- data/lib/view_component/storybook/{dsl/controls_dsl.rb → controls/controls_helpers.rb} +21 -16
- data/lib/view_component/storybook/controls/custom_config.rb +2 -4
- data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
- data/lib/view_component/storybook/controls/options_config.rb +12 -26
- data/lib/view_component/storybook/controls/simple_control_config.rb +1 -1
- data/lib/view_component/storybook/dsl.rb +0 -2
- data/lib/view_component/storybook/dsl/legacy_controls_dsl.rb +2 -2
- data/lib/view_component/storybook/engine.rb +2 -1
- data/lib/view_component/storybook/method_args/control_method_args.rb +15 -12
- data/lib/view_component/storybook/method_args/method_args.rb +37 -4
- data/lib/view_component/storybook/slots.rb +14 -0
- data/lib/view_component/storybook/slots/slot.rb +24 -0
- data/lib/view_component/storybook/slots/slot_config.rb +44 -0
- data/lib/view_component/storybook/stories.rb +16 -7
- data/lib/view_component/storybook/story.rb +18 -0
- data/lib/view_component/storybook/story_config.rb +115 -24
- data/lib/view_component/storybook/tasks/write_stories_json.rake +1 -1
- data/lib/view_component/storybook/version.rb +1 -1
- metadata +27 -9
- data/lib/view_component/storybook/controls/array_config.rb +0 -37
- data/lib/view_component/storybook/dsl/story_dsl.rb +0 -51
- data/lib/view_component/storybook/method_args/validatable_method_args.rb +0 -50
@@ -9,7 +9,7 @@ module ViewComponent
|
|
9
9
|
validate :validate_value_method_args
|
10
10
|
|
11
11
|
def with_value(*args, **kwargs, &block)
|
12
|
-
@value_method_args =
|
12
|
+
@value_method_args = MethodArgs::ControlMethodArgs.new(block, *args, **kwargs)
|
13
13
|
@value_method_args.with_param_prefix(param)
|
14
14
|
self
|
15
15
|
end
|
@@ -35,9 +35,7 @@ module ViewComponent
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def value_from_params(params)
|
38
|
-
|
39
|
-
|
40
|
-
method_args.block.call(*method_args.args, **method_args.kwargs)
|
38
|
+
value_method_args.call(params)
|
41
39
|
end
|
42
40
|
|
43
41
|
private
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module Controls
|
6
|
+
class MultiOptionsConfig < BaseOptionsConfig
|
7
|
+
TYPES = %i[multi-select check inline-check].freeze
|
8
|
+
|
9
|
+
validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
|
10
|
+
validate :validate_default_value, unless: -> { options.nil? || default_value.nil? }
|
11
|
+
|
12
|
+
def initialize(type, options, default_value, labels: nil, param: nil, name: nil)
|
13
|
+
super(type, options, Array.wrap(default_value), labels: labels, param: param, name: name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def value_from_params(params)
|
17
|
+
params_value = super(params)
|
18
|
+
|
19
|
+
if params_value.is_a?(String)
|
20
|
+
params_value = params_value.split(',')
|
21
|
+
params_value = params_value.map(&:to_sym) if symbol_values
|
22
|
+
end
|
23
|
+
params_value
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_csf_params
|
27
|
+
super.deep_merge(argTypes: { param => { options: options } })
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def csf_control_params
|
33
|
+
labels.nil? ? super : super.merge(labels: labels)
|
34
|
+
end
|
35
|
+
|
36
|
+
def symbol_values
|
37
|
+
@symbol_values ||= default_value.first.is_a?(Symbol)
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_default_value
|
41
|
+
errors.add(:default_value, :inclusion) unless default_value.to_set <= options.to_set
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -3,33 +3,11 @@
|
|
3
3
|
module ViewComponent
|
4
4
|
module Storybook
|
5
5
|
module Controls
|
6
|
-
class OptionsConfig <
|
7
|
-
|
8
|
-
# support the options being a Hash or an Array. Storybook supports either.
|
9
|
-
def inclusion_in(config)
|
10
|
-
case config.options
|
11
|
-
when Hash
|
12
|
-
config.options.values
|
13
|
-
when Array
|
14
|
-
config.options
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
TYPES = %i[select multi-select radio inline-radio check inline-check].freeze
|
6
|
+
class OptionsConfig < BaseOptionsConfig
|
7
|
+
TYPES = %i[select radio inline-radio].freeze
|
20
8
|
|
21
|
-
attr_reader :type, :options, :symbol_value
|
22
|
-
|
23
|
-
validates :type, :options, presence: true
|
24
9
|
validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
|
25
|
-
validates :default_value, inclusion: { in:
|
26
|
-
|
27
|
-
def initialize(type, options, default_value, param: nil, name: nil)
|
28
|
-
super(default_value, param: param, name: name)
|
29
|
-
@type = type
|
30
|
-
@options = options
|
31
|
-
@symbol_value = default_value.is_a?(Symbol)
|
32
|
-
end
|
10
|
+
validates :default_value, inclusion: { in: ->(config) { config.options } }, unless: -> { options.nil? || default_value.nil? }
|
33
11
|
|
34
12
|
def value_from_params(params)
|
35
13
|
params_value = super(params)
|
@@ -40,10 +18,18 @@ module ViewComponent
|
|
40
18
|
end
|
41
19
|
end
|
42
20
|
|
21
|
+
def to_csf_params
|
22
|
+
super.deep_merge(argTypes: { param => { options: options } })
|
23
|
+
end
|
24
|
+
|
43
25
|
private
|
44
26
|
|
45
27
|
def csf_control_params
|
46
|
-
super.merge(
|
28
|
+
labels.nil? ? super : super.merge(labels: labels)
|
29
|
+
end
|
30
|
+
|
31
|
+
def symbol_value
|
32
|
+
@symbol_value ||= default_value.is_a?(Symbol)
|
47
33
|
end
|
48
34
|
end
|
49
35
|
end
|
@@ -56,8 +56,8 @@ module ViewComponent
|
|
56
56
|
controls << Controls::OptionsConfig.new(:'inline-check', options, value, param: param, name: name)
|
57
57
|
end
|
58
58
|
|
59
|
-
def array(param, value,
|
60
|
-
controls << Controls::
|
59
|
+
def array(param, value, _separator = nil, name: nil)
|
60
|
+
controls << Controls::ObjectConfig.new(value, param: param, name: name)
|
61
61
|
end
|
62
62
|
|
63
63
|
def date(param, value, name: nil)
|
@@ -12,6 +12,7 @@ module ViewComponent
|
|
12
12
|
options = app.config.view_component_storybook
|
13
13
|
|
14
14
|
options.show_stories = Rails.env.development? if options.show_stories.nil?
|
15
|
+
options.stories_route ||= ViewComponent::Storybook.stories_route
|
15
16
|
|
16
17
|
if options.show_stories
|
17
18
|
options.stories_path ||= defined?(Rails.root) ? Rails.root.join("test/components/stories") : nil
|
@@ -33,7 +34,7 @@ module ViewComponent
|
|
33
34
|
|
34
35
|
if options.show_stories
|
35
36
|
app.routes.prepend do
|
36
|
-
get "
|
37
|
+
get "#{options.stories_route}/*stories/:story" => "view_component/storybook/stories#show", :internal => true
|
37
38
|
end
|
38
39
|
end
|
39
40
|
end
|
@@ -6,9 +6,9 @@ module ViewComponent
|
|
6
6
|
##
|
7
7
|
# Class representing arguments passed to a method which can be validated
|
8
8
|
# against the args of the target method
|
9
|
-
#
|
9
|
+
# In addition the args and kwargs can contain Controls the values of which can
|
10
10
|
# be resolved from a params hash
|
11
|
-
class ControlMethodArgs <
|
11
|
+
class ControlMethodArgs < MethodArgs
|
12
12
|
include ActiveModel::Validations::Callbacks
|
13
13
|
|
14
14
|
attr_reader :param_prefix
|
@@ -21,6 +21,15 @@ module ViewComponent
|
|
21
21
|
self
|
22
22
|
end
|
23
23
|
|
24
|
+
##
|
25
|
+
# resolve the controls values from the params
|
26
|
+
# call the target method or block with those values
|
27
|
+
def call(params, &target_block)
|
28
|
+
method_args = resolve_method_args(params)
|
29
|
+
|
30
|
+
(target_block || target_method).call(*method_args.args, **method_args.kwargs)
|
31
|
+
end
|
32
|
+
|
24
33
|
def resolve_method_args(params)
|
25
34
|
assign_control_params
|
26
35
|
|
@@ -31,7 +40,7 @@ module ViewComponent
|
|
31
40
|
value_from_params(arg, params)
|
32
41
|
end
|
33
42
|
|
34
|
-
MethodArgs.new(args_from_params, kwargs_from_params
|
43
|
+
MethodArgs.new(target_method, *args_from_params, **kwargs_from_params)
|
35
44
|
end
|
36
45
|
|
37
46
|
def controls
|
@@ -59,21 +68,15 @@ module ViewComponent
|
|
59
68
|
|
60
69
|
arg.param(param) if arg.param.nil? # don't overrite if set
|
61
70
|
# Always add prefix
|
62
|
-
arg.
|
71
|
+
arg.prefix_param(param_prefix) if param_prefix.present?
|
63
72
|
end
|
64
73
|
|
65
74
|
def value_from_params(arg, params)
|
66
|
-
|
67
|
-
value = arg.value_from_params(params)
|
68
|
-
value = arg.default_value if value.nil? # nil only not falsey
|
69
|
-
value
|
70
|
-
else
|
71
|
-
arg
|
72
|
-
end
|
75
|
+
control?(arg) ? arg.value_from_params(params) : arg
|
73
76
|
end
|
74
77
|
|
75
78
|
def control?(arg)
|
76
|
-
arg.is_a?(
|
79
|
+
arg.is_a?(Controls::ControlConfig)
|
77
80
|
end
|
78
81
|
|
79
82
|
def validate_controls
|
@@ -4,14 +4,47 @@ module ViewComponent
|
|
4
4
|
module Storybook
|
5
5
|
module MethodArgs
|
6
6
|
##
|
7
|
-
#
|
7
|
+
# Class representing arguments passed to a method which can be validated
|
8
|
+
# against the args of the target method
|
8
9
|
class MethodArgs
|
9
|
-
|
10
|
+
include ActiveModel::Validations
|
10
11
|
|
11
|
-
|
12
|
+
attr_reader :args, :kwargs, :target_method, :target_method_params_names
|
13
|
+
|
14
|
+
validate :validate_args, :validate_kwargs
|
15
|
+
|
16
|
+
def initialize(target_method, *args, **kwargs)
|
17
|
+
@target_method = target_method
|
12
18
|
@args = args
|
13
19
|
@kwargs = kwargs
|
14
|
-
@
|
20
|
+
@target_method_params_names = MethodParametersNames.new(target_method)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def validate_args
|
26
|
+
arg_count = args.count
|
27
|
+
|
28
|
+
if arg_count > target_method_params_names.max_arg_count
|
29
|
+
errors.add(:args, :too_many, max: target_method_params_names.max_arg_count, count: arg_count)
|
30
|
+
elsif arg_count < target_method_params_names.min_arg_count
|
31
|
+
errors.add(:args, :too_few, min: target_method_params_names.min_arg_count, count: arg_count)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_kwargs
|
36
|
+
kwargs.each_key do |kwarg|
|
37
|
+
unless target_method_params_names.include_kwarg?(kwarg)
|
38
|
+
errors.add(:kwargs, :invalid_arg, kwarg: kwarg)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return if target_method_params_names.covers_required_kwargs?(kwargs.keys)
|
43
|
+
|
44
|
+
expected_keys = target_method_params_names.req_kwarg_names.join(', ')
|
45
|
+
actual_keys = kwargs.keys.join(', ')
|
46
|
+
|
47
|
+
errors.add(:kwargs, :invalid, expected_keys: expected_keys, actual_keys: actual_keys)
|
15
48
|
end
|
16
49
|
end
|
17
50
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module Slots
|
6
|
+
class Slot
|
7
|
+
attr_reader :component, :slot_name, :slot_method_args, :content_block
|
8
|
+
|
9
|
+
# delegate :args, :kwargs, :controls, to: :slot_method_args
|
10
|
+
|
11
|
+
def initialize(component, slot_name, slot_method_args, content_block)
|
12
|
+
@component = component
|
13
|
+
@slot_name = slot_name
|
14
|
+
@slot_method_args = slot_method_args
|
15
|
+
@content_block = content_block
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(&block)
|
19
|
+
component.send(slot_name, *slot_method_args.args, **slot_method_args.kwargs, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module Slots
|
6
|
+
class SlotConfig
|
7
|
+
include ContentConcern
|
8
|
+
|
9
|
+
attr_reader :slot_name, :slot_method_args, :param, :content_block
|
10
|
+
|
11
|
+
def initialize(slot_name, slot_method_args, param, content_block)
|
12
|
+
@slot_name = slot_name
|
13
|
+
@slot_method_args = slot_method_args
|
14
|
+
@param = param
|
15
|
+
@content_block = content_block
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_component(component_class, slot_name, param, *args, **kwargs, &block)
|
19
|
+
slot_method_args =
|
20
|
+
MethodArgs::ControlMethodArgs
|
21
|
+
.new(component_class.instance_method(slot_name), *args, **kwargs)
|
22
|
+
.with_param_prefix(param)
|
23
|
+
new(slot_name, slot_method_args, param, block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def slot(componeont, params)
|
27
|
+
resolved_method_args = slot_method_args.resolve_method_args(params)
|
28
|
+
story_content_block = resolve_content_block(params)
|
29
|
+
Slot.new(componeont, slot_name, resolved_method_args, story_content_block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def controls
|
33
|
+
list = slot_method_args.controls.dup
|
34
|
+
list << content_control if content_control
|
35
|
+
list
|
36
|
+
end
|
37
|
+
|
38
|
+
def content_param
|
39
|
+
"#{param}__content".to_sym
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -7,19 +7,28 @@ module ViewComponent
|
|
7
7
|
include ActiveModel::Validations
|
8
8
|
|
9
9
|
class_attribute :story_configs, default: []
|
10
|
-
class_attribute :
|
10
|
+
class_attribute :stories_parameters, :stories_title, :stories_layout
|
11
11
|
|
12
12
|
validate :validate_story_configs
|
13
13
|
|
14
14
|
class << self
|
15
|
+
def title(title = nil)
|
16
|
+
# if no argument is passed act like a getter
|
17
|
+
self.stories_title = title unless title.nil?
|
18
|
+
stories_title
|
19
|
+
end
|
20
|
+
|
15
21
|
def story(name, component = default_component, &block)
|
16
|
-
story_config = StoryConfig.
|
22
|
+
story_config = StoryConfig.new(story_id(name), name, component, layout, &block)
|
23
|
+
story_config.instance_eval(&block)
|
17
24
|
story_configs << story_config
|
18
25
|
story_config
|
19
26
|
end
|
20
27
|
|
21
|
-
def parameters(
|
22
|
-
|
28
|
+
def parameters(params = nil)
|
29
|
+
# if no argument is passed act like a getter
|
30
|
+
self.stories_parameters = params unless params.nil?
|
31
|
+
stories_parameters
|
23
32
|
end
|
24
33
|
|
25
34
|
def layout(layout = nil)
|
@@ -60,7 +69,7 @@ module ViewComponent
|
|
60
69
|
end
|
61
70
|
|
62
71
|
# Find a component stories by its underscored class name.
|
63
|
-
def
|
72
|
+
def find_story_configs(stories_name)
|
64
73
|
all.find { |stories| stories.stories_name == stories_name }
|
65
74
|
end
|
66
75
|
|
@@ -70,7 +79,7 @@ module ViewComponent
|
|
70
79
|
end
|
71
80
|
|
72
81
|
# find the story by name
|
73
|
-
def
|
82
|
+
def find_story_config(name)
|
74
83
|
story_configs.find { |config| config.name == name.to_sym }
|
75
84
|
end
|
76
85
|
|
@@ -92,7 +101,7 @@ module ViewComponent
|
|
92
101
|
def inherited(other)
|
93
102
|
super(other)
|
94
103
|
# setup class defaults
|
95
|
-
other.
|
104
|
+
other.stories_title = other.stories_name.humanize.titlecase
|
96
105
|
other.story_configs = []
|
97
106
|
end
|
98
107
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
class Story
|
6
|
+
include ActiveModel::Validations
|
7
|
+
|
8
|
+
attr_reader :component, :content_block, :slots, :layout
|
9
|
+
|
10
|
+
def initialize(component, content_block, slots, layout)
|
11
|
+
@component = component
|
12
|
+
@content_block = content_block
|
13
|
+
@slots = slots
|
14
|
+
@layout = layout
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|