view_component_storybook 0.6.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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