view_component_storybook 0.3.0 → 0.9.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -11
  3. data/app/controllers/view_component/storybook/stories_controller.rb +3 -2
  4. data/app/views/view_component/storybook/stories/show.html.erb +3 -1
  5. data/config/locales/en.yml +32 -0
  6. data/lib/view_component/storybook.rb +2 -1
  7. data/lib/view_component/storybook/controls.rb +5 -1
  8. data/lib/view_component/storybook/controls/array_config.rb +9 -8
  9. data/lib/view_component/storybook/controls/boolean_config.rb +31 -0
  10. data/lib/view_component/storybook/controls/color_config.rb +26 -0
  11. data/lib/view_component/storybook/controls/control_config.rb +22 -26
  12. data/lib/view_component/storybook/controls/custom_config.rb +54 -0
  13. data/lib/view_component/storybook/controls/date_config.rb +14 -13
  14. data/lib/view_component/storybook/controls/number_config.rb +17 -16
  15. data/lib/view_component/storybook/controls/object_config.rb +11 -7
  16. data/lib/view_component/storybook/controls/options_config.rb +29 -7
  17. data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
  18. data/lib/view_component/storybook/controls/text_config.rb +13 -0
  19. data/lib/view_component/storybook/dsl.rb +1 -0
  20. data/lib/view_component/storybook/dsl/controls_dsl.rb +36 -46
  21. data/lib/view_component/storybook/dsl/legacy_controls_dsl.rb +98 -0
  22. data/lib/view_component/storybook/dsl/story_dsl.rb +17 -5
  23. data/lib/view_component/storybook/method_args.rb +16 -0
  24. data/lib/view_component/storybook/method_args/control_method_args.rb +88 -0
  25. data/lib/view_component/storybook/method_args/method_args.rb +19 -0
  26. data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
  27. data/lib/view_component/storybook/method_args/validatable_method_args.rb +50 -0
  28. data/lib/view_component/storybook/stories.rb +44 -7
  29. data/lib/view_component/storybook/story_config.rb +43 -9
  30. data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
  31. data/lib/view_component/storybook/version.rb +1 -1
  32. metadata +43 -18
  33. data/lib/view_component/storybook/controls/simple_config.rb +0 -38
@@ -3,34 +3,35 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class NumberConfig < ControlConfig
7
- attr_reader :options
6
+ class NumberConfig < SimpleControlConfig
7
+ TYPES = %i[number range].freeze
8
8
 
9
- validates :value, presence: true
9
+ attr_reader :type, :min, :max, :step
10
10
 
11
- def initialize(component, param, value, options = {}, name: nil)
12
- super(component, param, value, name: name)
13
- @options = options
14
- end
11
+ validates :type, presence: true
12
+ validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
15
13
 
16
- def type
17
- :number
14
+ def initialize(type, default_value, min: nil, max: nil, step: nil, param: nil, name: nil)
15
+ super(default_value, param: param, name: name)
16
+ @type = type
17
+ @min = min
18
+ @max = max
19
+ @step = step
18
20
  end
19
21
 
