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
@@ -6,43 +6,43 @@ module ViewComponent
6
6
  class ControlConfig
7
7
  include ActiveModel::Validations
8
8
 
9
- attr_reader :component, :param, :value, :name
9
+ validates :param, presence: true
10
10
 
11
- validates :component, :param, presence: true
12
- validates :param, inclusion: { in: ->(control_config) { control_config.component_params } }, unless: -> { component.nil? }
13
-
14
- def initialize(component, param, value, name: nil)
15
- @component = component
11
+ def initialize(param: nil, name: nil)
16
12
  @param = param
17
- @value = value
18
- @name = name || param.to_s.humanize.titlecase
13
+ @name = name
19
14
  end
20
15
 
21
- def to_csf_params
22
- validate!
23
- {
24
- args: { param => csf_value },
25
- argTypes: { param => { control: csf_control_params, name: name } }
26
- }
16
+ def name(new_name = nil)
17
+ if new_name.nil?
18
+ @name ||= param.to_s.humanize.titlecase
19
+ else
20
+ @name = new_name
21
+ self
22
+ end
27
23
  end
28
24
 
29
- def value_from_param(param)
30
- param
31
- end
25
+ def param(new_param = nil)
26
+ return @param if new_param.nil?
32
27
 
33
- def component_params
34
- @component_params ||= component && component.instance_method(:initialize).parameters.map(&:last)
28
+ @param = new_param
29
+ self
35
30
  end
36
31
 
37
- private
32
+ def prefix_param(prefix)
33
+ param("#{prefix}__#{@param}".to_sym)
34
+ end
38
35
 
39
- # provide extension points for subclasses to vary the value
40
- def csf_value
41
- value
36
+ def to_csf_params
37
+ # :nocov:
38
+ raise NotImplementedError
39
+ # :nocov:
42
40
  end
43
41
 
44
- def csf_control_params
45
- { type: type }
42
+ def value_from_params(params)
43
+ # :nocov:
44
+ raise NotImplementedError
45
+ # :nocov:
46
46
  end
47
47
  end
