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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -138
  3. data/app/controllers/view_component/storybook/stories_controller.rb +9 -11
  4. data/app/views/view_component/storybook/stories/show.html.erb +8 -3
  5. data/config/locales/en.yml +1 -1
  6. data/lib/view_component/storybook/content_concern.rb +42 -0
  7. data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
  8. data/lib/view_component/storybook/controls/control_config.rb +4 -0
  9. data/lib/view_component/storybook/{dsl/controls_dsl.rb → controls/controls_helpers.rb} +21 -16
  10. data/lib/view_component/storybook/controls/custom_config.rb +2 -4
  11. data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
  12. data/lib/view_component/storybook/controls/object_config.rb +3 -1
  13. data/lib/view_component/storybook/controls/options_config.rb +12 -26
  14. data/lib/view_component/storybook/controls/simple_control_config.rb +1 -1
  15. data/lib/view_component/storybook/controls.rb +3 -1
  16. data/lib/view_component/storybook/dsl/legacy_controls_dsl.rb +2 -2
  17. data/lib/view_component/storybook/dsl.rb +0 -2
  18. data/lib/view_component/storybook/engine.rb +13 -2
  19. data/lib/view_component/storybook/method_args/component_constructor_args.rb +23 -0
  20. data/lib/view_component/storybook/method_args/control_method_args.rb +15 -12
  21. data/lib/view_component/storybook/method_args/dry_initializer_component_constructor_args.rb +45 -0
  22. data/lib/view_component/storybook/method_args/method_args.rb +37 -4
  23. data/lib/view_component/storybook/method_args/method_parameters_names.rb +1 -1
  24. data/lib/view_component/storybook/method_args.rb +2 -1
  25. data/lib/view_component/storybook/slots/slot.rb +24 -0
  26. data/lib/view_component/storybook/slots/slot_config.rb +79 -0
  27. data/lib/view_component/storybook/slots.rb +14 -0
  28. data/lib/view_component/storybook/stories.rb +17 -10
  29. data/lib/view_component/storybook/story.rb +18 -0
  30. data/lib/view_component/storybook/story_config.rb +120 -26
  31. data/lib/view_component/storybook/tasks/write_stories_json.rake +1 -1
  32. data/lib/view_component/storybook/version.rb +1 -1
  33. data/lib/view_component/storybook.rb +23 -2
  34. metadata +43 -8
  35. data/lib/view_component/storybook/controls/array_config.rb +0 -37
  36. data/lib/view_component/storybook/dsl/story_dsl.rb +0 -51
  37. 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
- # I addition the args and kwargs can contain Controls the values of which can
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 < ValidatableMethodArgs
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, block)
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.param("#{param_prefix}__#{arg.param}".to_sym) if param_prefix.present?
71
+ arg.prefix_param(param_prefix) if param_prefix.present?
63
72
  end
64
73
 
65
74
  def value_from_params(arg, params)
66
- if control?(arg)
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?(ViewComponent::Storybook::Controls::ControlConfig)
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
- # Simple class representing arguments passed to a method
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
- attr_reader :args, :kwargs, :block
10
+ include ActiveModel::Validations
10
11
 
11
- def initialize(args, kwargs, block)
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
- @block = block
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
@@ -57,7 +57,7 @@ module ViewComponent
57
57
  def kwarg_names
58
58
  @kwarg_names ||= parameters.map do |type, name|
59
59
  name if KWARG_TYPES.include?(type)
60
- end.compact.to_set
60
+ end.compact
61
61
  end
62
62
 
63
63
  def arg_names
@@ -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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/dependencies/autoload"
4
+
5
+ module ViewComponent
6
+ module Storybook
7
+ module Slots
8
+ extend ActiveSupport::Autoload
9
+
10
+ autoload :SlotConfig
11
+ autoload :Slot
12
+ end
13
+ end
14
+ end
@@ -7,19 +7,28 @@ module ViewComponent
7
7
  include ActiveModel::Validations
8
8
 
9
9
  class_attribute :story_configs, default: []
10
- class_attribute :parameters, :title, :stories_layout
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.configure(story_id(name), name, component, layout, &block)
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(**params)
22
- self.parameters = params
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.open(json_path, "w") do |f|
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 find_stories(stories_name)
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 find_story(name)
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.title = other.stories_name.humanize.titlecase
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, :component
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, component, layout)
14
+ def initialize(id, name, component_class, layout)
14
15
  @id = id
15
16
  @name = name
16
- @component = component
17
+ @component_class = component_class
17
18
  @layout = layout
19
+ @slots ||= {}
18
20
  end
19
21
 
20
- def constructor_args(*args, **kwargs, &block)
21
- if args.empty? && kwargs.empty? && block.nil?
22
- @constructor_args ||= ViewComponent::Storybook::MethodArgs::ControlMethodArgs.new(component_constructor)
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
- @constructor_args = ViewComponent::Storybook::MethodArgs::ControlMethodArgs.new(
25
- component_constructor,
26
- *args,
27
- **kwargs,
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
- constructor_args.controls.each do |control|
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
- def self.configure(id, name, component, layout, &configuration)
48
- config = new(id, name, component, layout)
49
- ViewComponent::Storybook::Dsl::StoryDsl.evaluate!(config, &configuration)
50
- config
51
- end
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
- def validate_constructor_args
54
- return if constructor_args.valid?
98
+ story_content_block = resolve_content_block(params)
55
99
 
56
- constructor_args_errors = constructor_args.errors.full_messages.join(', ')
57
- errors.add(:constructor_args, :invalid, errors: constructor_args_errors)
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 component_constructor
73
- component.instance_method(:initialize)
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
@@ -12,7 +12,7 @@ namespace :view_component_storybook do
12
12
  exceptions << e
13
13
  end
14
14
 
15
- raise StandardError, exceptions.map(:message).join(", ") if exceptions.present?
15
+ raise StandardError, exceptions.map(&:message).join(", ") if exceptions.present?
16
16
 
17
17
  puts "Done"
18
18
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ViewComponent
4
4
  module Storybook
5
- VERSION = "0.9.0"
5
+ VERSION = "0.11.1"
6
6
  end
7
7
  end
@@ -10,9 +10,11 @@ module ViewComponent
10
10
  autoload :Controls
11
11
  autoload :Stories
12
12
  autoload :StoryConfig
13
- autoload :ControlMethodArgs
14
- autoload :Dsl
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