amber_component 0.0.2 → 0.0.4

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