48
48
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ module ControlsHelpers
7
+ def text(default_value)
8
+ Controls::TextConfig.new(default_value)
9
+ end
10
+
11
+ def boolean(default_value)
12
+ Controls::BooleanConfig.new(default_value)
13
+ end
14
+
15
+ def number(default_value, min: nil, max: nil, step: nil)
16
+ Controls::NumberConfig.new(:number, default_value, min: min, max: max, step: step)
17
+ end
18
+
19
+ def range(default_value, min: nil, max: nil, step: nil)
20
+ Controls::NumberConfig.new(:range, default_value, min: min, max: max, step: step)
21
+ end
22
+
23
+ def color(default_value, preset_colors: nil)
24
+ Controls::ColorConfig.new(default_value, preset_colors: preset_colors)
25
+ end
26
+
27
+ def object(default_value)
28
+ Controls::ObjectConfig.new(default_value)
29
+ end
30
+
31
+ def select(options, default_value, labels: nil)
32
+ Controls::OptionsConfig.new(:select, options, default_value, labels: labels)
33
+ end
34
+
35
+ def multi_select(options, default_value, labels: nil)
36
+ Controls::MultiOptionsConfig.new(:'multi-select', options, default_value, labels: labels)
37
+ end
38
+
39
+ def radio(options, default_value, labels: nil)
40
+ Controls::OptionsConfig.new(:radio, options, default_value, labels: labels)
41
+ end
42
+
43
+ def inline_radio(options, default_value, labels: nil)
44
+ Controls::OptionsConfig.new(:'inline-radio', options, default_value, labels: labels)
45
+ end
46
+
47
+ def check(options, default_value, labels: nil)
48
+ Controls::MultiOptionsConfig.new(:check, options, default_value, labels: labels)
49
+ end
50
+
51
+ def inline_check(options, default_value)
52
+ Controls::MultiOptionsConfig.new(:'inline-check', options, default_value)
53
+ end
54
+
55
+ def array(default_value, separator = nil)
56
+ ActiveSupport::Deprecation.warn("`array` `separator` argument will be removed in v1.0.0.") if separator
57
+ Controls::ObjectConfig.new(default_value)
58
+ end
59
+
60
+ def date(default_value)
61
+ Controls::DateConfig.new(default_value)
62
+ end
63
+
64
+ def custom(*args, **kwargs, &block)
65
+ Controls::CustomConfig.new.with_value(*args, **kwargs, &block)
66
+ end
67
+
68
+ def klazz(value_class, *args, **kwargs)
69
+ Controls::CustomConfig.new.with_value(*args, **kwargs) do |*a, **kwa|
70
+ value_class.new(*a, **kwa)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ class CustomConfig < ControlConfig
7
+ attr_reader :value_method_args
8
+
9
+ validate :validate_value_method_args
10
+
11
+ def with_value(*args, **kwargs, &block)
12
+ @value_method_args = MethodArgs::ControlMethodArgs.new(block, *args, **kwargs)
13
+ @value_method_args.with_param_prefix(param)
14
+ self
15
+ end
16
+
17
+ def param(new_param = nil)
18
+ value_method_args.with_param_prefix(new_param) unless new_param.nil?
19
+ super(new_param)
20
+ end
21
+
22
+ def to_csf_params
23
+ validate!
24
+ # TODO: Figure out if we can use 'category' with the args table
25
+ # export default {
26
+ # argTypes: {
27
+ # foo: {
28
+ # table: { category: 'cat', subcategory: 'sub' }
29
+ # }
30
+ # }
31
+ # }
32
+ value_method_args.controls.reduce({}) do |csf_params, control|
33
+ csf_params.deep_merge(control.to_csf_params)
34
+ end
35
+ end
36
+
37
+ def value_from_params(params)
38
+ value_method_args.call(params)
39
+ end
40
+
41
+ private
42
+
43
+ def validate_value_method_args
44
+ return if value_method_args.valid?
45
+
46
+ value_method_args_errors = value_method_args.errors.full_messages.join(', ')
47
+ errors.add(:value_method_args, :invalid, errors: value_method_args_errors)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -3,32 +3,33 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class DateConfig < ControlConfig
7
- validates :value, presence: true
8
-
9
- def initialize(component, param, value, name: nil)
10
- super(component, param, value, name: name)
6
+ class DateConfig < SimpleControlConfig
7
+ def initialize(default_value, param: nil, name: nil)
8
+ super(default_value, param: param, name: name)
11
9
  end
12
10
 
13
11
  def type
14
12
  :date
15
13
  end
16
14
 
17
- def value_from_param(param)
18
- if param.is_a?(String)
19
- DateTime.iso8601(param)
15
+ def value_from_params(params)
16
+ params_value = super(params)
17
+ if params_value.is_a?(String)
18
+ DateTime.iso8601(params_value)
20
19
  else
21
- super(param)
20
+ params_value
22
21
  end
23
22
  end
24
23
 
25
24
  private
26
25
 
27
26
  def csf_value
28
- params_value = value
29
- params_value = params_value.in_time_zone if params_value.is_a?(Date)
30
- params_value = params_value.iso8601 if params_value.is_a?(Time)
31
- params_value
27
+ case default_value
28
+ when Date
29
+ default_value.in_time_zone
30
+ when Time
31
+ default_value.iso8601
32
+ end
32
33
  end
33
34
  end
34
35
  end
@@ -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,32 +3,35 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class NumberConfig < ControlConfig
6
+ class NumberConfig < SimpleControlConfig
7
+ TYPES = %i[number range].freeze
8
+
7
9
  attr_reader :type, :min, :max, :step
8
10
 
9
- validates :value, :type, presence: true
11
+ validates :type, presence: true
12
+ validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
10
13
 
11
- def initialize(type, component, param, value, min: nil, max: nil, step: nil, name: nil)
12
- super(component, param, value, name: name)
14
+ def initialize(type, default_value, min: nil, max: nil, step: nil, param: nil, name: nil)
15
+ super(default_value, param: param, name: name)
13
16
  @type = type
14
17
  @min = min
15
18
  @max = max
16
19
  @step = step
17
20
  end
18
21
 
19
- def value_from_param(param)
20
- if param.is_a?(String) && param.present?
21
- (param.to_f % 1) > 0 ? param.to_f : param.to_i
22
+ def value_from_params(params)
23
+ params_value = super(params)
24
+ if params_value.is_a?(String) && params_value.present?
25
+ (params_value.to_f % 1) > 0 ? params_value.to_f : params_value.to_i
22
26
  else
