view_component_storybook 0.9.0 → 0.11.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.
- checksums.yaml +4 -4
- data/README.md +6 -138
- data/app/controllers/view_component/storybook/stories_controller.rb +9 -11
- data/app/views/view_component/storybook/stories/show.html.erb +8 -3
- data/config/locales/en.yml +1 -1
- data/lib/view_component/storybook/content_concern.rb +42 -0
- data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
- data/lib/view_component/storybook/controls/control_config.rb +4 -0
- data/lib/view_component/storybook/{dsl/controls_dsl.rb → controls/controls_helpers.rb} +21 -16
- data/lib/view_component/storybook/controls/custom_config.rb +2 -4
- data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
- data/lib/view_component/storybook/controls/object_config.rb +3 -1
- data/lib/view_component/storybook/controls/options_config.rb +12 -26
- data/lib/view_component/storybook/controls/simple_control_config.rb +1 -1
- data/lib/view_component/storybook/controls.rb +3 -1
- data/lib/view_component/storybook/dsl/legacy_controls_dsl.rb +2 -2
- data/lib/view_component/storybook/dsl.rb +0 -2
- data/lib/view_component/storybook/engine.rb +13 -2
- data/lib/view_component/storybook/method_args/component_constructor_args.rb +23 -0
- data/lib/view_component/storybook/method_args/control_method_args.rb +15 -12
- data/lib/view_component/storybook/method_args/dry_initializer_component_constructor_args.rb +45 -0
- data/lib/view_component/storybook/method_args/method_args.rb +37 -4
- data/lib/view_component/storybook/method_args/method_parameters_names.rb +1 -1
- data/lib/view_component/storybook/method_args.rb +2 -1
- data/lib/view_component/storybook/slots/slot.rb +24 -0
- data/lib/view_component/storybook/slots/slot_config.rb +79 -0
- data/lib/view_component/storybook/slots.rb +14 -0
- data/lib/view_component/storybook/stories.rb +17 -10
- data/lib/view_component/storybook/story.rb +18 -0
- data/lib/view_component/storybook/story_config.rb +120 -26
- data/lib/view_component/storybook/tasks/write_stories_json.rake +1 -1
- data/lib/view_component/storybook/version.rb +1 -1
- data/lib/view_component/storybook.rb +23 -2
- metadata +43 -8
- data/lib/view_component/storybook/controls/array_config.rb +0 -37
- data/lib/view_component/storybook/dsl/story_dsl.rb +0 -51
- data/lib/view_component/storybook/method_args/validatable_method_args.rb +0 -50
@@ -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
|
-
#
|
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 <
|
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
|
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.
|
71
|
+
arg.prefix_param(param_prefix) if param_prefix.present?
|
63
72
|
end
|
64
73
|
|
65
74
|
def value_from_params(arg, params)
|
66
|
-
|
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?(
|
79
|
+
arg.is_a?(Controls::ControlConfig)
|
77
80
|
end
|
78
81
|
|
79
82
|
def validate_controls
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module MethodArgs
|
6
|
+
##
|
7
|
+
# Class representing the constructor args for a Component the extends dry-initializer
|
8
|
+
class DryInitializerComponentConstructorArgs < ControlMethodArgs
|
9
|
+
INITIALIZE_METHOD = :__dry_initializer_initialize__
|
10
|
+
|
11
|
+
##
|
12
|
+
# Method Parameters names that are dry-initialize aware
|
13
|
+
# THe main difference is that Dry Initializer keyword args cannot be deduced from the constructor initialize params
|
14
|
+
# Instead we have to introspect the dry_initializer config for the option definitions
|
15
|
+
class DryConstructorParametersNames < MethodParametersNames
|
16
|
+
attr_reader :dry_initializer
|
17
|
+
|
18
|
+
def initialize(component_class)
|
19
|
+
super(component_class.instance_method(INITIALIZE_METHOD))
|
20
|
+
@dry_initializer = component_class.dry_initializer
|
21
|
+
end
|
22
|
+
|
23
|
+
# Required keywords are the only thing we need.
|
24
|
+
# We could define kwarg_names similarly but wihout the `optional` check. However, dry-initializer
|
25
|
+
# always ends has supports_keyrest? == true so kwarg_names isn't needed
|
26
|
+
def req_kwarg_names
|
27
|
+
@req_kwarg_names ||= dry_initializer.definitions.map do |name, definition|
|
28
|
+
name if definition.option && !definition.optional
|
29
|
+
end.compact
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.dry_initialize?(component_class)
|
34
|
+
component_class.private_method_defined?(INITIALIZE_METHOD)
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(component_class, *args, **kwargs)
|
38
|
+
super(component_class.instance_method(INITIALIZE_METHOD), *args, **kwargs)
|
39
|
+
|
40
|
+
@target_method_params_names = DryConstructorParametersNames.new(component_class)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -4,14 +4,47 @@ module ViewComponent
|
|
4
4
|
module Storybook
|
5
5
|
module MethodArgs
|
6
6
|
##
|
7
|
-
#
|
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
|
-
|
10
|
+
include ActiveModel::Validations
|
10
11
|
|
11
|
-
|
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
|
-
@
|
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
|
@@ -9,8 +9,9 @@ module ViewComponent
|
|
9
9
|
|
10
10
|
autoload :MethodArgs
|
11
11
|
autoload :MethodParametersNames
|
12
|
-
autoload :ValidatableMethodArgs
|
13
12
|
autoload :ControlMethodArgs
|
13
|
+
autoload :ComponentConstructorArgs
|
14
|
+
autoload :DryInitializerComponentConstructorArgs
|
14
15
|
end
|
15
16
|
end
|
16
17
|
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,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module Slots
|
6
|
+
class SlotConfig
|
7
|
+
include ActiveModel::Validations
|
8
|
+
include ContentConcern
|
9
|
+
|
10
|
+
attr_reader :slot_name, :slot_method_args, :param, :content_block
|
11
|
+
|
12
|
+
validate :validate_slot_method_args
|
13
|
+
|
14
|
+
def initialize(slot_name, slot_method_args, param, content_block)
|
15
|
+
@slot_name = slot_name
|
16
|
+
@slot_method_args = slot_method_args
|
17
|
+
@param = param
|
18
|
+
@content_block = content_block
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.from_component(component_class, slot_name, param, *args, **kwargs, &block)
|
22
|
+
new(
|
23
|
+
slot_name,
|
24
|
+
slot_method_args(component_class, slot_name, *args, **kwargs).with_param_prefix(param),
|
25
|
+
param,
|
26
|
+
block
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def slot(componeont, params)
|
31
|
+
resolved_method_args = slot_method_args.resolve_method_args(params)
|
32
|
+
story_content_block = resolve_content_block(params)
|
33
|
+
Slot.new(componeont, slot_name, resolved_method_args, story_content_block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def controls
|
37
|
+
list = slot_method_args.controls.dup
|
38
|
+
list << content_control if content_control
|
39
|
+
list
|
40
|
+
end
|
41
|
+
|
42
|
+
def content_param
|
43
|
+
"#{param}__content".to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validate_slot_method_args
|
49
|
+
return if slot_method_args.valid?
|
50
|
+
|
51
|
+
slot_method_args_errors = slot_method_args.errors.full_messages.join(', ')
|
52
|
+
errors.add(:slot_method_args, :invalid, errors: slot_method_args_errors)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.slot_method_args(component_class, slot_name, *args, **kwargs)
|
56
|
+
# Reverse engineer the signature of the slot so that we can validate control args to the slot
|
57
|
+
# The slot methods themselves just have rest params so we can't introspect them. Instead we
|
58
|
+
# look for 'renderable' details of the registered slot
|
59
|
+
# This approach is tightly coupled to internal ViewCopmonent apis and might prove to be brittle
|
60
|
+
registred_slot_name = component_class.slot_type(slot_name) == :collection_item ? ActiveSupport::Inflector.pluralize(slot_name).to_sym : slot_name
|
61
|
+
|
62
|
+
registered_slot = component_class.registered_slots[registred_slot_name]
|
63
|
+
|
64
|
+
if registered_slot[:renderable] || registered_slot[:renderable_class_name]
|
65
|
+
# The slot is a component - either a class or a string representing the class
|
66
|
+
component_class = registered_slot[:renderable] || component_class.const_get(registered_slot[:renderable_class_name])
|
67
|
+
MethodArgs::ComponentConstructorArgs.from_component_class(component_class, *args, **kwargs)
|
68
|
+
else
|
69
|
+
# the slot is a lamba or a simple content slot
|
70
|
+
slot_lamba = registered_slot[:renderable_function] || proc {}
|
71
|
+
MethodArgs::ControlMethodArgs.new(slot_lamba, *args, **kwargs)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private_class_method :slot_method_args
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -7,19 +7,28 @@ module ViewComponent
|
|
7
7
|
include ActiveModel::Validations
|
8
8
|
|
9
9
|
class_attribute :story_configs, default: []
|
10
|
-
class_attribute :
|
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.
|
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(
|
22
|
-
|
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)
|
@@ -38,9 +47,7 @@ module ViewComponent
|
|
38
47
|
|
39
48
|
def write_csf_json
|
40
49
|
json_path = File.join(stories_path, "#{stories_name}.stories.json")
|
41
|
-
File.
|
42
|
-
f.write(JSON.pretty_generate(to_csf_params))
|
43
|
-
end
|
50
|
+
File.write(json_path, JSON.pretty_generate(to_csf_params))
|
44
51
|
json_path
|
45
52
|
end
|
46
53
|
|
@@ -60,7 +67,7 @@ module ViewComponent
|
|
60
67
|
end
|
61
68
|
|
62
69
|
# Find a component stories by its underscored class name.
|
63
|
-
def
|
70
|
+
def find_story_configs(stories_name)
|
64
71
|
all.find { |stories| stories.stories_name == stories_name }
|
65
72
|
end
|
66
73
|
|
@@ -70,7 +77,7 @@ module ViewComponent
|
|
70
77
|
end
|
71
78
|
|
72
79
|
# find the story by name
|
73
|
-
def
|
80
|
+
def find_story_config(name)
|
74
81
|
story_configs.find { |config| config.name == name.to_sym }
|
75
82
|
end
|
76
83
|
|
@@ -92,7 +99,7 @@ module ViewComponent
|
|
92
99
|
def inherited(other)
|
93
100
|
super(other)
|
94
101
|
# setup class defaults
|
95
|
-
other.
|
102
|
+
other.stories_title = other.stories_name.humanize.titlecase
|
96
103
|
other.story_configs = []
|
97
104
|
end
|
98
105
|
|
@@ -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
|
@@ -4,37 +4,76 @@ module ViewComponent
|
|
4
4
|
module Storybook
|
5
5
|
class StoryConfig
|
6
6
|
include ActiveModel::Validations
|
7
|
+
include ContentConcern
|
8
|
+
include Controls::ControlsHelpers
|
7
9
|
|
8
|
-
attr_reader :id, :name, :
|
9
|
-
attr_accessor :parameters, :layout, :content_block
|
10
|
+
attr_reader :id, :name, :component_class
|
10
11
|
|
11
|
-
validate :validate_constructor_args
|
12
|
+
validate :validate_constructor_args, :validate_slots_args
|
12
13
|
|
13
|
-
def initialize(id, name,
|
14
|
+
def initialize(id, name, component_class, layout)
|
14
15
|
@id = id
|
15
16
|
@name = name
|
16
|
-
@
|
17
|
+
@component_class = component_class
|
17
18
|
@layout = layout
|
19
|
+
@slots ||= {}
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
22
|
+
def constructor(*args, **kwargs, &block)
|
23
|
+
@constructor_args = MethodArgs::ComponentConstructorArgs.from_component_class(
|
24
|
+
component_class,
|
25
|
+
*args,
|
26
|
+
**kwargs
|
27
|
+
)
|
28
|
+
content(nil, &block)
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Once deprecated block version is removed make this a private getter
|
34
|
+
def controls(&block)
|
35
|
+
if block_given?
|
36
|
+
ActiveSupport::Deprecation.warn("`controls` will be removed in v1.0.0. Use `#constructor` instead.")
|
37
|
+
controls_dsl = Dsl::LegacyControlsDsl.new
|
38
|
+
controls_dsl.instance_eval(&block)
|
39
|
+
|
40
|
+
controls_hash = controls_dsl.controls.index_by(&:param)
|
41
|
+
constructor(**controls_hash)
|
23
42
|
else
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
&block
|
29
|
-
)
|
43
|
+
list = constructor_args.controls.dup
|
44
|
+
list << content_control if content_control
|
45
|
+
list += slots.flat_map(&:controls) if slots
|
46
|
+
list
|
30
47
|
end
|
31
48
|
end
|
32
49
|
|
50
|
+
def layout(layout = nil)
|
51
|
+
@layout = layout unless layout.nil?
|
52
|
+
@layout
|
53
|
+
end
|
54
|
+
|
55
|
+
def parameters(parameters = nil)
|
56
|
+
@parameters = parameters unless parameters.nil?
|
57
|
+
@parameters
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
61
|
+
if component_class.slot_type(method_name)
|
62
|
+
slot(method_name, *args, **kwargs, &block)
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def respond_to_missing?(method_name, _include_private = false)
|
69
|
+
component_class.slot_type(method_name).present?
|
70
|
+
end
|
71
|
+
|
33
72
|
def to_csf_params
|
34
73
|
validate!
|
35
74
|
csf_params = { name: name, parameters: { server: { id: id } } }
|
36
75
|
csf_params.deep_merge!(parameters: parameters) if parameters.present?
|
37
|
-
|
76
|
+
controls.each do |control|
|
38
77
|
csf_params.deep_merge!(control.to_csf_params)
|
39
78
|
end
|
40
79
|
csf_params
|
@@ -44,17 +83,25 @@ module ViewComponent
|
|
44
83
|
valid? || raise(ValidationError, self)
|
45
84
|
end
|
46
85
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
86
|
+
##
|
87
|
+
# Build a Story from this config
|
88
|
+
# * Resolves the values of the constructor args from the params
|
89
|
+
# * constructs the component
|
90
|
+
# * resolve the content_control and content_block to a single block
|
91
|
+
# * builds a list of Slots by resolving their args from the params
|
92
|
+
def story(params)
|
93
|
+
# constructor_args.target_method is UnboundMethod so can't call it directly
|
94
|
+
component = constructor_args.call(params) do |*args, **kwargs|
|
95
|
+
component_class.new(*args, **kwargs)
|
96
|
+
end
|
52
97
|
|
53
|
-
|
54
|
-
return if constructor_args.valid?
|
98
|
+
story_content_block = resolve_content_block(params)
|
55
99
|
|
56
|
-
|
57
|
-
|
100
|
+
story_slots = slots.map do |slot_config|
|
101
|
+
slot_config.slot(component, params)
|
102
|
+
end
|
103
|
+
|
104
|
+
Storybook::Story.new(component, story_content_block, story_slots, layout)
|
58
105
|
end
|
59
106
|
|
60
107
|
class ValidationError < StandardError
|
@@ -69,8 +116,55 @@ module ViewComponent
|
|
69
116
|
|
70
117
|
private
|
71
118
|
|
72
|
-
def
|
73
|
-
|
119
|
+
def constructor_args
|
120
|
+
@constructor_args ||= MethodArgs::ComponentConstructorArgs.from_component_class(component_class)
|
121
|
+
end
|
122
|
+
|
123
|
+
def slots
|
124
|
+
@slots.values.flatten
|
125
|
+
end
|
126
|
+
|
127
|
+
def validate_constructor_args
|
128
|
+
return if constructor_args.valid?
|
129
|
+
|
130
|
+
constructor_args_errors = constructor_args.errors.full_messages.join(', ')
|
131
|
+
errors.add(:constructor_args, :invalid, errors: constructor_args_errors)
|
132
|
+
end
|
133
|
+
|
134
|
+
def validate_slots_args
|
135
|
+
slots.reject(&:valid?).each do |slot_config|
|
136
|
+
slot_errors = slot_config.errors.full_messages.join(', ')
|
137
|
+
errors.add(:slots, :invalid, errors: slot_errors)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def slot(slot_name, *args, **kwargs, &block)
|
142
|
+
# if the name is a slot then build a SlotConfig with slot_name and param the same
|
143
|
+
if component_class.slot_type(slot_name) == :collection_item
|
144
|
+
# generate a unique param generated by the count of slots with this name already added
|
145
|
+
@slots[slot_name] ||= []
|
146
|
+
slot_index = @slots[slot_name].count + 1
|
147
|
+
slot_config = Slots::SlotConfig.from_component(
|
148
|
+
component_class,
|
149
|
+
slot_name,
|
150
|
+
"#{slot_name}#{slot_index}".to_sym,
|
151
|
+
*args,
|
152
|
+
**kwargs,
|
153
|
+
&block
|
154
|
+
)
|
155
|
+
@slots[slot_name] << slot_config
|
156
|
+
else
|
157
|
+
slot_config = Slots::SlotConfig.from_component(
|
158
|
+
component_class,
|
159
|
+
slot_name,
|
160
|
+
slot_name,
|
161
|
+
*args,
|
162
|
+
**kwargs,
|
163
|
+
&block
|
164
|
+
)
|
165
|
+
@slots[slot_name] = slot_config
|
166
|
+
end
|
167
|
+
slot_config
|
74
168
|
end
|
75
169
|
end
|
76
170
|
end
|
@@ -10,9 +10,11 @@ module ViewComponent
|
|
10
10
|
autoload :Controls
|
11
11
|
autoload :Stories
|
12
12
|
autoload :StoryConfig
|
13
|
-
autoload :
|
14
|
-
autoload :
|
13
|
+
autoload :Story
|
14
|
+
autoload :Slots
|
15
|
+
autoload :ContentConcern
|
15
16
|
autoload :MethodArgs
|
17
|
+
autoload :Dsl
|
16
18
|
|
17
19
|
include ActiveSupport::Configurable
|
18
20
|
# Set the location of component previews through app configuration:
|
@@ -29,6 +31,25 @@ module ViewComponent
|
|
29
31
|
#
|
30
32
|
mattr_accessor :show_stories, instance_writer: false
|
31
33
|
|
34
|
+
# Set the entry route for component stories:
|
35
|
+
#
|
36
|
+
# config.view_component_storybook.stories_route = "/stories"
|
37
|
+
#
|
38
|
+
# Defaults to `/rails/stories` when `show_stories` is enabled.
|
39
|
+
#
|
40
|
+
mattr_accessor :stories_route, instance_writer: false
|
41
|
+
|
42
|
+
# :nocov:
|
43
|
+
if defined?(ViewComponent::Storybook::Engine)
|
44
|
+
ActiveSupport::Deprecation.warn(
|
45
|
+
"This manually engine loading is deprecated and will be removed in v1.0.0. " \
|
46
|
+
"Remove `require \"view_component/storybook/engine\"`."
|
47
|
+
)
|
48
|
+
elsif defined?(Rails::Engine)
|
49
|
+
require "view_component/storybook/engine"
|
50
|
+
end
|
51
|
+
# :nocov:
|
52
|
+
|
32
53
|
ActiveSupport.run_load_hooks(:view_component_storybook, self)
|
33
54
|
end
|
34
55
|
end
|