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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -138
  3. data/app/controllers/view_component/storybook/stories_controller.rb +9 -11
  4. data/app/views/view_component/storybook/stories/show.html.erb +8 -3
  5. data/config/locales/en.yml +1 -1
  6. data/lib/view_component/storybook.rb +14 -2
  7. data/lib/view_component/storybook/content_concern.rb +42 -0
  8. data/lib/view_component/storybook/controls.rb +3 -1
  9. data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
  10. data/lib/view_component/storybook/controls/control_config.rb +4 -0
  11. data/lib/view_component/storybook/{dsl/controls_dsl.rb → controls/controls_helpers.rb} +21 -16
  12. data/lib/view_component/storybook/controls/custom_config.rb +2 -4
  13. data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
  14. data/lib/view_component/storybook/controls/options_config.rb +12 -26
  15. data/lib/view_component/storybook/controls/simple_control_config.rb +1 -1
  16. data/lib/view_component/storybook/dsl.rb +0 -2
  17. data/lib/view_component/storybook/dsl/legacy_controls_dsl.rb +2 -2
  18. data/lib/view_component/storybook/engine.rb +2 -1
  19. data/lib/view_component/storybook/method_args/control_method_args.rb +15 -12
  20. data/lib/view_component/storybook/method_args/method_args.rb +37 -4
  21. data/lib/view_component/storybook/slots.rb +14 -0
  22. data/lib/view_component/storybook/slots/slot.rb +24 -0
  23. data/lib/view_component/storybook/slots/slot_config.rb +44 -0
  24. data/lib/view_component/storybook/stories.rb +16 -7
  25. data/lib/view_component/storybook/story.rb +18 -0
  26. data/lib/view_component/storybook/story_config.rb +115 -24
  27. data/lib/view_component/storybook/tasks/write_stories_json.rake +1 -1
  28. data/lib/view_component/storybook/version.rb +1 -1
  29. metadata +27 -9
  30. data/lib/view_component/storybook/controls/array_config.rb +0 -37
  31. data/lib/view_component/storybook/dsl/story_dsl.rb +0 -51
  32. 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 = ViewComponent::Storybook::MethodArgs::ControlMethodArgs.new(block, *args, **kwargs, &block)
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
- method_args = value_method_args.resolve_method_args(params)
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 < SimpleControlConfig
7
- class << self
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: method(:inclusion_in) }, unless: -> { options.nil? || default_value.nil? }
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(options: options)
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
@@ -23,7 +23,7 @@ module ViewComponent
23
23
  end
24
24
 
25
25
  def value_from_params(params)
26
- params[param]
26
+ params.key?(param) ? params[param] : default_value
27
27
  end
28
28
 
29
29
  private
@@ -7,8 +7,6 @@ module ViewComponent
7
7
  module Dsl
8
8
  extend ActiveSupport::Autoload
9
9
 
10
- autoload :StoryDsl
11
- autoload :ControlsDsl
12
10
  autoload :LegacyControlsDsl
13
11
  end
14
12
  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, separator = ",", name: nil)
60
- controls << Controls::ArrayConfig.new(value, separator, param: param, name: name)
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 "/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
@@ -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
- # I addition the args and kwargs can contain Controls the values of which can
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 < ValidatableMethodArgs
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, block)
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.param("#{param_prefix}__#{arg.param}".to_sym) if param_prefix.present?
71
+ arg.prefix_param(param_prefix) if param_prefix.present?
63
72
  end
64
73
 
65
74
  def value_from_params(arg, params)
66
- if control?(arg)
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?(ViewComponent::Storybook::Controls::ControlConfig)
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
- # Simple class representing arguments passed to a method
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
- attr_reader :args, :kwargs, :block
10
+ include ActiveModel::Validations
10
11
 
11
- def initialize(args, kwargs, block)
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
- @block = block
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,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
@@ -7,19 +7,28 @@ module ViewComponent
7
7
  include ActiveModel::Validations
8
8
 
9
9
  class_attribute :story_configs, default: []
10
- class_attribute :parameters, :title, :stories_layout
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.configure(story_id(name), name, component, layout, &block)
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(**params)
22
- self.parameters = params
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 find_stories(stories_name)
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 find_story(name)
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.title = other.stories_name.humanize.titlecase
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