23
- super(param)
27
+ params_value
24
28
  end
25
29
  end
26
30
 
27
31
  private
28
32
 
29
33
  def csf_control_params
30
- params = super
31
- params.merge(min: min, max: max, step: step).compact
34
+ super.merge(min: min, max: max, step: step).compact
32
35
  end
33
36
  end
34
37
  end
@@ -3,18 +3,24 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class ObjectConfig < ControlConfig
7
- validates :value, presence: true
8
-
6
+ class ObjectConfig < SimpleControlConfig
9
7
  def type
10
8
  :object
11
9
  end
12
10
 
13
- def value_from_param(param)
14
- if param.is_a?(String)
15
- JSON.parse(param).symbolize_keys
11
+ def value_from_params(params)
12
+ params_value = super(params)
13
+ if params_value.is_a?(String)
14
+ parsed_json = JSON.parse(params_value)
15
+ if parsed_json.is_a?(Array)
16
+ parsed_json.map do |item|
17
+ item.is_a?(Hash) ? item.deep_symbolize_keys : item
18
+ end
19
+ else
20
+ parsed_json.deep_symbolize_keys
21
+ end
16
22
  else
17
- super(param)
23
+ params_value
18
24
  end
19
25
  end
20
26
  end
@@ -3,49 +3,33 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class OptionsConfig < ControlConfig
6
+ class OptionsConfig < BaseOptionsConfig
7
+ TYPES = %i[select radio inline-radio].freeze
7
8
 
8
- class << self
9
- # support the options being a Hash or an Array. Storybook supports either.
10
- def inclusion_in(config)
11
- case config.options
12
- when Hash
13
- config.options.values
14
- when Array
15
- config.options
16
- else
17
- []
18
- end
19
- end
20
- end
21
-
22
- TYPES = %i[select multi-select radio inline-radio check inline-check].freeze
23
-
24
- attr_reader :type, :options, :symbol_value
25
-
26
- validates :value, :type, :options, presence: true
27
9
  validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
28
- validates :value, inclusion: { in: method(:inclusion_in) }, unless: -> { options.nil? || value.nil? }
10
+ validates :default_value, inclusion: { in: ->(config) { config.options } }, unless: -> { options.nil? || default_value.nil? }
29
11
 
30
- def initialize(type, component, param, options, default_value, name: nil)
31
- super(component, param, default_value, name: name)
32
- @type = type
33
- @options = options
34
- @symbol_value = default_value.is_a?(Symbol)
35
- end
36
-
37
- def value_from_param(param)
38
- if param.is_a?(String) && symbol_value
39
- param.to_sym
12
+ def value_from_params(params)
13
+ params_value = super(params)
14
+ if params_value.is_a?(String) && symbol_value
15
+ params_value.to_sym
40
16
  else
41
- super(param)
17
+ params_value
42
18
  end
43
19
  end
44
20
 
21
+ def to_csf_params
22
+ super.deep_merge(argTypes: { param => { options: options } })
23
+ end
24
+
45
25
  private
46
26
 
47
27
  def csf_control_params
48
- 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)
49
33
  end
50
34
  end
51
35
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ ##
7
+ # A simple Control Config maps to one Storybook Control
8
+ # It has a value and pulls its value from params by key
9
+ class SimpleControlConfig < ControlConfig
10
+ attr_reader :default_value
11
+
12
+ def initialize(default_value, param: nil, name: nil)
13
+ super(param: param, name: name)
14
+ @default_value = default_value
15
+ end
16
+
17
+ def to_csf_params
18
+ validate!
19
+ {
20
+ args: { param => csf_value },
21
+ argTypes: { param => { control: csf_control_params, name: name } }
22
+ }
23
+ end
24
+
25
+ def value_from_params(params)
26
+ params.key?(param) ? params[param] : default_value
27
+ end
28
+
29
+ private
30
+
31
+ # provide extension points for subclasses to vary the value
32
+ def type
33
+ # :nocov:
34
+ raise NotImplementedError
35
+ # :nocov:
36
+ end
37
+
38
+ def csf_value
39
+ default_value
40
+ end
41
+
42
+ def csf_control_params
43
+ { type: type }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end