20
- def value_from_param(param)
21
- if param.is_a?(String) && param.present?
22
- (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
23
26
  else
24
- super(param)
27
+ params_value
25
28
  end
26
29
  end
27
30
 
28
31
  private
29
32
 
30
33
  def csf_control_params
31
- params = super
32
- params[:options] = options unless options.empty?
33
- params
34
+ super.merge(min: min, max: max, step: step).compact
34
35
  end
35
36
  end
36
37
  end
@@ -3,18 +3,22 @@
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(&:deep_symbolize_keys)
17
+ else
18
+ parsed_json.deep_symbolize_keys
19
+ end
16
20
  else
17
- super(param)
21
+ params_value
18
22
  end
19
23
  end
20
24
  end
@@ -3,19 +3,41 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class OptionsConfig < ControlConfig
7
- TYPES = %i[select radios].freeze
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
8
20
 
9
- attr_reader :type, :options
21
+ attr_reader :type, :options, :symbol_value
10
22
 
11
- validates :value, :type, :options, presence: true
23
+ validates :type, :options, presence: true
12
24
  validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
13
- validates :value, inclusion: { in: ->(config) { config.options.values } }, unless: -> { options.nil? || value.nil? }
25
+ validates :default_value, inclusion: { in: method(:inclusion_in) }, unless: -> { options.nil? || default_value.nil? }
14
26
 
15
- def initialize(type, component, param, options, default_value, name: nil)
16
- super(component, param, default_value, name: name)
27
+ def initialize(type, options, default_value, param: nil, name: nil)
28
+ super(default_value, param: param, name: name)
17
29
  @type = type
18
30
  @options = options
31
+ @symbol_value = default_value.is_a?(Symbol)
32
+ end
33
+
34
+ def value_from_params(params)
35
+ params_value = super(params)
36
+ if params_value.is_a?(String) && symbol_value
37
+ params_value.to_sym
38
+ else
39
+ params_value
40
+ end
19
41
  end
20
42
 
21
43
  private
@@ -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[param]
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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ class TextConfig < SimpleControlConfig
7
+ def type
8
+ :text
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -9,6 +9,7 @@ module ViewComponent
9
9
 
10
10
  autoload :StoryDsl
11
11
  autoload :ControlsDsl
12
+ autoload :LegacyControlsDsl
12
13
  end
13
14
  end
14
15
  end
@@ -3,75 +3,65 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Dsl
6
- class ControlsDsl
7
- attr_reader :component, :controls
6
+ module ControlsDsl
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
8
18
 
9
- def initialize(component)
10
- @component = component
11
- @controls = []
19
+ def range(default_value, min: nil, max: nil, step: nil)
20
+ Controls::NumberConfig.new(:range, default_value, min: min, max: max, step: step)
12
21
  end
13
22
 
14
- def text(param, value, name: nil)
15
- controls << Controls::SimpleConfig.new(:text, component, param, value, name: name)
23
+ def color(default_value, preset_colors: nil)
24
+ Controls::ColorConfig.new(default_value, preset_colors: preset_colors)
16
25
  end
17
26
 
18
- def boolean(param, value, name: nil)
19
- controls << Controls::SimpleConfig.new(:boolean, component, param, value, name: name)
27
+ def object(default_value)
28
+ Controls::ObjectConfig.new(default_value)
20
29
  end
21
30
 
22
- def number(param, value, options = {}, name: nil)
23
- controls << Controls::NumberConfig.new(component, param, value, options, name: name)
31
+ def select(options, default_value)
32
+ Controls::OptionsConfig.new(:select, options, default_value)
24
33
  end
25
34
 
26
- def color(param, value, name: nil)
27
- controls << Controls::SimpleConfig.new(:color, component, param, value, name: name)
35
+ def multi_select(options, default_value)
36
+ Controls::OptionsConfig.new(:'multi-select', options, default_value)
28
37
  end
29
38
 
30
- def object(param, value, name: nil)
31
- controls << Controls::ObjectConfig.new(component, param, value, name: name)
39
+ def radio(options, default_value)
40
+ Controls::OptionsConfig.new(:radio, options, default_value)
32
41
  end
33
42
 
34
- def select(param, options, value, name: nil)
35
- controls << Controls::OptionsConfig.new(:select, component, param, options, value, name: name)
43
+ def inline_radio(options, default_value)
44
+ Controls::OptionsConfig.new(:'inline-radio', options, default_value)
36
45
  end
37
46
 
38
- def radios(param, options, value, name: nil)
39
- controls << Controls::OptionsConfig.new(:radios, component, param, options, value, name: name)
47
+ def check(options, default_value)
48
+ Controls::OptionsConfig.new(:check, options, default_value)
40
49
  end
41
50
 
42
- def array(param, value, separator = ",", name: nil)
43
- controls << Controls::ArrayConfig.new(component, param, value, separator, name: name)
51
+ def inline_check(options, default_value)
52
+ Controls::OptionsConfig.new(:'inline-check', options, default_value)
44
53
  end
45
54
 
46
- def date(param, value, name: nil)
47
- controls << Controls::DateConfig.new(component, param, value, name: name)
55
+ def array(default_value, separator = ",")
56
+ Controls::ArrayConfig.new(default_value, separator)
48
57
  end
49
58
 
50
- def respond_to_missing?(_method)
51
- true
59
+ def date(default_value)
60
+ Controls::DateConfig.new(default_value)
52
61
  end
53
62
 
54
- def method_missing(method, *args)
55
- value = args.first
56
- knob_method = case value
57
- when Date
58
- :date
59
- when Array
60
- :array
61
- when Hash
62
- :object
63
- when Numeric
64
- :number
65
- when TrueClass, FalseClass
66
- :boolean
67
- when String
68
- :text
69
- end
70
- if knob_method
71
- send(knob_method, method, *args)
72
- else
73
- super
74
- end
63
+ def custom(*args, **kwargs, &block)
64
+ Controls::CustomConfig.new.with_value(*args, **kwargs, &block)
75
65
  end
76
66
 
77
67
  Controls = ViewComponent::Storybook::Controls
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Dsl
6
+ class LegacyControlsDsl
7
+ def controls
8
+ @controls ||= []
9
+ end
10
+
11
+ def text(param, value, name: nil)
12
+ controls << Controls::TextConfig.new(value, param: param, name: name)
13
+ end
14
+
15
+ def boolean(param, value, name: nil)
16
+ controls << Controls::BooleanConfig.new(value, param: param, name: name)
17
+ end
18
+
19
+ def number(param, value, name: nil, min: nil, max: nil, step: nil)
20
+ controls << Controls::NumberConfig.new(:number, value, param: param, name: name, min: min, max: max, step: step)
21
+ end
22
+
23
+ def range(param, value, name: nil, min: nil, max: nil, step: nil)
24
+ controls << Controls::NumberConfig.new(:range, value, param: param, name: name, min: min, max: max, step: step)
25
+ end
26
+
27
+ def color(param, value, name: nil, preset_colors: nil)
28
+ controls << Controls::ColorConfig.new(value, param: param, name: name, preset_colors: preset_colors)
29
+ end
30
+
31
+ def object(param, value, name: nil)
32
+ controls << Controls::ObjectConfig.new(value, param: param, name: name)
33
+ end
34
+
35
+ def select(param, options, value, name: nil)
36
+ controls << Controls::OptionsConfig.new(:select, options, value, param: param, name: name)
37
+ end
38
+
39
+ def multi_select(param, options, value, name: nil)
40
+ controls << Controls::OptionsConfig.new(:'multi-select', options, value, param: param, name: name)
41
+ end
42
+
43
+ def radio(param, options, value, name: nil)
44
+ controls << Controls::OptionsConfig.new(:radio, options, value, param: param, name: name)
45
+ end
46
+
47
+ def inline_radio(param, options, value, name: nil)
48
+ controls << Controls::OptionsConfig.new(:'inline-radio', options, value, param: param, name: name)
49
+ end
50
+
51
+ def check(param, options, value, name: nil)
52
+ controls << Controls::OptionsConfig.new(:check, options, value, param: param, name: name)
53
+ end
54
+
55
+ def inline_check(param, options, value, name: nil)
56
+ controls << Controls::OptionsConfig.new(:'inline-check', options, value, param: param, name: name)
57
+ end
58
+
59
+ def array(param, value, separator = ",", name: nil)
60
+ controls << Controls::ArrayConfig.new(value, separator, param: param, name: name)
61
+ end
62
+
63
+ def date(param, value, name: nil)
64
+ controls << Controls::DateConfig.new(value, param: param, name: name)
65
+ end
66
+
67
+ def respond_to_missing?(_method, *)
68
+ true
69
+ end
70
+
71
+ def method_missing(method, *args)
72
+ value = args.first
73
+ control_method = case value
74
+ when Date
75
+ :date
76
+ when Array
77
+ :array
78
+ when Hash
79
+ :object
80
+ when Numeric
81
+ :number
82
+ when TrueClass, FalseClass
83
+ :boolean
84
+ when String
85
+ :text
86
+ end
87
+ if control_method
88
+ send(control_method, method, *args)
89
+ else
90
+ super
91
+ end
92
+ end
93
+
94
+ Controls = ViewComponent::Storybook::Controls
95
+ end
96
+ end
97
+ end
98
+ end
@@ -4,28 +4,40 @@ module ViewComponent
4
4
  module Storybook
5
5
  module Dsl
6
6
  class StoryDsl
7
+ include ControlsDsl
8
+
7
9
  def self.evaluate!(story_config, &block)
8
10
  new(story_config).instance_eval(&block)
9
11
  end
10
12
 
11
13
  def parameters(**params)
12
- @story_config.parameters = params
14
+ story_config.parameters = params
13
15
  end
14
16
 
15
17
  def controls(&block)
16
- controls_dsl = ControlsDsl.new(story_config.component)
18
+ ActiveSupport::Deprecation.warn("`controls` will be removed in v1.0.0. Use `#constructor` instead.")
19
+ controls_dsl = LegacyControlsDsl.new
17
20
  controls_dsl.instance_eval(&block)
18
- @story_config.controls = controls_dsl.controls
21
+
22
+ controls_hash = controls_dsl.controls.index_by(&:param)
23
+ story_config.constructor_args(**controls_hash)
19
24
  end
20
25
 
21
26
  def layout(layout)
22
- @story_config.layout = layout
27
+ story_config.layout = layout
23
28
  end
24
29
 
25
30
  def content(&block)
26
- @story_config.content_block = block
31
+ story_config.content_block = block
27
32
  end
28
33
 
34
+ def constructor(*args, **kwargs, &block)
35
+ story_config.constructor_args(*args, **kwargs)
36
+ story_config.content_block = block
37
+ end
38
+
39
+ delegate :component, to: :story_config
40
+
29
41
  private
30
42
 
31
43
  attr_reader :story_config