view_component_storybook 0.9.0 → 0.10.0

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 (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