amber_component 0.0.2 → 0.0.4

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -1
  5. data/.solargraph.yml +1 -2
  6. data/CONTRIBUTING.md +87 -0
  7. data/Gemfile +4 -1
  8. data/Gemfile.lock +24 -99
  9. data/README.md +25 -42
  10. data/Rakefile +17 -1
  11. data/amber_component.gemspec +4 -2
  12. data/banner.png +0 -0
  13. data/docs/.bundle/config +2 -0
  14. data/docs/.gitignore +5 -0
  15. data/docs/404.html +25 -0
  16. data/docs/Gemfile +37 -0
  17. data/docs/Gemfile.lock +89 -0
  18. data/docs/README.md +19 -0
  19. data/docs/_config.yml +148 -0
  20. data/docs/_data/amber_component.yml +3 -0
  21. data/docs/_sass/_variables.scss +2 -0
  22. data/docs/_sass/color_schemes/amber_component.scss +11 -0
  23. data/docs/_sass/custom/custom.scss +60 -0
  24. data/docs/api/index.md +8 -0
  25. data/docs/assets/images/logo_wide.png +0 -0
  26. data/docs/changelog/index.md +8 -0
  27. data/docs/favicon.ico +0 -0
  28. data/docs/getting_started/index.md +8 -0
  29. data/docs/getting_started/installation.md +7 -0
  30. data/docs/getting_started/ruby_support.md +7 -0
  31. data/docs/getting_started/wireframes.md +7 -0
  32. data/docs/index.md +17 -0
  33. data/docs/introduction/basic_usage.md +7 -0
  34. data/docs/introduction/index.md +8 -0
  35. data/docs/introduction/why_amber_component.md +7 -0
  36. data/docs/resources/index.md +8 -0
  37. data/docs/styles/index.md +8 -0
  38. data/docs/styles/usage.md +7 -0
  39. data/docs/views/index.md +8 -0
  40. data/docs/views/usage.md +7 -0
  41. data/icon.png +0 -0
  42. data/lib/amber_component/assets.rb +59 -0
  43. data/lib/amber_component/base.rb +85 -434
  44. data/lib/amber_component/helpers/class_helper.rb +22 -0
  45. data/lib/amber_component/helpers/component_helper.rb +19 -0
  46. data/lib/amber_component/helpers/css_helper.rb +25 -0
  47. data/lib/amber_component/helpers.rb +11 -0
  48. data/lib/amber_component/prop_definition.rb +54 -0
  49. data/lib/amber_component/props.rb +111 -0
  50. data/lib/amber_component/railtie.rb +21 -0
  51. data/lib/amber_component/rendering.rb +53 -0
  52. data/lib/amber_component/template_handler/erb.rb +17 -0
  53. data/lib/amber_component/template_handler.rb +26 -0
  54. data/lib/amber_component/typed_content.rb +3 -3
  55. data/lib/amber_component/version.rb +1 -1
  56. data/lib/amber_component/views.rb +198 -0
  57. data/lib/amber_component.rb +14 -11
  58. data/lib/generators/amber_component/install_generator.rb +23 -0
  59. data/lib/generators/amber_component/templates/application_component.rb +13 -0
  60. data/lib/generators/amber_component_generator.rb +24 -0
  61. data/lib/generators/templates/component.rb.erb +9 -0
  62. data/lib/generators/templates/component_test.rb.erb +9 -0
  63. data/lib/generators/templates/style.css.erb +3 -0
  64. data/lib/generators/templates/view.html.erb +3 -0
  65. metadata +87 -19
  66. data/.kanbn/index.md +0 -23
  67. data/.kanbn/tasks/add-instance-variables-to-view-when-block-given-markdown.md +0 -9
  68. data/.kanbn/tasks/bind-clas-to-action-view-method-call-example-component-data-data-without-calling-any-method.md +0 -9
  69. data/.kanbn/tasks/bind-scoped-css-to-head-of-doc.md +0 -10
  70. data/.kanbn/tasks/check-if-we-need-full-rails-gem-pack.md +0 -9
  71. data/.kanbn/tasks/simple-proto.md +0 -10
  72. data/.kanbn/tasks/verify-if-template-is-haml-or-erb.md +0 -9
  73. data/PLANS.md +0 -17
  74. data/lib/amber_component/helper.rb +0 -8
  75. data/lib/amber_component/style_injector.rb +0 -52
  76. data/sig/amber_components.rbs +0 -4
