view_component_storybook 0.6.0 → 0.10.1
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.
- 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 +13 -7
- data/lib/view_component/storybook/controls/options_config.rb +17 -33
- 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
|