view_component_storybook 0.8.0 → 0.11.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.
- checksums.yaml +4 -4
- data/README.md +7 -128
- data/app/controllers/view_component/storybook/stories_controller.rb +10 -11
- data/app/views/view_component/storybook/stories/show.html.erb +8 -1
- data/config/locales/en.yml +32 -0
- 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/boolean_config.rb +7 -6
- data/lib/view_component/storybook/controls/color_config.rb +4 -5
- data/lib/view_component/storybook/controls/control_config.rb +24 -36
- data/lib/view_component/storybook/controls/controls_helpers.rb +76 -0
- data/lib/view_component/storybook/controls/custom_config.rb +52 -0
- data/lib/view_component/storybook/controls/date_config.rb +14 -11
- data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
- data/lib/view_component/storybook/controls/number_config.rb +9 -9
- data/lib/view_component/storybook/controls/object_config.rb +13 -5
- data/lib/view_component/storybook/controls/options_config.rb +17 -30
- data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
- data/lib/view_component/storybook/controls/text_config.rb +1 -1
- data/lib/view_component/storybook/controls.rb +5 -1
- data/lib/view_component/storybook/dsl/{controls_dsl.rb → legacy_controls_dsl.rb} +18 -21
- data/lib/view_component/storybook/dsl.rb +1 -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 +91 -0
- 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 +52 -0
- data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
- data/lib/view_component/storybook/method_args.rb +17 -0
- 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 +60 -10
- data/lib/view_component/storybook/story.rb +18 -0
- data/lib/view_component/storybook/story_config.rb +143 -15
- data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
- data/lib/view_component/storybook/version.rb +1 -1
- data/lib/view_component/storybook.rb +25 -0
- metadata +64 -20
- data/lib/view_component/storybook/controls/array_config.rb +0 -36
- data/lib/view_component/storybook/dsl/story_dsl.rb +0 -39
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Storybook
|
5
|
+
module MethodArgs
|
6
|
+
class MethodParametersNames
|
7
|
+
REQ_KWARG_TYPE = :keyreq
|
8
|
+
KWARG_TYPES = [REQ_KWARG_TYPE, :key].freeze
|
9
|
+
REQ_ARG_TYPE = :req
|
10
|
+
ARG_TYPES = [REQ_ARG_TYPE, :opt].freeze
|
11
|
+
KWARG_REST = :keyrest
|
12
|
+
REST = :rest
|
13
|
+
|
14
|
+
attr_reader :target_method
|
15
|
+
|
16
|
+
def initialize(target_method)
|
17
|
+
@target_method = target_method
|
18
|
+
end
|
19
|
+
|
20
|
+
def arg_name(pos)
|
21
|
+
if pos < named_arg_count
|
22
|
+
arg_names[pos]
|
23
|
+
else
|
24
|
+
offset_pos = pos - named_arg_count
|
25
|
+
"#{rest_arg_name}#{offset_pos}".to_sym
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def include_kwarg?(kwarg_name)
|
30
|
+
supports_keyrest? || kwarg_names.include?(kwarg_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def covers_required_kwargs?(names)
|
34
|
+
names.to_set >= req_kwarg_names.to_set
|
35
|
+
end
|
36
|
+
|
37
|
+
def max_arg_count
|
38
|
+
supports_rest? ? Float::INFINITY : named_arg_count
|
39
|
+
end
|
40
|
+
|
41
|
+
def min_arg_count
|
42
|
+
req_arg_count
|
43
|
+
end
|
44
|
+
|
45
|
+
def req_kwarg_names
|
46
|
+
@req_kwarg_names ||= parameters.map do |type, name|
|
47
|
+
name if type == REQ_KWARG_TYPE
|
48
|
+
end.compact
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def parameters
|
54
|
+
@parameters ||= target_method.parameters
|
55
|
+
end
|
56
|
+
|
57
|
+
def kwarg_names
|
58
|
+
@kwarg_names ||= parameters.map do |type, name|
|
59
|
+
name if KWARG_TYPES.include?(type)
|
60
|
+
end.compact
|
61
|
+
end
|
62
|
+
|
63
|
+
def arg_names
|
64
|
+
@arg_names ||= parameters.map do |type, name|
|
65
|
+
name if ARG_TYPES.include?(type)
|
66
|
+
end.compact
|
67
|
+
end
|
68
|
+
|
69
|
+
def req_arg_names
|
70
|
+
@req_arg_names ||= parameters.map do |type, name|
|
71
|
+
name if type == REQ_ARG_TYPE
|
72
|
+
end.compact
|
73
|
+
end
|
74
|
+
|
75
|
+
def named_arg_count
|
76
|
+
@named_arg_count ||= arg_names.count
|
77
|
+
end
|
78
|
+
|
79
|
+
def req_arg_count
|
80
|
+
@req_arg_count ||= req_arg_names.count
|
81
|
+
end
|
82
|
+
|
83
|
+
def rest_arg_name
|
84
|
+
@rest_arg_name ||= parameters.map { |type, name| name if type == REST }.first
|
85
|
+
end
|
86
|
+
|
87
|
+
def supports_keyrest?
|
88
|
+
@supports_keyrest ||= parameters.map(&:first).include?(KWARG_REST)
|
89
|
+
end
|
90
|
+
|
91
|
+
def supports_rest?
|
92
|
+
@supports_rest ||= parameters.map(&:first).include?(REST)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/dependencies/autoload"
|
4
|
+
|
5
|
+
module ViewComponent
|
6
|
+
module Storybook
|
7
|
+
module MethodArgs
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload :MethodArgs
|
11
|
+
autoload :MethodParametersNames
|
12
|
+
autoload :ControlMethodArgs
|
13
|
+
autoload :ComponentConstructorArgs
|
14
|
+
autoload :DryInitializerComponentConstructorArgs
|
15
|
+
end
|
16
|
+
end
|
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
|
@@ -4,19 +4,31 @@ module ViewComponent
|
|
4
4
|
module Storybook
|
5
5
|
class Stories
|
6
6
|
extend ActiveSupport::DescendantsTracker
|
7
|
+
include ActiveModel::Validations
|
7
8
|
|
8
9
|
class_attribute :story_configs, default: []
|
9
|
-
class_attribute :
|
10
|
+
class_attribute :stories_parameters, :stories_title, :stories_layout
|
11
|
+
|
12
|
+
validate :validate_story_configs
|
10
13
|
|
11
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
|
+
|
12
21
|
def story(name, component = default_component, &block)
|
13
|
-
story_config = StoryConfig.
|
22
|
+
story_config = StoryConfig.new(story_id(name), name, component, layout, &block)
|
23
|
+
story_config.instance_eval(&block)
|
14
24
|
story_configs << story_config
|
15
25
|
story_config
|
16
26
|
end
|
17
27
|
|
18
|
-
def parameters(
|
19
|
-
|
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
|
20
32
|
end
|
21
33
|
|
22
34
|
def layout(layout = nil)
|
@@ -26,6 +38,7 @@ module ViewComponent
|
|
26
38
|
end
|
27
39
|
|
28
40
|
def to_csf_params
|
41
|
+
validate!
|
29
42
|
csf_params = { title: title }
|
30
43
|
csf_params[:parameters] = parameters if parameters.present?
|
31
44
|
csf_params[:stories] = story_configs.map(&:to_csf_params)
|
@@ -34,9 +47,7 @@ module ViewComponent
|
|
34
47
|
|
35
48
|
def write_csf_json
|
36
49
|
json_path = File.join(stories_path, "#{stories_name}.stories.json")
|
37
|
-
File.
|
38
|
-
f.write(JSON.pretty_generate(to_csf_params))
|
39
|
-
end
|
50
|
+
File.write(json_path, JSON.pretty_generate(to_csf_params))
|
40
51
|
json_path
|
41
52
|
end
|
42
53
|
|
@@ -56,7 +67,7 @@ module ViewComponent
|
|
56
67
|
end
|
57
68
|
|
58
69
|
# Find a component stories by its underscored class name.
|
59
|
-
def
|
70
|
+
def find_story_configs(stories_name)
|
60
71
|
all.find { |stories| stories.stories_name == stories_name }
|
61
72
|
end
|
62
73
|
|
@@ -66,16 +77,29 @@ module ViewComponent
|
|
66
77
|
end
|
67
78
|
|
68
79
|
# find the story by name
|
69
|
-
def
|
80
|
+
def find_story_config(name)
|
70
81
|
story_configs.find { |config| config.name == name.to_sym }
|
71
82
|
end
|
72
83
|
|
84
|
+
# validation - ActiveModel::Validations like but on the class vs the instance
|
85
|
+
def valid?
|
86
|
+
# use an instance so we can enjoy the benefits of ActiveModel::Validations
|
87
|
+
@validation_instance = new
|
88
|
+
@validation_instance.valid?
|
89
|
+
end
|
90
|
+
|
91
|
+
delegate :errors, to: :@validation_instance
|
92
|
+
|
93
|
+
def validate!
|
94
|
+
valid? || raise(ValidationError, @validation_instance)
|
95
|
+
end
|
96
|
+
|
73
97
|
private
|
74
98
|
|
75
99
|
def inherited(other)
|
76
100
|
super(other)
|
77
101
|
# setup class defaults
|
78
|
-
other.
|
102
|
+
other.stories_title = other.stories_name.humanize.titlecase
|
79
103
|
other.story_configs = []
|
80
104
|
end
|
81
105
|
|
@@ -95,6 +119,32 @@ module ViewComponent
|
|
95
119
|
"#{stories_name}/#{name.to_s.parameterize}".underscore
|
96
120
|
end
|
97
121
|
end
|
122
|
+
|
123
|
+
protected
|
124
|
+
|
125
|
+
def validate_story_configs
|
126
|
+
story_configs.reject(&:valid?).each do |story_config|
|
127
|
+
story_errors = story_config.errors.full_messages.join(', ')
|
128
|
+
errors.add(:story_configs, :invalid_story, story_name: story_config.name, story_errors: story_errors)
|
129
|
+
end
|
130
|
+
|
131
|
+
story_names = story_configs.map(&:name)
|
132
|
+
duplicate_names = story_names.group_by(&:itself).map { |k, v| k if v.length > 1 }.compact
|
133
|
+
return if duplicate_names.empty?
|
134
|
+
|
135
|
+
duplicate_name_sentence = duplicate_names.map { |name| "'#{name}'" }.to_sentence
|
136
|
+
errors.add(:story_configs, :duplicate_stories, count: duplicate_names.count, duplicate_names: duplicate_name_sentence)
|
137
|
+
end
|
138
|
+
|
139
|
+
class ValidationError < StandardError
|
140
|
+
attr_reader :stories
|
141
|
+
|
142
|
+
def initialize(stories)
|
143
|
+
@stories = stories
|
144
|
+
|
145
|
+
super("#{@stories.class.name} invalid: (#{@stories.errors.full_messages.join(', ')})")
|
146
|
+
end
|
147
|
+
end
|
98
148
|
end
|
99
149
|
end
|
100
150
|
end
|
@@ -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,19 +4,73 @@ 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 :controls, :parameters, :layout, :content_block
|
10
|
+
attr_reader :id, :name, :component_class
|
10
11
|
|
11
|
-
|
12
|
+
validate :validate_constructor_args, :validate_slots_args
|
13
|
+
|
14
|
+
def initialize(id, name, component_class, layout)
|
12
15
|
@id = id
|
13
16
|
@name = name
|
14
|
-
@
|
17
|
+
@component_class = component_class
|
15
18
|
@layout = layout
|
16
|
-
@
|
19
|
+
@slots ||= {}
|
20
|
+
end
|
21
|
+
|
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)
|
42
|
+
else
|
43
|
+
list = constructor_args.controls.dup
|
44
|
+
list << content_control if content_control
|
45
|
+
list += slots.flat_map(&:controls) if slots
|
46
|
+
list
|
47
|
+
end
|
48
|
+
end
|
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?
|
17
70
|
end
|
18
71
|
|
19
72
|
def to_csf_params
|
73
|
+
validate!
|
20
74
|
csf_params = { name: name, parameters: { server: { id: id } } }
|
21
75
|
csf_params.deep_merge!(parameters: parameters) if parameters.present?
|
22
76
|
controls.each do |control|
|
@@ -25,18 +79,92 @@ module ViewComponent
|
|
25
79
|
csf_params
|
26
80
|
end
|
27
81
|
|
28
|
-
def
|
29
|
-
|
30
|
-
value = control.value_from_param(params[control.param])
|
31
|
-
value = control.value if value.nil? # nil only not falsey
|
32
|
-
[control.param, value]
|
33
|
-
end.to_h
|
82
|
+
def validate!
|
83
|
+
valid? || raise(ValidationError, self)
|
34
84
|
end
|
35
85
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
97
|
+
|
98
|
+
story_content_block = resolve_content_block(params)
|
99
|
+
|
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)
|
105
|
+
end
|
106
|
+
|
107
|
+
class ValidationError < StandardError
|
108
|
+
attr_reader :story_config
|
109
|
+
|
110
|
+
def initialize(story_config)
|
111
|
+
@story_config = story_config
|
112
|
+
|
113
|
+
super("'#{@story_config.name}' invalid: (#{@story_config.errors.full_messages.join(', ')})")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
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
|
40
168
|
end
|
41
169
|
end
|
42
170
|
end
|
@@ -4,10 +4,16 @@ namespace :view_component_storybook do
|
|
4
4
|
desc "Write CSF JSON stories for all Stories"
|
5
5
|
task write_stories_json: :environment do
|
6
6
|
puts "Writing Stories JSON"
|
7
|
+
exceptions = []
|
7
8
|
ViewComponent::Storybook::Stories.all.each do |stories|
|
8
9
|
json_path = stories.write_csf_json
|
9
10
|
puts "#{stories.name} => #{json_path}"
|
11
|
+
rescue StandardError => e
|
12
|
+
exceptions << e
|
10
13
|
end
|
14
|
+
|
15
|
+
raise StandardError, exceptions.map(&:message).join(", ") if exceptions.present?
|
16
|
+
|
11
17
|
puts "Done"
|
12
18
|
end
|
13
19
|
end
|
@@ -10,6 +10,10 @@ module ViewComponent
|
|
10
10
|
autoload :Controls
|
11
11
|
autoload :Stories
|
12
12
|
autoload :StoryConfig
|
13
|
+
autoload :Story
|
14
|
+
autoload :Slots
|
15
|
+
autoload :ContentConcern
|
16
|
+
autoload :MethodArgs
|
13
17
|
autoload :Dsl
|
14
18
|
|
15
19
|
include ActiveSupport::Configurable
|
@@ -27,6 +31,27 @@ module ViewComponent
|
|
27
31
|
#
|
28
32
|
mattr_accessor :show_stories, instance_writer: false
|
29
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 do
|
41
|
+
"/rails/stories"
|
42
|
+
end
|
43
|
+
|
44
|
+
# :nocov:
|
45
|
+
if defined?(ViewComponent::Storybook::Engine)
|
46
|
+
ActiveSupport::Deprecation.warn(
|
47
|
+
"This manually engine loading is deprecated and will be removed in v1.0.0. " \
|
48
|
+
"Remove `require \"view_component/storybook/engine\"`."
|
49
|
+
)
|
50
|
+
elsif defined?(Rails::Engine)
|
51
|
+
require "view_component/storybook/engine"
|
52
|
+
end
|
53
|
+
# :nocov:
|
54
|
+
|
30
55
|
ActiveSupport.run_load_hooks(:view_component_storybook, self)
|
31
56
|
end
|
32
57
|
end
|