@@ -3,490 +3,141 @@
3
3
  require 'set'
4
4
  require 'erb'
5
5
  require 'tilt'
6
- require 'active_model/callbacks'
7
6
  require 'memery'
7
+ require 'active_model/callbacks'
8
+ require 'action_view'
8
9
 
9
- require_relative './style_injector'
10
-
11
- # Abstract class which serves as a base
12
- # for all Amber Components.
13
- #
14
- # There are a few life cycle callbacks that can be defined.
15
- # The same way as in `ActiveRecord` models and `ActionController` controllers.
16
- #
17
- # - before_render
18
- # - around_render
19
- # - after_render
20
- # - before_initialize
21
- # - around_initialize
22
- # - after_initialize
23
- #
24
- # class ButtonComponent < ::AmberComponent::Base
25
- # # You can provide a Symbol of the method that should be called
26
- # before_render :before_render_method
27
- # # Or provide a block that will be executed
28
- # after_initialize do
29
- # # Your code here
30
- # end
31
- #
32
- # def before_render_method
33
- # # Your code here
34
- # end
35
- # end
36
- #
37
- #
38
- # @abstract Create a subclass to define a new component.
39
10
  module ::AmberComponent
40
- class Base # :nodoc:
11
+ # Abstract class which serves as a base
12
+ # for all Amber Components.
13
+ #
14
+ # There are a few life cycle callbacks that can be defined.
15
+ # The same way as in `ActiveRecord` models and `ActionController` controllers.
16
+ #
17
+ # - before_render
18
+ # - around_render
19
+ # - after_render
20
+ # - before_initialize
21
+ # - around_initialize
22
+ # - after_initialize
23
+ #
24
+ # class ButtonComponent < ::AmberComponent::Base
25
+ # # You can provide a Symbol of the method that should be called
26
+ # before_render :before_render_method
27
+ # # Or provide a block that will be executed
28
+ # after_initialize do
29
+ # # Your code here
30
+ # end
31
+ #
32
+ # def before_render_method
33
+ # # Your code here
34
+ # end
35
+ # end
36
+ #
37
+ #
38
+ # @abstract Create a subclass to define a new component.
39
+ class Base < ::ActionView::Base
40
+ # for defining callback such as `after_initialize`
41
41
  extend ::ActiveModel::Callbacks
42
-
43
- include Helper
44
-
45
- # @return [Regexp]
46
- VIEW_FILE_REGEXP = /^view\./.freeze
47
- # @return [Regexp]
48
- STYLE_FILE_REGEXP = /^style\./.freeze
49
-
50
- # View types with built-in embedded Ruby
51
- #
52
- # @return [Set<Symbol>]
53
- VIEW_TYPES_WITH_RUBY = ::Set[:erb, :haml, :slim].freeze
54
- # @return [Set<Symbol>]
55
- ALLOWED_VIEW_TYPES = ::Set[:erb, :haml, :slim, :html, :md, :markdown].freeze
56
- # @return [Set<Symbol>]
57
- ALLOWED_STYLE_TYPES = ::Set[:sass, :scss, :less].freeze
42
+ extend Helpers::ClassHelper
43
+
44
+ include Helpers::CssHelper
45
+ include Views::InstanceMethods
46
+ extend Views::ClassMethods
47
+ include Assets::InstanceMethods
48
+ extend Assets::ClassMethods
49
+ include Rendering::InstanceMethods
50
+ extend Rendering::ClassMethods
51
+ include Props::InstanceMethods
52
+ extend Props::ClassMethods
58
53
 
59
54
  class << self
60
55
  include ::Memery
61
56
 
