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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -1
- data/.solargraph.yml +1 -2
- data/CONTRIBUTING.md +87 -0
- data/Gemfile +4 -1
- data/Gemfile.lock +24 -99
- data/README.md +25 -42
- data/Rakefile +17 -1
- data/amber_component.gemspec +4 -2
- data/banner.png +0 -0
- data/docs/.bundle/config +2 -0
- data/docs/.gitignore +5 -0
- data/docs/404.html +25 -0
- data/docs/Gemfile +37 -0
- data/docs/Gemfile.lock +89 -0
- data/docs/README.md +19 -0
- data/docs/_config.yml +148 -0
- data/docs/_data/amber_component.yml +3 -0
- data/docs/_sass/_variables.scss +2 -0
- data/docs/_sass/color_schemes/amber_component.scss +11 -0
- data/docs/_sass/custom/custom.scss +60 -0
- data/docs/api/index.md +8 -0
- data/docs/assets/images/logo_wide.png +0 -0
- data/docs/changelog/index.md +8 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting_started/index.md +8 -0
- data/docs/getting_started/installation.md +7 -0
- data/docs/getting_started/ruby_support.md +7 -0
- data/docs/getting_started/wireframes.md +7 -0
- data/docs/index.md +17 -0
- data/docs/introduction/basic_usage.md +7 -0
- data/docs/introduction/index.md +8 -0
- data/docs/introduction/why_amber_component.md +7 -0
- data/docs/resources/index.md +8 -0
- data/docs/styles/index.md +8 -0
- data/docs/styles/usage.md +7 -0
- data/docs/views/index.md +8 -0
- data/docs/views/usage.md +7 -0
- data/icon.png +0 -0
- data/lib/amber_component/assets.rb +59 -0
- data/lib/amber_component/base.rb +85 -434
- data/lib/amber_component/helpers/class_helper.rb +22 -0
- data/lib/amber_component/helpers/component_helper.rb +19 -0
- data/lib/amber_component/helpers/css_helper.rb +25 -0
- data/lib/amber_component/helpers.rb +11 -0
- data/lib/amber_component/prop_definition.rb +54 -0
- data/lib/amber_component/props.rb +111 -0
- data/lib/amber_component/railtie.rb +21 -0
- data/lib/amber_component/rendering.rb +53 -0
- data/lib/amber_component/template_handler/erb.rb +17 -0
- data/lib/amber_component/template_handler.rb +26 -0
- data/lib/amber_component/typed_content.rb +3 -3
- data/lib/amber_component/version.rb +1 -1
- data/lib/amber_component/views.rb +198 -0
- data/lib/amber_component.rb +14 -11
- data/lib/generators/amber_component/install_generator.rb +23 -0
- data/lib/generators/amber_component/templates/application_component.rb +13 -0
- data/lib/generators/amber_component_generator.rb +24 -0
- data/lib/generators/templates/component.rb.erb +9 -0
- data/lib/generators/templates/component_test.rb.erb +9 -0
- data/lib/generators/templates/style.css.erb +3 -0
- data/lib/generators/templates/view.html.erb +3 -0
- metadata +87 -19
- data/.kanbn/index.md +0 -23
- data/.kanbn/tasks/add-instance-variables-to-view-when-block-given-markdown.md +0 -9
- data/.kanbn/tasks/bind-clas-to-action-view-method-call-example-component-data-data-without-calling-any-method.md +0 -9
- data/.kanbn/tasks/bind-scoped-css-to-head-of-doc.md +0 -10
- data/.kanbn/tasks/check-if-we-need-full-rails-gem-pack.md +0 -9
- data/.kanbn/tasks/simple-proto.md +0 -10
- data/.kanbn/tasks/verify-if-template-is-haml-or-erb.md +0 -9
- data/PLANS.md +0 -17
- data/lib/amber_component/helper.rb +0 -8
- data/lib/amber_component/style_injector.rb +0 -52
- data/sig/amber_components.rbs +0 -4
data/lib/amber_component/base.rb
CHANGED
@@ -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
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
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
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
206
|
-
#
|
207
|
-
|
208
|
-
|
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
|
-
|
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
|
-
#
|
214
|
-
# of this component that matches the provided `Regexp`
|
102
|
+
# Defines an instance method on the given `mod` Module/Class.
|
215
103
|
#
|
216
|
-
# @param
|
217
|
-
# @
|
218
|
-
|
219
|
-
|
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
|
-
::
|
222
|
-
next unless ::File.file?(::File.join(asset_dir_path, file))
|
111
|
+
return if ::ENV['RAILS_ENV'] == 'production'
|
223
112
|
|
224
|
-
|
225
|
-
|
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
|
-
|
235
|
-
end
|
236
|
-
end
|
127
|
+
next if bind_props(kwargs)
|
237
128
|
|
238
|
-
|
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
|
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
|