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