62
- # @param kwargs [Hash{Symbol => Object}]
63
- # @return [String]
64
- def run(**kwargs, &block)
65
- comp = new(**kwargs)
66
-
67
- comp.render(&block)
68
- end
69
-
70
- alias call run
71
-
72
-
73
- # @return [String]
74
- memoize def asset_dir_path
75
- class_const_name = name.split('::').last
76
- component_file_path, = module_parent.const_source_location(class_const_name)
77
-
78
- component_file_path.delete_suffix('.rb')
79
- end
80
-
81
- # @return [String]
82
- def view_path
83
- asset_path view_file_name
84
- end
85
-
86
- # @return [String, nil]
87
- def view_file_name
88
- files = asset_file_names(VIEW_FILE_REGEXP)
89
- raise MultipleViews, "More than one view file for `#{name}` found!" if files.length > 1
90
-
91
- files.first
92
- end
93
-
94
- # @return [Symbol]
95
- def view_type
96
- (view_file_name.split('.')[1..].reject { _1.match?(/erb/) }.last || 'erb')&.to_sym
97
- end
98
-
99
- # @return [String]
100
- def style_path
101
- asset_path style_file_name
102
- end
103
-
104
- # @return [String, nil]
105
- def style_file_name
106
- files = asset_file_names(STYLE_FILE_REGEXP)
107
- raise MultipleStyles, "More than one style file for `#{name}` found!" if files.length > 1
108
-
109
- files.first
110
- end
111
-
112
- # @return [Symbol]
113
- def style_type
114
- (style_file_name.split('.')[1..].reject { _1.match?(/erb/) }.last || 'erb')&.to_sym
115
- end
57
+ memoize :asset_dir_path
116
58
 
117
59
  # Memoize these methods in production
118
- if defined?(::Rails) && ::Rails.env.production?
60
+ if ::ENV['RAILS_ENV'] == 'production'
119
61
  memoize :view_path
120
62
  memoize :view_file_name
121
63
  memoize :view_type
122
-
123
- memoize :style_path
124
- memoize :style_file_name
125
- memoize :style_type
126
- end
127
-
128
- # Register an inline view by returning a String from the passed block.
129
- #
130
- # Usage:
131
- #
132
- # view do
133
- # <<~ERB
134
- # <h1>
135
- # Hello <%= @name %>
136
- # </h1>
137
- # ERB
138
- # end
139
- #
140
- # or:
141
- #
142
- # view :haml do
143
- # <<~HAML
144
- # %h1
145
- # Hello
146
- # = @name
147
- # HAML
148
- # end
149
- #
150
- # @param type [Symbol]
151
- # @return [void]
152
- def view(type = :erb, &block)
153
- @method_view = TypedContent.new(type: type, content: block)
154
64
  end
155
65
 
156
- # ERB/Haml/Slim view registered through the `view` method.
157
- #
158
- # @return [TypedContent]
159
- attr_reader :method_view
160
-
161
- # Register an inline style by returning a String from the passed block.
162
- #
163
- # Usage:
164
- #
165
- # style do
166
- # '.my-class { color: red; }'
167
- # end
168
- #
169
- # or:
170
- #
171
- # style :sass do
172
- # <<~SASS
173
- # .my-class
174
- # color: red
175
- # SASS
176
- # end
177
- #
178
- # @param type [Symbol]
179
- # @return [void]
180
- def style(type = :css, &block)
181
- @method_style = TypedContent.new(type: type, content: block)
182
- end
183
-
184
- # CSS/SCSS/Sass styles registered through the `style` method.
185
- #
186
- # @return [TypedContent]
187
- attr_reader :method_style
188
-
189
66
  private
190
67
 
191
68
  # @param subclass [Class]
192
69
  # @return [void]
193
70
  def inherited(subclass)
194
- # @type [Module]
195
- parent_module = subclass.module_parent
196
- method_body = proc do |**kwargs, &block|
197
- subclass.run(**kwargs, &block)
71
+ super
72
+ method_body = lambda do |**kwargs, &block|
73
+ subclass.render(**kwargs, &block)
198
74
  end
75
+ parent_module = subclass.module_parent
199
76
 
200
- Helper.define_method(subclass.name, &method_body) && return if parent_module.equal? ::Object
77
+ if parent_module.equal?(::Object)
78
+ method_name = subclass.name
79
+ define_helper_method(subclass, Helpers::ComponentHelper, method_name.underscore, method_body)
80
+ return
81
+ end
201
82
 
202
- parent_module.define_singleton_method(subclass.name.split('::').last, &method_body)
83
+ method_name = subclass.const_name
84
+ define_helper_method(subclass, parent_module.singleton_class, method_name.underscore, method_body)
203
85
  end
204
86
 
205
- # @param file_name [String, nil]
206
- # @return [String, nil]
207
- def asset_path(file_name)
208
- return unless file_name
87
+ # Gets or defines an anonymous module that
88
+ # will store all dynamically generated helper methods
89
+ # for the received module/class.
90
+ #
91
+ # @param mod [Module, Class]
92
+ # @return [Module]
93
+ def helper_module(mod)
94
+ ivar_name = :@__amber_component_helper_module
95
+ mod.instance_variable_get(ivar_name)&.then { return _1 }
209
96
 
