loco_motion-rails 0.0.6

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