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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -128
  3. data/app/controllers/view_component/storybook/stories_controller.rb +10 -11
  4. data/app/views/view_component/storybook/stories/show.html.erb +8 -1
  5. data/config/locales/en.yml +32 -0
  6. data/lib/view_component/storybook.rb +14 -0
  7. data/lib/view_component/storybook/content_concern.rb +42 -0
  8. data/lib/view_component/storybook/controls.rb +5 -1
  9. data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
  10. data/lib/view_component/storybook/controls/boolean_config.rb +7 -6
  11. data/lib/view_component/storybook/controls/color_config.rb +4 -6
  12. data/lib/view_component/storybook/controls/control_config.rb +25 -25
  13. data/lib/view_component/storybook/controls/controls_helpers.rb +76 -0
  14. data/lib/view_component/storybook/controls/custom_config.rb +52 -0
  15. data/lib/view_component/storybook/controls/date_config.rb +14 -13
  16. data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
  17. data/lib/view_component/storybook/controls/number_config.rb +13 -10
  18. data/lib/view_component/storybook/controls/object_config.rb +13 -7
  19. data/lib/view_component/storybook/controls/options_config.rb +17 -33
  20. data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
  21. data/lib/view_component/storybook/controls/text_config.rb +1 -3
  22. data/lib/view_component/storybook/dsl.rb +1 -2
  23. data/lib/view_component/storybook/dsl/{controls_dsl.rb → legacy_controls_dsl.rb} +19 -22
  24. data/lib/view_component/storybook/engine.rb +2 -1
  25. data/lib/view_component/storybook/method_args.rb +16 -0
  26. data/lib/view_component/storybook/method_args/control_method_args.rb +91 -0
  27. data/lib/view_component/storybook/method_args/method_args.rb +52 -0
  28. data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
  29. data/lib/view_component/storybook/slots.rb +14 -0
  30. data/lib/view_component/storybook/slots/slot.rb +24 -0
  31. data/lib/view_component/storybook/slots/slot_config.rb +44 -0
  32. data/lib/view_component/storybook/stories.rb +60 -14
  33. data/lib/view_component/storybook/story.rb +18 -0
  34. data/lib/view_component/storybook/story_config.rb +140 -15
  35. data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
  36. data/lib/view_component/storybook/version.rb +1 -1
  37. metadata +64 -23
  38. data/lib/view_component/storybook/controls/array_config.rb +0 -36
  39. data/lib/view_component/storybook/dsl/story_dsl.rb +0 -39
@@ -3,9 +3,7 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class TextConfig < ControlConfig
7
- validates :value, presence: true
8
-
6
+ class TextConfig < SimpleControlConfig
9
7
  def type
10
8
  :text
11
9
  end
@@ -7,8 +7,7 @@ module ViewComponent
7
7
  module Dsl
8
8
  extend ActiveSupport::Autoload
9
9
 
10
- autoload :StoryDsl
11
- autoload :ControlsDsl
10
+ autoload :LegacyControlsDsl
12
11
  end
13
12
  end
14
13
  end
@@ -3,71 +3,68 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Dsl
6
- class ControlsDsl
7
- attr_reader :component, :controls
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(component, param, value, name: name)
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(component, param, value, name: name)
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, component, param, value, name: name, min: min, max: max, step: step)
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, component, param, value, name: name, min: min, max: max, step: step)
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(component, param, value, name: name, preset_colors: preset_colors)
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(component, param, value, name: name)
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, component, param, options, value, name: name)
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', component, param, options, value, name: name)
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, component, param, options, value, name: name)
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', component, param, options, value, name: name)
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, component, param, options, value, name: name)
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', component, param, options, value, name: name)
56
+ controls << Controls::OptionsConfig.new(:'inline-check', options, value, param: param, name: name)
60
57
  end
61
58
 
62
- def array(param, value, separator = ",", name: nil)
63
- controls << Controls::ArrayConfig.new(component, param, value, separator, name: name)
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(component, param, value, name: name)
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 "/rails/stories/*stories/:story" => "view_component/storybook/stories#show", :internal => true
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,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/dependencies/autoload"
4
+
5
+ module ViewComponent
6
+ module Storybook
7
+ module Slots
8
+ extend ActiveSupport::Autoload
9
+
10
+ autoload :SlotConfig
11
+ autoload :Slot
12
+ end
13
+ end
14
+ 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