210
- ::File.join(asset_dir_path, file_name)
97
+ helper_mod = mod.instance_variable_set(ivar_name, ::Module.new)
98
+ mod.include helper_mod
99
+ helper_mod
211
100
  end
212
101
 
213
- # Returns the name of the file inside the asset directory
214
- # of this component that matches the provided `Regexp`
102
+ # Defines an instance method on the given `mod` Module/Class.
215
103
  #
216
- # @param type_regexp [Regexp]
217
- # @return [Array<String>]
218
- def asset_file_names(type_regexp)
219
- return [] unless ::File.directory?(asset_dir_path)
104
+ # @param component [Class]
105
+ # @param target_mod [Module, Class]
106
+ # @param method_name [String, Symbol]
107
+ # @param body [Proc]
108
+ def define_helper_method(component, target_mod, method_name, body)
109
+ helper_module(target_mod).define_method(method_name, &body)
220
110
 
221
- ::Dir.entries(asset_dir_path).select do |file|
222
- next unless ::File.file?(::File.join(asset_dir_path, file))
111
+ return if ::ENV['RAILS_ENV'] == 'production'
223
112
 
224
- file.match? type_regexp
225
- end
113
+ ::Warning.warn <<~WARN if target_mod.instance_methods.include?(method_name)
114
+ #{caller(0, 1).first}: warning:
115
+ `#{component}` shadows the name of an already existing `#{target_mod}` method `#{method_name}`.
116
+ Consider renaming this component, because the original method will be overridden.
117
+ WARN
226
118
  end
227
119
  end
228
120
 
229
121
  define_model_callbacks :initialize, :render
230
122
 
231
123
  # @param kwargs [Hash{Symbol => Object}]
124
+ # @raise [AmberComponent::MissingPropsError] when required props are missing
232
125
  def initialize(**kwargs)
233
126
  run_callbacks :initialize do
234
- bind_variables(kwargs)
235
- end
236
- end
127
+ next if bind_props(kwargs)
237
128
 
238
- # @return [String]
239
- def render(&block)
240
- run_callbacks :render do
241
- element = inject_views(&block)
242
- styles = inject_styles
243
- element += styles unless styles.nil?
244
- element.html_safe
129
+ bind_instance_variables(kwargs)
245
130
  end
246
131
  end
247
132
 
248
- def render_in(context)
249
- byebug
250
- end
251
-
252
133
  private
253
134
 
254
135
  # @param kwargs [Hash{Symbol => Object}]
255
136
  # @return [void]
256
- def bind_variables(kwargs)
137
+ def bind_instance_variables(kwargs)
257
138
  kwargs.each do |key, value|
258
139
  instance_variable_set("@#{key}", value)
259
140
  end
260
141
  end
