loco_motion-rails 0.0.6

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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +880 -0
  4. data/app/components/daisy/actions/button_component.html.haml +7 -0
  5. data/app/components/daisy/actions/button_component.rb +52 -0
  6. data/app/components/daisy/actions/dropdown_component.html.haml +13 -0
  7. data/app/components/daisy/actions/dropdown_component.rb +48 -0
  8. data/app/components/daisy/actions/modal_component.html.haml +31 -0
  9. data/app/components/daisy/actions/modal_component.rb +92 -0
  10. data/app/components/daisy/actions/swap_component.html.haml +17 -0
  11. data/app/components/daisy/actions/swap_component.rb +56 -0
  12. data/app/components/daisy/actions/theme_controller_component.html.haml +5 -0
  13. data/app/components/daisy/actions/theme_controller_component.rb +8 -0
  14. data/app/components/daisy/data_display/accordion_component.html.haml +4 -0
  15. data/app/components/daisy/data_display/accordion_component.rb +82 -0
  16. data/app/components/daisy/data_display/avatar_component.html.haml +9 -0
  17. data/app/components/daisy/data_display/avatar_component.rb +60 -0
  18. data/app/components/daisy/data_display/badge_component.html.haml +2 -0
  19. data/app/components/daisy/data_display/badge_component.rb +27 -0
  20. data/app/components/daisy/data_display/card_component.html.haml +17 -0
  21. data/app/components/daisy/data_display/card_component.rb +53 -0
  22. data/app/components/daisy/data_display/carousel_component.html.haml +3 -0
  23. data/app/components/daisy/data_display/carousel_component.rb +17 -0
  24. data/app/components/daisy/data_display/chat_component.html.haml +14 -0
  25. data/app/components/daisy/data_display/chat_component.rb +38 -0
  26. data/app/components/daisy/data_display/collapse_component.html.haml +13 -0
  27. data/app/components/daisy/data_display/collapse_component.rb +37 -0
  28. data/app/components/daisy/data_display/countdown_component.html.haml +24 -0
  29. data/app/components/daisy/data_display/countdown_component.rb +70 -0
  30. data/app/components/daisy/data_display/countdown_controller.js +78 -0
  31. data/app/components/daisy/data_display/diff_component.html.haml +6 -0
  32. data/app/components/daisy/data_display/diff_component.rb +25 -0
  33. data/app/components/daisy/data_display/kbd_component.html.haml +2 -0
  34. data/app/components/daisy/data_display/kbd_component.rb +21 -0
  35. data/app/components/daisy/data_display/stat_component.html.haml +27 -0
  36. data/app/components/daisy/data_display/stat_component.rb +41 -0
  37. data/app/components/daisy/data_display/table_component.html.haml +14 -0
  38. data/app/components/daisy/data_display/table_component.rb +148 -0
  39. data/app/components/daisy/data_display/timeline_component.html.haml +7 -0
  40. data/app/components/daisy/data_display/timeline_component.rb +8 -0
  41. data/app/components/daisy/data_display/timeline_event_component.html.haml +28 -0
  42. data/app/components/daisy/data_display/timeline_event_component.rb +47 -0
  43. data/app/components/daisy/feedback/alert_component.html.haml +8 -0
  44. data/app/components/daisy/feedback/alert_component.rb +19 -0
  45. data/app/components/daisy/layout/join_component.rb +15 -0
  46. data/app/components/daisy/navigation/bottom_nav_component.rb +59 -0
  47. data/app/components/daisy/navigation/breadcrumbs_component.html.haml +7 -0
  48. data/app/components/daisy/navigation/breadcrumbs_component.rb +15 -0
  49. data/app/components/daisy/navigation/link_component.html.haml +4 -0
  50. data/app/components/daisy/navigation/link_component.rb +34 -0
  51. data/app/components/daisy/navigation/menu_component.html.haml +3 -0
  52. data/app/components/daisy/navigation/menu_component.rb +49 -0
  53. data/app/components/daisy/navigation/navbar_component.html.haml +4 -0
  54. data/app/components/daisy/navigation/navbar_component.rb +12 -0
  55. data/app/components/daisy/navigation/steps_component.rb +40 -0
  56. data/app/components/daisy/navigation/tabs_component.html.haml +4 -0
  57. data/app/components/daisy/navigation/tabs_component.rb +107 -0
  58. data/app/components/hero/icon_component.rb +18 -0
  59. data/lib/daisy/helpers.rb +61 -0
  60. data/lib/daisy.rb +19 -0
  61. data/lib/loco_motion/base_component.rb +371 -0
  62. data/lib/loco_motion/basic_component.rb +18 -0
  63. data/lib/loco_motion/component_config.rb +165 -0
  64. data/lib/loco_motion/engine.rb +8 -0
  65. data/lib/loco_motion/errors.rb +33 -0
  66. data/lib/loco_motion.rb +48 -0
  67. metadata +408 -0
