view_component_storybook 0.5.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 +7 -128
- data/app/controllers/view_component/storybook/stories_controller.rb +10 -11
- data/app/views/view_component/storybook/stories/show.html.erb +8 -1
- data/config/locales/en.yml +32 -0
- data/lib/view_component/storybook.rb +14 -0
- data/lib/view_component/storybook/content_concern.rb +42 -0
- data/lib/view_component/storybook/controls.rb +5 -1
- data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
- data/lib/view_component/storybook/controls/boolean_config.rb +7 -6
- data/lib/view_component/storybook/controls/color_config.rb +4 -6
- data/lib/view_component/storybook/controls/control_config.rb +25 -25
- data/lib/view_component/storybook/controls/controls_helpers.rb +76 -0
- data/lib/view_component/storybook/controls/custom_config.rb +52 -0
- data/lib/view_component/storybook/controls/date_config.rb +14 -13
- data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
- data/lib/view_component/storybook/controls/number_config.rb +13 -10
- data/lib/view_component/storybook/controls/object_config.rb +11 -7
- data/lib/view_component/storybook/controls/options_config.rb +18 -25
- data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
- data/lib/view_component/storybook/controls/text_config.rb +1 -3
- data/lib/view_component/storybook/dsl.rb +1 -2
- data/lib/view_component/storybook/dsl/{controls_dsl.rb → legacy_controls_dsl.rb} +19 -22
- data/lib/view_component/storybook/engine.rb +2 -1
- data/lib/view_component/storybook/method_args.rb +16 -0
- data/lib/view_component/storybook/method_args/control_method_args.rb +91 -0
- data/lib/view_component/storybook/method_args/method_args.rb +52 -0
- data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
- 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 +60 -14
- data/lib/view_component/storybook/story.rb +18 -0
- data/lib/view_component/storybook/story_config.rb +140 -15
- data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
- data/lib/view_component/storybook/version.rb +1 -1
- metadata +64 -23
- data/lib/view_component/storybook/controls/array_config.rb +0 -36
- data/lib/view_component/storybook/dsl/story_dsl.rb +0 -39
@@ -3,71 +3,68 @@
|
|
3
3
|
module ViewComponent
|
4
4
|
module Storybook
|
5
5
|
module Dsl
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(component)
|
10
|
-
@component = component
|
11
|
-
@controls = []
|
6
|
+
class LegacyControlsDsl
|
7
|
+
def controls
|
8
|
+
@controls ||= []
|
12
9
|
end
|
13
10
|
|
14
11
|
def text(param, value, name: nil)
|
15
|
-
controls << Controls::TextConfig.new(
|
12
|
+
controls << Controls::TextConfig.new(value, param: param, name: name)
|
16
13
|
end
|
17
14
|
|
18
15
|
def boolean(param, value, name: nil)
|
19
|
-
controls << Controls::BooleanConfig.new(
|
16
|
+
controls << Controls::BooleanConfig.new(value, param: param, name: name)
|
20
17
|
end
|
21
18
|
|
22
19
|
def number(param, value, name: nil, min: nil, max: nil, step: nil)
|
23
|
-
controls << Controls::NumberConfig.new(:number,
|
20
|
+
controls << Controls::NumberConfig.new(:number, value, param: param, name: name, min: min, max: max, step: step)
|
24
21
|
end
|
25
22
|
|
26
23
|
def range(param, value, name: nil, min: nil, max: nil, step: nil)
|
27
|
-
controls << Controls::NumberConfig.new(:range,
|
24
|
+
controls << Controls::NumberConfig.new(:range, value, param: param, name: name, min: min, max: max, step: step)
|
28
25
|
end
|
29
26
|
|
30
27
|
def color(param, value, name: nil, preset_colors: nil)
|
31
|
-
controls << Controls::ColorConfig.new(
|
28
|
+
controls << Controls::ColorConfig.new(value, param: param, name: name, preset_colors: preset_colors)
|
32
29
|
end
|
33
30
|
|
34
31
|
def object(param, value, name: nil)
|
35
|
-
controls << Controls::ObjectConfig.new(
|
32
|
+
controls << Controls::ObjectConfig.new(value, param: param, name: name)
|
36
33
|
end
|
37
34
|
|
38
35
|
def select(param, options, value, name: nil)
|
39
|
-
controls << Controls::OptionsConfig.new(:select,
|
36
|
+
controls << Controls::OptionsConfig.new(:select, options, value, param: param, name: name)
|
40
37
|
end
|
41
38
|
|
42
39
|
def multi_select(param, options, value, name: nil)
|
43
|
-
controls << Controls::OptionsConfig.new(:'multi-select',
|
40
|
+
controls << Controls::OptionsConfig.new(:'multi-select', options, value, param: param, name: name)
|
44
41
|
end
|
45
42
|
|
46
43
|
def radio(param, options, value, name: nil)
|
47
|
-
controls << Controls::OptionsConfig.new(:radio,
|
44
|
+
controls << Controls::OptionsConfig.new(:radio, options, value, param: param, name: name)
|
48
45
|
end
|
49
46
|
|
50
47
|
def inline_radio(param, options, value, name: nil)
|
51
|
-
controls << Controls::OptionsConfig.new(:'inline-radio',
|
48
|
+
controls << Controls::OptionsConfig.new(:'inline-radio', options, value, param: param, name: name)
|
52
49
|
end
|
53
50
|
|
54
51
|
def check(param, options, value, name: nil)
|
55
|
-
controls << Controls::OptionsConfig.new(:check,
|
52
|
+
controls << Controls::OptionsConfig.new(:check, options, value, param: param, name: name)
|
56
53
|
end
|
57
54
|
|
58
55
|
def inline_check(param, options, value, name: nil)
|
59
|
-
controls << Controls::OptionsConfig.new(:'inline-check',
|
56
|
+
controls << Controls::OptionsConfig.new(:'inline-check', options, value, param: param, name: name)
|
60
57
|
end
|
61
58
|
|
62
|
-
def array(param, value,
|
63
|
-
controls << Controls::
|
59
|
+
def array(param, value, _separator = nil, name: nil)
|
60
|
+
controls << Controls::ObjectConfig.new(value, param: param, name: name)
|
64
61
|
end
|
65
62
|
|
66
63
|
def date(param, value, name: nil)
|
67
|
-
controls << Controls::DateConfig.new(
|
64
|
+
controls << Controls::DateConfig.new(value, param: param, name: name)
|
68
65
|
end
|
69
66
|
|
70
|
-
def respond_to_missing?(_method)
|
67
|
+
def respond_to_missing?(_method, *)
|
71
68
|
true
|
72
69
|
end
|
73
70
|
|
@@ -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
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/dependencies/autoload"
|
4
|
+
|
5
|
+
module ViewComponent
|
6
|
+
module Storybook
|
7
|
+
module MethodArgs
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload :MethodArgs
|
11
|
+
autoload :MethodParametersNames
|
12
|
+
autoload :ValidatableMethodArgs
|
13
|
+
autoload :ControlMethodArgs
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module MethodArgs
|
6
|
+
##
|
7
|
+
# Class representing arguments passed to a method which can be validated
|
8
|
+
# against the args of the target method
|
9
|
+
# In addition the args and kwargs can contain Controls the values of which can
|
10
|
+
# be resolved from a params hash
|
11
|
+
class ControlMethodArgs < MethodArgs
|
12
|
+
include ActiveModel::Validations::Callbacks
|
13
|
+
|
14
|
+
attr_reader :param_prefix
|
15
|
+
|
16
|
+
validate :validate_controls
|
17
|
+
before_validation :assign_control_params
|
18
|
+
|
19
|
+
def with_param_prefix(prefix)
|
20
|
+
@param_prefix = prefix
|
21
|
+
self
|
22
|
+
end
|
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
|
+
|
33
|
+
def resolve_method_args(params)
|
34
|
+
assign_control_params
|
35
|
+
|
36
|
+
args_from_params = args.map do |arg|
|
37
|
+
value_from_params(arg, params)
|
38
|
+
end
|
39
|
+
kwargs_from_params = kwargs.transform_values do |arg|
|
40
|
+
value_from_params(arg, params)
|
41
|
+
end
|
42
|
+
|
43
|
+
MethodArgs.new(target_method, *args_from_params, **kwargs_from_params)
|
44
|
+
end
|
45
|
+
|
46
|
+
def controls
|
47
|
+
@controls ||= (args + kwargs.values).select(&method(:control?))
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def assign_control_params
|
53
|
+
return if @assigned_control_params
|
54
|
+
|
55
|
+
args.each_with_index do |arg, index|
|
56
|
+
add_param_if_control(arg, target_method_params_names.arg_name(index))
|
57
|
+
end
|
58
|
+
|
59
|
+
kwargs.each do |key, arg|
|
60
|
+
add_param_if_control(arg, key)
|
61
|
+
end
|
62
|
+
|
63
|
+
@assigned_control_params = true
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_param_if_control(arg, param)
|
67
|
+
return unless control?(arg)
|
68
|
+
|
69
|
+
arg.param(param) if arg.param.nil? # don't overrite if set
|
70
|
+
# Always add prefix
|
71
|
+
arg.prefix_param(param_prefix) if param_prefix.present?
|
72
|
+
end
|
73
|
+
|
74
|
+
def value_from_params(arg, params)
|
75
|
+
control?(arg) ? arg.value_from_params(params) : arg
|
76
|
+
end
|
77
|
+
|
78
|
+
def control?(arg)
|
79
|
+
arg.is_a?(Controls::ControlConfig)
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_controls
|
83
|
+
controls.reject(&:valid?).each do |control|
|
84
|
+
control_errors = control.errors.full_messages.join(', ')
|
85
|
+
errors.add(:controls, :invalid_control, control_name: control.name, control_errors: control_errors)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module MethodArgs
|
6
|
+
##
|
7
|
+
# Class representing arguments passed to a method which can be validated
|
8
|
+
# against the args of the target method
|
9
|
+
class MethodArgs
|
10
|
+
include ActiveModel::Validations
|
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
|
18
|
+
@args = args
|
19
|
+
@kwargs = kwargs
|
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)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module MethodArgs
|
6
|
+
class MethodParametersNames
|
7
|
+
REQ_KWARG_TYPE = :keyreq
|
8
|
+
KWARG_TYPES = [REQ_KWARG_TYPE, :key].freeze
|
9
|
+
REQ_ARG_TYPE = :req
|
10
|
+
ARG_TYPES = [REQ_ARG_TYPE, :opt].freeze
|
11
|
+
KWARG_REST = :keyrest
|
12
|
+
REST = :rest
|
13
|
+
|
14
|
+
attr_reader :target_method
|
15
|
+
|
16
|
+
def initialize(target_method)
|
17
|
+
@target_method = target_method
|
18
|
+
end
|
19
|
+
|
20
|
+
def arg_name(pos)
|
21
|
+
if pos < named_arg_count
|
22
|
+
arg_names[pos]
|
23
|
+
else
|
24
|
+
offset_pos = pos - named_arg_count
|
25
|
+
"#{rest_arg_name}#{offset_pos}".to_sym
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def include_kwarg?(kwarg_name)
|
30
|
+
supports_keyrest? || kwarg_names.include?(kwarg_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def covers_required_kwargs?(names)
|
34
|
+
names.to_set >= req_kwarg_names.to_set
|
35
|
+
end
|
36
|
+
|
37
|
+
def max_arg_count
|
38
|
+
supports_rest? ? Float::INFINITY : named_arg_count
|
39
|
+
end
|
40
|
+
|
41
|
+
def min_arg_count
|
42
|
+
req_arg_count
|
43
|
+
end
|
44
|
+
|
45
|
+
def req_kwarg_names
|
46
|
+
@req_kwarg_names ||= parameters.map do |type, name|
|
47
|
+
name if type == REQ_KWARG_TYPE
|
48
|
+
end.compact
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def parameters
|
54
|
+
@parameters ||= target_method.parameters
|
55
|
+
end
|
56
|
+
|
57
|
+
def kwarg_names
|
58
|
+
@kwarg_names ||= parameters.map do |type, name|
|
59
|
+
name if KWARG_TYPES.include?(type)
|
60
|
+
end.compact.to_set
|
61
|
+
end
|
62
|
+
|
63
|
+
def arg_names
|
64
|
+
@arg_names ||= parameters.map do |type, name|
|
65
|
+
name if ARG_TYPES.include?(type)
|
66
|
+
end.compact
|
67
|
+
end
|
68
|
+
|
69
|
+
def req_arg_names
|
70
|
+
@req_arg_names ||= parameters.map do |type, name|
|
71
|
+
name if type == REQ_ARG_TYPE
|
72
|
+
end.compact
|
73
|
+
end
|
74
|
+
|
75
|
+
def named_arg_count
|
76
|
+
@named_arg_count ||= arg_names.count
|
77
|
+
end
|
78
|
+
|
79
|
+
def req_arg_count
|
80
|
+
@req_arg_count ||= req_arg_names.count
|
81
|
+
end
|
82
|
+
|
83
|
+
def rest_arg_name
|
84
|
+
@rest_arg_name ||= parameters.map { |type, name| name if type == REST }.first
|
85
|
+
end
|
86
|
+
|
87
|
+
def supports_keyrest?
|
88
|
+
@supports_keyrest ||= parameters.map(&:first).include?(KWARG_REST)
|
89
|
+
end
|
90
|
+
|
91
|
+
def supports_rest?
|
92
|
+
@supports_rest ||= parameters.map(&:first).include?(REST)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
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
|