261
-
262
- # Helper method to render view from string or with other provided type.
263
- #
264
- # Usage:
265
- #
266
- # render_custom_view('<h1>Hello World</h1>')
267
- #
268
- # or:
269
- #
270
- # render_custom_view content: '**Hello World**', type: 'md'
271
- #
272
- # @param style [TypedContent, Hash{Symbol => String, Symbol, Proc}, String]
273
- # @return [String, nil]
274
- def render_custom_view(view, &block)
275
- return '' unless view
276
- return view if view.is_a?(::String)
277
-
278
- view = TypedContent.wrap(view)
279
- type = view.type
280
- content = view.to_s
281
-
282
- if content.empty?
283
- raise EmptyView, <<~ERR.squish
284
- Custom view for `#{self.class}` from view method cannot be empty!
285
- ERR
286
- end
287
-
288
- unless ALLOWED_VIEW_TYPES.include? type
289
- raise UnknownViewType, <<~ERR.squish
290
- Unknown view type for `#{self.class}` from view method!
291
- Check return value of param type in `view :[type] do`
292
- ERR
293
- end
294
-
295
- unless VIEW_TYPES_WITH_RUBY.include? type
296
- # first render the content with ERB if the
297
- # type does not support embedding Ruby by default
298
- content = render_string(content, :erb, block)
299
- end
300
-
301
- render_string(content, type, block)
302
- end
303
-
304
- # @return [String]
305
- def render_view_from_file(&block)
306
- view_path = self.class.view_path
307
- return '' if view_path.nil? || !::File.file?(view_path)
308
-
309
- content = ::File.read(view_path)
310
- type = self.class.view_type
311
-
312
- unless VIEW_TYPES_WITH_RUBY.include? type
313
- content = render_string(content, :erb, block)
314
- end
315
-
316
- render_string(content, type, block)
317
- end
318
-
319
- # Method returning view from method in class file.
320
- # Usage:
321
- #
322
- # view do
323
- # <<~HTML
324
- # <h1>
325
- # Hello <%= @name %>
326
- # </h1>
327
- # HTML
328
- # end
329
- #
330
- # or:
331
- #
332
- # view :haml do
333
- # <<~HAML
334
- # %h1
335
- # Hello
336
- # = @name
337
- # HAML
338
- # end
339
- #
340
- # @return [String]
341
- def render_class_method_view(&block)
342
- render_custom_view(self.class.method_view, &block)
343
- end
344
-
345
- # Method returning view from params in view.
346
- # Usage:
347
- #
348
- # <%= ExampleComponent data: data, view: "<h1>Hello #{@name}</h1>" %>
349
- #
350
- # or:
351
- #
352
- # <%= ExampleComponent data: data, view: { content: "<h1>Hello #{@name}</h1>", type: 'erb' } %>
353
- #
354
- # @return [String]
355
- def render_view_from_inline(&block)
356
- data = \
357
- if @view.is_a? String
358
- TypedContent.new(
359
- type: :erb,
360
- content: @view
361
- )
362
- else
363
- @view
364
- end
365
-
366
- render_custom_view(data, &block)
367
- end
368
-
369
- # @return [String]
370
- def inject_views(&block)
371
- view_from_file = render_view_from_file(&block)
372
- view_from_method = render_class_method_view(&block)
373
- view_from_inline = render_view_from_inline(&block)
374
-
375
- view_content = view_from_file unless view_from_file.empty?
376
- view_content = view_from_method unless view_from_method.empty?
377
- view_content = view_from_inline unless view_from_inline.empty?
378
-
379
- if view_content.nil? || view_content.empty?
380
- raise ViewFileNotFound, "View for `#{self.class}` could not be found!"
381
- end
382
-
383
- view_content
384
- end
385
-
386
- # Helper method to render style from css string or with other provided type.
387
- #
388
- # Usage:
389
- #
390
- # render_custom_style('.my-class { color: red; }')
391
- #
392
- # or:
393
- #
394
- # render_custom_style style: '.my-class { color: red; }', type: 'sass'
395
- #
396
- # @param style [TypedContent, Hash{Symbol => Symbol, String, Proc}, String]
397
- # @return [String, nil]
398
- def render_custom_style(style)
399
- return '' unless style
400
- return style if style.is_a?(::String)
401
-
402
- style = TypedContent.wrap(style)
403
- type = style.type
404
- content = style.to_s
405
-
406
- if content.empty?
407
- raise EmptyStyle, <<~ERR.squish
408
- Custom style for `#{self.class}` from style method cannot be empty!
409
- ERR
410
- end
411
-
412
- unless ALLOWED_STYLE_TYPES.include? type
413
- raise UnknownStyleType, <<~ERR.squish
414
- Unknown style type for `#{self.class}` from style method!
415
- Check return value of param type in `style :[type] do`
416
- ERR
417
- end
418
-
419
- # first render the content with ERB
420
- content = render_string(content, :erb)
421
-
422
- render_string(content, type)
423
- end
424
-
425
- # Method returning style from file (style.(css|sass|scss|less)) if exists.
426
- #
427
- # @return [String]
428
- def render_style_from_file
429
- style_path = self.class.style_path
430
- return '' unless style_path
431
-
432
- content = ::File.read(style_path)
433
- type = self.class.style_type
434
-
435
- return content if type == :css
436
-
437
- content = render_string(content, :erb)
438
- render_string(content, type)
439
- end
440
-
441
- # Method returning style from method in class file.
442
- # Usage:
443
- #
444
- # style do
445
- # '.my-class { color: red; }'
446
- # end
447
- #
448
- # or:
449
- #
450
- # style :sass do
451
- # <<~SASS
452
- # .my-class
453
- # color: red
454
- # SASS
455
- # end
456
- #
457
- # @return [String]
458
- def render_class_method_style
459
- render_custom_style(self.class.method_style)
460
- end
461
-
462
- # Method returning style from params in view.
463
- # Usage:
464
- #
465
- # <%= ExampleComponent data: data, style: '.my-class { color: red; }' %>
466
- #
467
- # or:
468
- #
469
- # <%= ExampleComponent data: data, style: {style: '.my-class { color: red; }', type: 'sass'} %>
470
- #
471
- # @return [String]
472
- def render_style_from_inline
473
- render_custom_style(@style)
474
- end
475
-
476
- # @param content [String]
477
- # @param type [Symbol]
478
- # @param block [Proc, nil]
479
- # @return [String]
480
- def render_string(content, type, block = nil)
481
- ::Tilt[type].new { content }.render(self, &block)
482
- end
483
-
484
- # @return [String]
485
- def inject_styles
486
- style_content = render_style_from_file + render_class_method_style + render_style_from_inline
487
- return if style_content.empty?
488
-
489
- ::AmberComponent::StyleInjector.inject(style_content)
490
- end
491
142
  end