@@ -0,0 +1,371 @@
1
+ class LocoMotion::BaseComponent < ViewComponent::Base
2
+
3
+ SELF_CLOSING_TAGS = %i[area base br col embed hr img input keygen link meta param source track wbr].freeze
4
+
5
+ include Heroicons::IconsHelper
6
+
7
+ class_attribute :component_name
8
+ class_attribute :component_parts, default: { component: {} }
9
+ class_attribute :valid_modifiers, default: []
10
+ class_attribute :valid_sizes, default: []
11
+
12
+ #
13
+ # Return the current configruation of this component.
14
+ #
15
+ # @return LocoMotion::ComponentConfig
16
+ #
17
+ attr_reader :config
18
+
19
+ #
20
+ # Allow users to alter the config through the component itself
21
+ #
22
+ delegate :set_tag_name, :add_css, :add_html, :add_stimulus_controller, :modifiers,
23
+ to: :config
24
+
25
+ #
26
+ # Create a new instance of a component.
27
+ #
28
+ def initialize(*args, **kws, &block)
29
+ super
30
+
31
+ # Create our config object
32
+ @config = LocoMotion::ComponentConfig.new(self, **kws, &block)
33
+ end
34
+
35
+ #
36
+ # Override the default slot to render the BasicComponent if no component is
37
+ # provided.
38
+ #
39
+ def self.renders_one(*args)
40
+ # If they don't pass extra options, default to BasicComponent
41
+ args&.size == 1 ? super(*args + [LocoMotion::BasicComponent]) : super
42
+ end
43
+
44
+ #
45
+ # Override the default many slot to render the BasicComponent if no component
46
+ # is provided.
47
+ #
48
+ def self.renders_many(*args)
49
+ # If they don't pass extra options, default to BasicComponent
50
+ args&.size == 1 ? super(*args + [LocoMotion::BasicComponent]) : super
51
+ end
52
+
53
+ #
54
+ # Sets the component name used in CSS generation.
55
+ #
56
+ # @param component_name [Symbol,String] The name of the component.
57
+ #
58
+ def self.set_component_name(component_name)
59
+ self.component_name = component_name
60
+ end
61
+
62
+ #
63
+ # Defines a new part of this component which can customize CSS, HTML and more.
64
+ #
65
+ # @param part_name [Symbol] The name of the part.
66
+ # @param part_defaults [Hash] Any default config options such as `tag_name`.
67
+ #
68
+ def self.define_part(part_name, part_defaults = {})
69
+ # Note that since we're using Rails' class_attribute method for these, we
70
+ # must take care not to alter the original object but rather use a setter
71
+ # (the `=` in this case) to set the new value so Rails knows not to override
72
+ # the parent value.
73
+ #
74
+ # For example, we cannot use `merge!` or `[part_name] = ` here.
75
+ self.component_parts = component_parts.merge({ part_name => part_defaults })
76
+ end
77
+
78
+ #
79
+ # Convenience method for defining multiple parts at once with no defaults.
80
+ #
81
+ # @param part_names [Array<Symbol>] The names of the parts you wish to define.
82
+ #
83
+ def self.define_parts(*part_names)
84
+ (part_names || []).each do |part_name|
85
+ define_part(part_name)
86
+ end
87
+ end
88
+
89
+ #
90
+ # Defines a single modifier of this component. Modifiers control certain
91
+ # rendering aspects of the component.
92
+ #
93
+ # @param modifier_name [Symbol] The name of the modifier.
94
+ #
95
+ def self.define_modifier(modifier_name)
96
+ define_modifiers(modifier_name)
97
+ end
98
+
99
+ #
100
+ # Define multiple modifiers for this component. Modifiers control certain
101
+ # rendering aspects of the component.
102
+ #
103
+ # @param modifier_names [Array[Symbol]] An array of the modifier names you wish
104
+ # to define.
105
+ #
106
+ def self.define_modifiers(*modifier_names)
107
+ # Note that since we're using Rails' class_attribute method for these, we
108
+ # must take care not to alter the original object but rather use a setter
109
+ # (the `+=` in this case) to set the new value so Rails knows not to
110
+ # override the parent value.
111
+ #
112
+ # For example, we cannot use `<<` or `concat` here.
113
+ self.valid_modifiers ||= []
114
+ self.valid_modifiers += modifier_names
115
+ end
116
+
117
+ #
118
+ # Define a single size of this component. Sizes control how big or small this
119
+ # component will render.
120
+ #
121
+ # @param size_name [Symbol] The name of the size you wish to define.
122
+ #
123
+ def self.define_size(size_name)
124
+ define_sizes(size_name)
125
+ end
126
+
127
+ #
128
+ # Define multiple sizes for this component. Sizes control how big or small
129
+ # this component will render.
130
+ #
131
+ # @param size_names [Array[Symbol]] An array of the sizes you wish to define.
132
+ #
133
+ def self.define_sizes(*size_names)
134
+ # Note that since we're using Rails' class_attribute method for these, we
135
+ # must take care not to alter the original object but rather use a setter
136
+ # (the `+=` in this case) to set the new value so Rails knows not to
137
+ # override the parent value.
138
+ #
139
+ # For example, we cannot use `<<` or `concat` here.
140
+ self.valid_sizes ||= []
141
+ self.valid_sizes += size_names
142
+ end
143
+
144
+ #
145
+ # Allows you to bulid a customized version of this component without having to
146
+ # define a new class.
147
+ #
148
+ def self.build(*build_args, **build_kws, &build_block)
149
+ klass = Class.new(self)
150
+
151
+ # Unless already defined, delegate the name method to the superclass so
152
+ # ViewComponent can find the sidecar partials and render them when no call
153
+ # method is defined.
154
+ unless klass.method_defined?(:name)
155
+ klass.instance_eval do
156
+ def name
157
+ superclass.name
158
+ end
159
+ end
160
+ end
161
+
162
+ # Override the initialize method to combine the build and instance args
163
+ klass.class_eval do
164
+ original_initialize = method_defined?(:initialize) ? instance_method(:initialize) : nil
165
+
166
+ define_method(:initialize) do |*instance_args, **instance_kws, &instance_block|
167
+ if original_initialize
168
+ original_initialize.bind(self).call
169
+ else
170
+ super(*instance_args, **instance_kws, &instance_block)
171
+ end
172
+
173
+ @config.smart_merge!(**build_kws)
174
+ end
175
+ end
176
+
177
+ # Finally, execute any block they passed in to allow for customizations
178
+ klass.class_eval(&build_block) if block_given?
179
+
180
+ klass
181
+ end
182
+
183
+ #
184
+ # Returns a reference to this component. Useful for passing a parent component
185
+ # into child components.
186
+ #
187
+ # @return [BaseComponent] A reference to this component.
188
+ #
189
+ def component_ref
190
+ self
191
+ end
192
+
193
+
194
+ #
195
+ # Sets the parent component of this component. Enables child components to ask
196
+ # questions of their parent and access parent config.
197
+ #
198
+ def set_loco_parent(parent)
199
+ @loco_parent = parent
200
+ end
201
+ attr_reader :loco_parent
202
+
203
+ #
204
+ # Renders the given part.
205
+ #
206
+ def part(part_name, &block)
207
+
208
+ # Validate the part_name
209
+ @config.validate_part(part_name)
210
+
211
+ # Grab the rendered tag name
212
+ tag_name = rendered_tag_name(part_name)
213
+
214
+ if block_given?
215
+ content_tag(tag_name, **rendered_html(part_name), &block)
216
+ else
217
+ # The `tag()` helper will allow you to pass any tag without a block, but
218
+ # this isn't valid HTML. In particular, it will render a "self-closing"
219
+ # <div /> tag which doesn't actually close the div.
220
+ #
221
+ # Therefore, we need to pass some kind of block to ensure it closes. We've
222
+ # choosen a comment to keep the output as clean as possible while still
223
+ # informing a developer what is happening.
224
+ if SELF_CLOSING_TAGS.include?(tag_name.to_sym)
225
+ tag(tag_name, **rendered_html(part_name))
226
+ else
227
+ content_tag(tag_name, **rendered_html(part_name)) do
228
+ "<!-- Empty Part Block //-->".html_safe
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ #
235
+ # Returns the user-provided or component-default HTML tag-name.
236
+ #
237
+ # @param part_name [Symbol] The part whose tag-name you desire.
238
+ #
239
+ # @return [Symbol,String] The HTML tag-name for the requested comopnent part.
240
+ #
241
+ def rendered_tag_name(part_name)
242
+ part = @config.get_part(part_name)
243
+
244
+ part[:user_tag_name] || part[:default_tag_name]
245
+ end
246
+
247
+ #
248
+ # Builds a string suitable for the HTML element's `class` attribute for the
249
+ # requested component part.
250
+ #
251
+ # @param part_name [Symbol] The component part whose CSS you desire.
252
+ #
253
+ # @return [String] A string of CSS names.
254
+ #
255
+ def rendered_css(part_name)
256
+ default_css = @config.get_part(part_name)[:default_css]
257
+ user_css = @config.get_part(part_name)[:user_css]
258
+
259
+ cssify([default_css, user_css])
260
+ end
261
+
262
+ #
263
+ # Builds a Hash of all of the HTML attributes for the requested component
264
+ # part.
265
+ #
266
+ # @param part_name [Symbol] The component part whose HTML you desire.
267
+ #
268
+ # @return [Hash] A combination of all generated, component default, and
269
+ # user-specified HTML attributes for the part.
270
+ #
271
+ def rendered_html(part_name)
272
+ default_html = @config.get_part(part_name)[:default_html] || {}
273
+ user_html = @config.get_part(part_name)[:user_html] || {}
274
+
275
+ generated_html = {
276
+ class: rendered_css(part_name),
277
+ data: rendered_data(part_name)
278
+ }.deep_merge(default_html).deep_merge(user_html)
279
+ end
280
+
281
+ #
282
+ # Builds the HTML `data` attribute.
283
+ #
284
+ # @param part_name [Symbol] The component part whose HTML `data` attribute
285
+ # you desire.
286
+ #
287
+ # @return [Hash] A hash of objects to be rendered in the `data` attribute.
288
+ #
289
+ def rendered_data(part_name)
290
+ generated_data = {}
291
+
292
+ stimulus_controllers = rendered_stimulus_controllers(part_name)
293
+
294
+ generated_data[:controller] = stimulus_controllers if stimulus_controllers.present?
295
+
296
+ generated_data
297
+ end
298
+
299
+ #
300
+ # Builds a list of Stimulus controllers for the HTML `data-controller`
301
+ # attribute.
302
+ #
303
+ # @param part_name [Symbol] The component part whose Stimulus controllers you
304
+ # desire.
305
+ #
306
+ # @ return [String] A space-separated list of Stimulus controllers.
307
+ #
308
+ def rendered_stimulus_controllers(part_name)
309
+ default_controllers = @config.get_part(part_name)[:default_stimulus_controllers]
310
+ user_controllers = @config.get_part(part_name)[:user_stimulus_controllers]
311
+
312
+ strip_spaces([default_controllers, user_controllers].join(" "))
313
+ end
314
+
315
+ #
316
+ # Convert strings, symbols, and arrays of those into a single CSS-like string.
317
+ #
318
+ def cssify(content)
319
+ css = [content].flatten.compact
320
+
321
+ strip_spaces(css.join(" "))
322
+ end
323
+
324
+ #
325
+ # Strip extra whitespace from a given string.
326
+ #
327
+ # @param str [String] The string you wish to strip.
328
+ #
329
+ # @return [String] A string with minimal possible whitespace.
330
+ #
331
+ def strip_spaces(str)
332
+ str.gsub(/ +/, " ").strip
333
+ end
334
+
335
+ #
336
+ # Retrieve the requested component option, or the desired default if no option
337
+ # was provided.
338
+ #
339
+ # @param key [Symbol] The name of the keyword argument option you wish to
340
+ # retrieve.
341
+ # @param default [Object] Any value that you wish to use as a default should
342
+ # the option be undefined. Defaults to `nil`.
343
+ #
344
+ def config_option(key, default = nil)
345
+ value = @config.options[key]
346
+
347
+ value.nil? ? default : value
348
+ end
349
+
350
+ # Provide some nice output for debugging or other purposes.
351
+ #
352
+ def inspect
353
+ parts = component_parts.map do |part_name, part_defaults|
354
+ {
355
+ part_name: part_name,
356
+ tag_name: rendered_tag_name(part_name),
357
+ css: rendered_css(part_name),
358
+ html: rendered_html(part_name)
359
+ }
360
+ end
361
+
362
+ [
363
+ "#<#{self.class.name}",
364
+ "@component_name=#{(component_name || :unnamed).inspect}",
365
+ "@valid_modifiers=#{valid_modifiers.inspect}",
366
+ "@valid_sizes=#{valid_sizes.inspect}",
367
+ "@config=#{@config.inspect}",
368
+ "@component_parts=#{parts.inspect}",
369
+ ].join(" ") + ">"
370
+ end
371
+ end
@@ -0,0 +1,18 @@
1
+ #
2
+ # The BasicComponent class is used for all slots that don't provide a component
3
+ # so that users can pass in all of the same CSS and HTML options that a standard
4
+ # component would have.
5
+ #
6
+ class LocoMotion::BasicComponent < LocoMotion.configuration.base_component_class
7
+
8
+ def call
9
+ part(:component) do
10
+ content
11
+ end
12
+ end
13
+
14
+ def self.name
15
+ "BasicComponent"
16
+ end
17
+
18
+ end
@@ -0,0 +1,165 @@
1
+ class LocoMotion::ComponentConfig
2
+ attr_reader :component, :parts, :options, :modifiers, :size
3
+
4
+ def initialize(component, **kws, &block)
5
+ @component = component
6
+ @options = kws
7
+
8
+ @parts = {}
9
+ @modifiers = (kws[:modifiers] || [kws[:modifier]]).compact
10
+ @size = kws[:size]
11
+
12
+ build
13
+ validate
14
+ end
15
+
16
+ def build
17
+ # Allow users to pass css/html for a specific part (i.e. modal_dialog)
18
+ @component.component_parts.each do |part, defaults|
19
+ @parts[part] = {
20
+ default_css: [],
21
+ default_html: {},
22
+ default_tag_name: defaults[:tag_name] || :div,
23
+ default_stimulus_controllers: [],
24
+
25
+ user_css: @options["#{part}_css".to_sym] || [],
26
+ user_html: @options["#{part}_html".to_sym] || {},
27
+ user_tag_name: @options["#{part}_tag_name".to_sym],
28
+ user_stimulus_controllers: @options["#{part}_controllers".to_sym] || [],
29
+ }
30
+ end
31
+
32
+ # Allow useres to pass some shortened attributes for the component part
33
+ merge_user_options!(**@options)
34
+ end
35
+
36
+ #
37
+ # Add specific component user options if they pass shortened attributes.
38
+ #
39
+ def merge_user_options!(**kws)
40
+ @parts[:component][:user_tag_name] = kws[:tag_name] if kws[:tag_name]
41
+ @parts[:component][:user_css].push(kws[:css]) if kws[:css]
42
+ @parts[:component][:user_html].deep_merge!(kws[:html]) if kws[:html]
43
+ @parts[:component][:user_stimulus_controllers].push(kws[:controllers]) if kws[:controllers]
44
+ end
45
+
46
+ #
47
+ # Merge additional options into the defaults config by combining the new
48
+ # options with the existing options, rather than overwriting (where possible).
49
+ #
50
+ # HTML will be deep merged, CSS will be appended, and tag_name will be
51
+ # overwritten.
52
+ #
53
+ def smart_merge!(**kws)
54
+ @component.component_parts.each do |part, defaults|
55
+ set_tag_name(part, kws["#{part}_tag_name".to_sym])
56
+ add_css(part, kws["#{part}_css".to_sym])
57
+ add_html(part, kws["#{part}_html".to_sym])
58
+
59
+ controllers = kws["#{part}_controllers".to_sym] || []
60
+
61
+ controllers.each do |controller_name|
62
+ add_stimulus_controller(part, controller_name)
63
+ end
64
+ end
65
+
66
+ # Make sure to merge any user-provided options as well
67
+ merge_user_options!(**kws)
68
+ end
69
+
70
+ #
71
+ # Returns the part for the reqeust part name or an empty hash if none was
72
+ # found.
73
+ #
74
+ def get_part(part_name)
75
+ @parts[part_name] || {}
76
+ end
77
+
78
+ #
79
+ # Sets the default tag name for the requested component part.
80
+ #
81
+ def set_tag_name(part_name, tag_name)
82
+ @parts[part_name][:default_tag_name] = tag_name if tag_name
83
+ end
84
+
85
+ #
86
+ # Adds default CSS to the requested component part.
87
+ #
88
+ def add_css(part_name, css)
89
+ @parts[part_name][:default_css] << css if css
90
+ end
91
+
92
+ #
93
+ # Adds default HTML to the requested component part.
94
+ #
95
+ def add_html(part_name, html)
96
+ @parts[part_name][:default_html] = @parts[part_name][:default_html].deep_merge(html) if html
97
+ end
98
+
99
+ #
100
+ # Add a default Stimulus (Javascript) controller to the requested component part.
101
+ #
102
+ def add_stimulus_controller(part_name, controller_name)
103
+ @parts[part_name] ||= {}
104
+ @parts[part_name][:default_stimulus_controllers] ||= []
105
+ @parts[part_name][:default_stimulus_controllers] << controller_name
106
+ end
107
+
108
+ #
109
+ # Validate the component config and throw errors if there are issues.
110
+ #
111
+ def validate
112
+ validate_modifiers
113
+ end
114
+
115
+ #
116
+ # Validate that all of the modifiers are correct.
117
+ #
118
+ def validate_modifiers
119
+ # Check to make sure they have passed a valid / defined modifier
120
+ (@modifiers || []).each do |modifier|
121
+ if modifier.present? && !@component.valid_modifiers.include?(modifier)
122
+ raise LocoMotion::InvalidModifierError.new(modifier, @component)
123
+ end
124
+ end
125
+ end
126
+
127
+ #
128
+ # Validates that the requested part is valid for the component.
129
+ #
130
+ def validate_part(part_name)
131
+ raise LocoMotion::UnknownPartError.new(part_name, @component) unless valid_parts.include?(part_name)
132
+ end
133
+
134
+ #
135
+ # Return a list of valid parts for the component.
136
+ #
137
+ def valid_parts
138
+ @parts.keys
139
+ end
140
+
141
+ #
142
+ # Render a Hash version of the config.
143
+ #
144
+ def to_h
145
+ {
146
+ options: @options,
147
+ parts: @parts,
148
+ modifiers: @modifiers,
149
+ size: @size
150
+ }
151
+ end
152
+
153
+ #
154
+ # For now, just return the Hash version for inspect.
155
+ #
156
+ def inspect
157
+ [
158
+ "#<#{self.class.name}",
159
+ "@options=#{@options.inspect}",
160
+ "@parts=#{@parts.inspect}",
161
+ "@modifiers=#{@modifiers.inspect}",
162
+ "@size=#{@size.inspect}",
163
+ ].join(" ") + ">"
164
+ end
165
+ end
@@ -0,0 +1,8 @@
1
+ module LocoMotion
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace LocoMotion
4
+
5
+ config.autoload_paths << "#{root}/app"
6
+ config.autoload_paths << "#{root}/lib"
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ module LocoMotion
2
+ class UnknownPartError < StandardError
3
+ def initialize(part, component, custom_message = nil)
4
+ no_parts_explanation = "No parts are defined on the component."
5
+ default_explanation = "Valid parts are #{component.config.valid_parts.map(&:inspect).to_sentence}."
6
+
7
+ has_parts = component.config.valid_parts.present?
8
+
9
+ default_message = [
10
+ "Unknown part #{part.inspect}.",
11
+ "#{has_parts ? default_explanation : no_parts_explanation}"
12
+ ].join(' ')
13
+
14
+ super(custom_message || default_message)
15
+ end
16
+ end
17
+
18
+ class InvalidModifierError < StandardError
19
+ def initialize(modifier, component, custom_message = nil)
20
+ no_modifiers_explanation = "No modifiers are defined on the component."
21
+ default_explanation = "Valid modifiers are #{component.valid_modifiers.map(&:inspect).to_sentence}."
22
+
23
+ has_modifiers = component.valid_modifiers.present?
24
+
25
+ default_message = [
26
+ "Unknown modifier #{modifier.inspect}.",
27
+ "#{has_modifiers ? default_explanation : no_modifiers_explanation}"
28
+ ].join(' ')
29
+
30
+ super(custom_message || default_message)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ require "rails"
2
+ require "haml-rails"
3
+ require "heroicons-rails"
4
+
5
+ require Gem::Specification.find_by_name("heroicons-rails").gem_dir + "/app/helpers/heroicons/icons_helper.rb"
6
+
7
+ require "view_component"
8
+ require "loco_motion/engine"
9
+ require "loco_motion/errors"
10
+ require "loco_motion/component_config"
11
+ require "loco_motion/base_component"
12
+
13
+ require "daisy"
14
+
15
+ #
16
+ # Module containing all features related to the LocoMotion gem.
17
+ #
18
+ module LocoMotion
19
+
20
+ class << self
21
+ def configure
22
+ yield(configuration)
23
+ end
24
+
25
+ def configuration
26
+ @configuration ||= Configuration.new
27
+ end
28
+
29
+ # Mostly used for internal testing; not needed in Rails
30
+ def require_components
31
+ Dir.glob(File.dirname(__FILE__) + '/../app/components/**/*.rb').each do |file|
32
+ require file
33
+ end
34
+ end
35
+
36
+ def define_render_helper(name, component)
37
+ end
38
+ end
39
+
40
+ class Configuration
41
+ attr_accessor :base_component_class
42
+
43
+ def initialize
44
+ @base_component_class = LocoMotion::BaseComponent
45
+ end
46
+ end
47
+
48
+ end