492
143
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::AmberComponent
4
+ module Helpers
5
+ # Adds class-specific utilities.
6
+ module ClassHelper
7
+ # Name of the constant this class/module is saved in (in the parent module).
8
+ #
9
+ # @return [String]
10
+ def const_name
11
+ name.split('::').last
12
+ end
13
+
14
+ # Get the exact place where this class/module has been defined.
15
+ #
16
+ # @return [Array(String, Integer), Array(Boolean, Integer)] File path followed by line number.
17
+ def source_location
18
+ module_parent.const_source_location const_name
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_view'
4
+
5
+ module ::AmberComponent
6
+ module Helpers
7
+ # Contains methods for quickly rendering
8
+ # components defined under the root namespace `Object`.
9
+ module ComponentHelper
10
+ @__amber_component_helper_module = self
11
+ end
12
+ end
13
+ end
14
+
15
+ class ::ActionView::Base
16
+ # Add those convenience methods to all
17
+ # controllers and views.
18
+ include ::AmberComponent::Helpers::ComponentHelper
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_view'
4
+
5
+ module ::AmberComponent
6
+ module Helpers
7
+ # Adds a few utility methods for working with CSS
8
+ # inside components.
9
+ module CssHelper
10
+ # Helper method which creates a name for a css class or id
11
+ # which is scoped to the current component class.
12
+ #
13
+ # self.class #=> Navigation::DropdownMenuComponent
14
+ # css_id(:list_item) #=> "navigation-dropdown_menu_component--list_item"
15
+ #
16
+ # @param name [String, Symbol]
17
+ # @return [String]
18
+ def css_identifier(name)
19
+ "#{self.class.name.underscore.gsub('/', '-')}--#{name.to_s.underscore}"
20
+ end
21
+
22
+ alias css_id css_identifier
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::AmberComponent
4
+ # Contains all helpers added by `amber_component`
5
+ module Helpers
6
+ end
7
+ end
8
+
9
+ require_relative 'helpers/component_helper'
10
+ require_relative 'helpers/class_helper'
11
+ require_relative 'helpers/css_helper'
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::AmberComponent
4
+ # Internal class which represents a property
5
+ # on a component class.
6
+ class PropDefinition
7
+ # @return [Symbol]
8
+ attr_reader :name
9
+ # @return [Class, nil]
10
+ attr_reader :type
11
+ # @return [Boolean]
12
+ attr_reader :required
13
+ # @return [Object, Proc, nil]
14
+ attr_reader :default
15
+ # @return [Boolean]
16
+ attr_reader :allow_nil
17
+
18
+ # @param name [Symbol]
19
+ # @param type [Class, nil]
20
+ # @param required [Boolean]
21
+ # @param default [Object, Proc, nil]
22
+ # @param allow_nil [Boolean]
23
+ def initialize(name:, type: nil, required: false, default: nil, allow_nil: false)
24
+ @name = name
25
+ @type = type
26
+ @required = required
27
+ @default = default
28
+ @allow_nil = allow_nil
29
+ end
30
+
31
+ alias required? required
32
+ alias allow_nil? allow_nil
33
+
34
+ # @return [Boolean]
35
+ def type?
36
+ !@type.nil?
37
+ end
38
+
39
+ # @return [Boolean]
40
+ def default?
41
+ !@default.nil?
42
+ end
43
+
44
+ # Evaluate the default value if it's a `Proc`
45
+ # and return the result.
46
+ #
47
+ # @return [Object]
48
+ def default!
49
+ return @default.call if @default.is_a?(::Proc)
50
+
51
+ @default
52
+ end
53
+ end
54
+ end