amber_component 0.0.5 → 1.1.0
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/Gemfile.lock +6 -6
- data/README.md +238 -92
- data/amber_component.gemspec +0 -3
- data/assets/javascripts/amber_component/stimulus_loading.js +30 -0
- data/banner.png +0 -0
- data/lib/amber_component/configuration.rb +43 -0
- data/lib/amber_component/props.rb +1 -1
- data/lib/amber_component/railtie.rb +3 -1
- data/lib/amber_component/version.rb +1 -1
- data/lib/amber_component.rb +20 -0
- data/lib/generators/amber_component/install_generator.rb +85 -2
- data/lib/generators/amber_component_generator.rb +35 -0
- data/lib/generators/templates/controller.js.erb +12 -0
- data/lib/generators/templates/view.haml.erb +5 -1
- data/lib/generators/templates/view.html.erb.erb +4 -1
- data/lib/generators/templates/view.slim.erb +5 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3fe50a4aaf1482572196e8ae5fd9fd7e2f04ef1ee07b9b5275b0454623e54a6d
|
|
4
|
+
data.tar.gz: 17c1e2848263efac38a029621d26a8d2eae5b4b3d47d2c9eaf8e040b14fe3eae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e566cb7199d6d8c890081557ed20ac02410085992b036b6a603e909a62ac6fb6af0e853910ab02ac087fd8e3bcaf398d59496e7172ac1233d843206785b15509
|
|
7
|
+
data.tar.gz: 3b8bb6257ba09f3f6fbf98c68a128ab99adfa289e8ee3d674de744052b4b69f7b06c0e5f43a5e7bb4ae54533a78728fea15ba2534b947b4e730869b5e09b8932
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
amber_component (
|
|
4
|
+
amber_component (1.1.0)
|
|
5
5
|
actionview (>= 6)
|
|
6
6
|
activemodel (>= 6)
|
|
7
7
|
activesupport (>= 6)
|
|
@@ -94,21 +94,21 @@ GEM
|
|
|
94
94
|
rainbow (3.1.1)
|
|
95
95
|
rake (13.0.6)
|
|
96
96
|
rchardet (1.8.0)
|
|
97
|
-
regexp_parser (2.
|
|
97
|
+
regexp_parser (2.6.0)
|
|
98
98
|
reverse_markdown (2.1.1)
|
|
99
99
|
nokogiri
|
|
100
100
|
rexml (3.2.5)
|
|
101
|
-
rubocop (1.
|
|
101
|
+
rubocop (1.38.0)
|
|
102
102
|
json (~> 2.3)
|
|
103
103
|
parallel (~> 1.10)
|
|
104
104
|
parser (>= 3.1.2.1)
|
|
105
105
|
rainbow (>= 2.2.2, < 4.0)
|
|
106
106
|
regexp_parser (>= 1.8, < 3.0)
|
|
107
107
|
rexml (>= 3.2.5, < 4.0)
|
|
108
|
-
rubocop-ast (>= 1.
|
|
108
|
+
rubocop-ast (>= 1.23.0, < 2.0)
|
|
109
109
|
ruby-progressbar (~> 1.7)
|
|
110
110
|
unicode-display_width (>= 1.4.0, < 3.0)
|
|
111
|
-
rubocop-ast (1.
|
|
111
|
+
rubocop-ast (1.23.0)
|
|
112
112
|
parser (>= 3.1.1.0)
|
|
113
113
|
ruby-progressbar (1.11.0)
|
|
114
114
|
ruby2_keywords (0.0.5)
|
|
@@ -124,7 +124,7 @@ GEM
|
|
|
124
124
|
simplecov (~> 0.19)
|
|
125
125
|
simplecov-html (0.12.3)
|
|
126
126
|
simplecov_json_formatter (0.1.4)
|
|
127
|
-
solargraph (0.
|
|
127
|
+
solargraph (0.47.2)
|
|
128
128
|
backport (~> 1.2)
|
|
129
129
|
benchmark
|
|
130
130
|
bundler (>= 1.17.2)
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
# AmberComponent
|
|
11
11
|
|
|
12
|
-
AmberComponent is a simple component library which seamlessly hooks into your Rails project and allows you to create simple backend components
|
|
12
|
+
AmberComponent is a simple component library which seamlessly hooks into your Rails project and allows you to create simple backend components which consist of a Ruby controller, view, stylesheet and even a JavaScript controller (using [Stimulus](https://stimulus.hotwired.dev/)).
|
|
13
13
|
|
|
14
14
|
Created by [Garbus Beach](https://github.com/garbusbeach) and [Mateusz Drewniak](https://github.com/Verseth).
|
|
15
15
|
|
|
@@ -33,16 +33,37 @@ If you're using a Rails application there's an installation generator that you s
|
|
|
33
33
|
$ bin/rails generate amber_component:install
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Amber component supports [Stimulus](https://stimulus.hotwired.dev/) to make your components
|
|
37
|
+
reactive using JavaScript.
|
|
38
|
+
|
|
39
|
+
If you want to use stimulus you should install this gem with the `--stimulus` flag
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
$ bin/rails generate amber_component:install --stimulus
|
|
43
|
+
```
|
|
44
|
+
|
|
36
45
|
## Usage
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
### Components
|
|
39
48
|
|
|
40
|
-
Components are located under `app/components`.
|
|
49
|
+
Components are located under `app/components`. And their tests under `test/components`.
|
|
41
50
|
|
|
42
51
|
Every component consists of:
|
|
43
52
|
- a Ruby file which defines its properties, encapsulates logic and may implement helper methods (like a controller)
|
|
44
53
|
- a view/template file (html.erb, haml, slim etc.)
|
|
45
54
|
- a style file (css, scss, sass etc.)
|
|
55
|
+
- [optional] a JavaScript file with a Stimulus controller (if you installed the gem with `--stimulus`)
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
app/components/
|
|
59
|
+
├─ [name]_component.rb
|
|
60
|
+
└─ [name]_component/
|
|
61
|
+
├─ style.css # may be .sass or .scss
|
|
62
|
+
├─ view.html.erb
|
|
63
|
+
└─ controller.js # if stimulus is configured
|
|
64
|
+
test/components/
|
|
65
|
+
└─ [name]_component_test.rb
|
|
66
|
+
```
|
|
46
67
|
|
|
47
68
|
An individual component which implements a button may look like this.
|
|
48
69
|
|
|
@@ -57,7 +78,9 @@ end
|
|
|
57
78
|
```html
|
|
58
79
|
<!-- app/components/button_component/view.html.erb -->
|
|
59
80
|
|
|
60
|
-
<div class="button_component"
|
|
81
|
+
<div class="button_component"
|
|
82
|
+
data-controller="button-component"
|
|
83
|
+
data-action="click->button-component#greet">
|
|
61
84
|
<%= label %>
|
|
62
85
|
</div>
|
|
63
86
|
```
|
|
@@ -76,6 +99,25 @@ end
|
|
|
76
99
|
}
|
|
77
100
|
```
|
|
78
101
|
|
|
102
|
+
If you used the `--stimulus` option when installing the gem, a JS controller will be generated as well.
|
|
103
|
+
```js
|
|
104
|
+
// app/components/button_component/controller.js
|
|
105
|
+
|
|
106
|
+
import { Controller } from "@hotwired/stimulus"
|
|
107
|
+
|
|
108
|
+
// Read more about Stimulus here https://stimulus.hotwired.dev/
|
|
109
|
+
export default class extends Controller {
|
|
110
|
+
connect() {
|
|
111
|
+
console.log("Stimulus controller 'button-component' is connected!")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
greet() {
|
|
115
|
+
alert("Hi there!")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
|
|
79
121
|
You can render this component in other components or in a Rails view.
|
|
80
122
|
|
|
81
123
|
```html
|
|
@@ -97,34 +139,69 @@ ButtonComponent.call label: 'Click me!'
|
|
|
97
139
|
#=> '<div class="button_component">Click me!</div>'
|
|
98
140
|
```
|
|
99
141
|
|
|
100
|
-
###
|
|
142
|
+
### Components with namespaces
|
|
101
143
|
|
|
102
|
-
|
|
103
|
-
of all ActionView helpers and Rails route helpers.
|
|
144
|
+
Components may be defined inside multiple modules/namespaces.
|
|
104
145
|
|
|
105
|
-
|
|
146
|
+
```ruby
|
|
147
|
+
# app/components/sign_up/button_component.rb
|
|
106
148
|
|
|
107
|
-
|
|
108
|
-
|
|
149
|
+
class SignUp::ButtonComponent < AmberComponent::Base
|
|
150
|
+
prop :label, required: true
|
|
151
|
+
end
|
|
152
|
+
```
|
|
109
153
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<%= f.text_field :first_name %>
|
|
154
|
+
```html
|
|
155
|
+
<!-- app/components/sign_up/button_component/view.html.erb -->
|
|
113
156
|
|
|
114
|
-
|
|
115
|
-
|
|
157
|
+
<div class="sign_up_button_component">
|
|
158
|
+
<%= label %>
|
|
159
|
+
</div>
|
|
160
|
+
```
|
|
116
161
|
|
|
117
|
-
|
|
118
|
-
|
|
162
|
+
```scss
|
|
163
|
+
// app/components/sign_up/button_component/style.scss
|
|
119
164
|
|
|
120
|
-
|
|
121
|
-
|
|
165
|
+
.sign_up_button_component {
|
|
166
|
+
background-color: indigo;
|
|
167
|
+
border-radius: 1rem;
|
|
168
|
+
transition-duration: 500ms;
|
|
122
169
|
|
|
123
|
-
|
|
124
|
-
|
|
170
|
+
&:hover {
|
|
171
|
+
background-color: blue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
125
175
|
|
|
126
|
-
|
|
127
|
-
|
|
176
|
+
You can render such a component by calling the `::call` method
|
|
177
|
+
on its class, or by using the helper method defined on its parent module.
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
SignUp::ButtonComponent.call label: 'Sign up!'
|
|
181
|
+
SignUp.button_component label: 'Sign up!'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Generating Components
|
|
185
|
+
|
|
186
|
+
You can generate new components by running
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
$ bin/rails generate component [name]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Name of the component may be PascalCased like `FooBar` or snake_cased `foo_bar`
|
|
193
|
+
|
|
194
|
+
This will generate a new component in `app/components/[name]_component.rb` along with a view, stylesheet, test file and a stimulus controller (if configured).
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
app/components/
|
|
198
|
+
├─ [name]_component.rb
|
|
199
|
+
└─ [name]_component/
|
|
200
|
+
├─ style.css
|
|
201
|
+
├─ view.html.erb
|
|
202
|
+
└─ controller.js # if stimulus is configured
|
|
203
|
+
test/components/
|
|
204
|
+
└─ [name]_component_test.rb
|
|
128
205
|
```
|
|
129
206
|
|
|
130
207
|
### Component properties
|
|
@@ -155,39 +232,6 @@ CommentComponent.call body: 'Foo bar', author: User.first
|
|
|
155
232
|
comment_component body: 'Foo bar', author: User.first
|
|
156
233
|
```
|
|
157
234
|
|
|
158
|
-
### Overriding prop getters and setters
|
|
159
|
-
|
|
160
|
-
Getters and setters for properties are
|
|
161
|
-
defined in a module which means that you can override them and call them with `super`.
|
|
162
|
-
|
|
163
|
-
```ruby
|
|
164
|
-
# app/components/priority_icon_component.rb
|
|
165
|
-
|
|
166
|
-
class PriorityIconComponent < ApplicationComponent
|
|
167
|
-
PriorityStruct = Struct.new :icon, :color
|
|
168
|
-
|
|
169
|
-
PRIORITY_MAP = {
|
|
170
|
-
low: PriorityStruct.new('fa-solid fa-chevrons-down', 'green'),
|
|
171
|
-
medium: PriorityStruct.new('fa-solid fa-chevron-up', 'yellow'),
|
|
172
|
-
high: PriorityStruct.new('fa-solid fa-chevrons-up', 'red')
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
prop :severity, default: -> { :low }
|
|
176
|
-
|
|
177
|
-
def severity=(val)
|
|
178
|
-
# super will call the original
|
|
179
|
-
# implementation of the setter
|
|
180
|
-
super(PRIORITY_MAP[val])
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
```html
|
|
186
|
-
<!-- app/components/priority_icon_component/view.html.erb -->
|
|
187
|
-
|
|
188
|
-
<i style="color: <%= severity&.color %>;" class="<%= severity&.icon %>"></i>
|
|
189
|
-
```
|
|
190
|
-
|
|
191
235
|
### Helper methods
|
|
192
236
|
|
|
193
237
|
Defining helper methods which are available
|
|
@@ -239,6 +283,39 @@ end
|
|
|
239
283
|
</div>
|
|
240
284
|
```
|
|
241
285
|
|
|
286
|
+
### Overriding prop getters and setters
|
|
287
|
+
|
|
288
|
+
Getters and setters for properties are
|
|
289
|
+
defined in a module which means that you can override them and call them with `super`.
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
# app/components/priority_icon_component.rb
|
|
293
|
+
|
|
294
|
+
class PriorityIconComponent < ApplicationComponent
|
|
295
|
+
PriorityStruct = Struct.new :icon, :color
|
|
296
|
+
|
|
297
|
+
PRIORITY_MAP = {
|
|
298
|
+
low: PriorityStruct.new('fa-solid fa-chevrons-down', 'green'),
|
|
299
|
+
medium: PriorityStruct.new('fa-solid fa-chevron-up', 'yellow'),
|
|
300
|
+
high: PriorityStruct.new('fa-solid fa-chevrons-up', 'red')
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
prop :severity, default: -> { :low }
|
|
304
|
+
|
|
305
|
+
def severity=(val)
|
|
306
|
+
# super will call the original
|
|
307
|
+
# implementation of the setter
|
|
308
|
+
super(PRIORITY_MAP[val])
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
```html
|
|
314
|
+
<!-- app/components/priority_icon_component/view.html.erb -->
|
|
315
|
+
|
|
316
|
+
<i style="color: <%= severity&.color %>;" class="<%= severity&.icon %>"></i>
|
|
317
|
+
```
|
|
318
|
+
|
|
242
319
|
### Nested components
|
|
243
320
|
|
|
244
321
|
It's possible to nest components or provide
|
|
@@ -283,12 +360,14 @@ a block.
|
|
|
283
360
|
<!-- app/controller/tasks/show.html.erb -->
|
|
284
361
|
|
|
285
362
|
<%= ModalComponent.call id: 'update-task-modal' title: 'Update the task' do %>
|
|
363
|
+
<!-- You can provide HTML and render other components -->
|
|
286
364
|
<h2>This is your task!</h2>
|
|
287
365
|
<%= form_with model: @task do |f| %>
|
|
288
366
|
<%= f.text_field :name %>
|
|
289
367
|
<%= f.text_area :description %>
|
|
290
368
|
<%= f.submit %>
|
|
291
369
|
<% end %>
|
|
370
|
+
<%= OtherComponent.call some: 'prop' %>
|
|
292
371
|
<% end %>
|
|
293
372
|
```
|
|
294
373
|
|
|
@@ -299,64 +378,131 @@ only when it is present (will work without nested content)
|
|
|
299
378
|
you can use `yield.html_safe if block_given?`
|
|
300
379
|
|
|
301
380
|
In general `block_given?` will return `true` when a block/nested content is present, otherwise `false`.
|
|
381
|
+
You can use it to render content conditionally based on
|
|
382
|
+
whether nested content is present.
|
|
302
383
|
|
|
303
|
-
###
|
|
384
|
+
### Rails helpers inside component templates
|
|
304
385
|
|
|
305
|
-
|
|
386
|
+
Component views/template files can make use
|
|
387
|
+
of all ActionView helpers and Rails route helpers.
|
|
306
388
|
|
|
307
|
-
|
|
308
|
-
# app/components/sign_up/button_component.rb
|
|
389
|
+
This makes component views very flexible and convenient.
|
|
309
390
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
end
|
|
313
|
-
```
|
|
391
|
+
```erb
|
|
392
|
+
<!-- app/components/login_form_component/view.html.erb -->
|
|
314
393
|
|
|
315
|
-
|
|
316
|
-
|
|
394
|
+
<%= form_with url: sign_up_path, class: "login_form_component" do |f| %>
|
|
395
|
+
<%= f.label :first_name %>
|
|
396
|
+
<%= f.text_field :first_name %>
|
|
317
397
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
</div>
|
|
321
|
-
```
|
|
398
|
+
<%= f.label :last_name %>
|
|
399
|
+
<%= f.text_field :last_name %>
|
|
322
400
|
|
|
323
|
-
|
|
324
|
-
|
|
401
|
+
<%= f.label :email, "Email Address" %>
|
|
402
|
+
<%= f.text_field :email %>
|
|
325
403
|
|
|
326
|
-
.
|
|
327
|
-
|
|
328
|
-
border-radius: 1rem;
|
|
329
|
-
transition-duration: 500ms;
|
|
404
|
+
<%= f.label :password %>
|
|
405
|
+
<%= f.password_field :password %>
|
|
330
406
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
407
|
+
<%= f.label :password_confirmation, "Confirm Password" %>
|
|
408
|
+
<%= f.password_field :password_confirmation %>
|
|
409
|
+
|
|
410
|
+
<%= f.submit "Create account" %>
|
|
411
|
+
<% end %>
|
|
335
412
|
```
|
|
336
413
|
|
|
337
|
-
|
|
338
|
-
|
|
414
|
+
### Testing Components
|
|
415
|
+
|
|
416
|
+
### Rails
|
|
417
|
+
|
|
418
|
+
After setting up this gem with the rails generator
|
|
419
|
+
`rails generate amber_component:install` a new abstract
|
|
420
|
+
test class will be available called `ApplicationComponentTestCase`.
|
|
421
|
+
|
|
422
|
+
It provides a handful of helper methods to make it
|
|
423
|
+
easier to inspect the rendered HTML.
|
|
424
|
+
|
|
425
|
+
A simple test file may look like this:
|
|
339
426
|
|
|
340
427
|
```ruby
|
|
341
|
-
|
|
342
|
-
|
|
428
|
+
# test/components/foo_component_test.rb
|
|
429
|
+
|
|
430
|
+
require 'test_helper'
|
|
431
|
+
|
|
432
|
+
class FooComponentTest < ApplicationComponentTestCase
|
|
433
|
+
test 'render correct HTML' do
|
|
434
|
+
# Specify what the assertions are supposed to
|
|
435
|
+
# check against.
|
|
436
|
+
#
|
|
437
|
+
# There can be multiple renders in one test
|
|
438
|
+
# but they override the previous one.
|
|
439
|
+
# So there is only one rendered component
|
|
440
|
+
# at any given time.
|
|
441
|
+
render do
|
|
442
|
+
FooComponent.call some: 'prop'
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Assertions on the rendered HTML
|
|
446
|
+
|
|
447
|
+
# Use a CSS selector
|
|
448
|
+
assert_selector ".foo_component span.my_class", text: 'Some Text'
|
|
449
|
+
# Check text
|
|
450
|
+
assert_text 'Amber Component is awesome!'
|
|
451
|
+
end
|
|
452
|
+
end
|
|
343
453
|
```
|
|
344
454
|
|
|
345
|
-
|
|
455
|
+
A full list of available assertions can be found [here](https://rubydoc.info/github/jnicklas/capybara/Capybara/Node/Matchers).
|
|
346
456
|
|
|
347
|
-
|
|
457
|
+
### Non Rails
|
|
348
458
|
|
|
349
|
-
|
|
350
|
-
|
|
459
|
+
There is a test case class for minitest. You can
|
|
460
|
+
access it by requiring `'amber_component/minitest_test_case'`.
|
|
461
|
+
|
|
462
|
+
It has the same assertion methods as the Rails test case class.
|
|
463
|
+
It requires [capybara](https://github.com/teamcapybara/capybara) to be installed and present in the Gemfile.
|
|
464
|
+
|
|
465
|
+
A full list of available assertions can be found [here](https://rubydoc.info/github/jnicklas/capybara/Capybara/Node/Matchers).
|
|
466
|
+
|
|
467
|
+
```ruby
|
|
468
|
+
require 'amber_component/minitest_test_case'
|
|
469
|
+
|
|
470
|
+
class FooComponentTest < AmberComponent::MinitestTestCase
|
|
471
|
+
def test_render_correct_html
|
|
472
|
+
# Specify what the assertions are supposed to
|
|
473
|
+
# check against.
|
|
474
|
+
#
|
|
475
|
+
# There can be multiple renders in one test
|
|
476
|
+
# but they override the previous one.
|
|
477
|
+
# So there is only one rendered component
|
|
478
|
+
# at any given time.
|
|
479
|
+
render do
|
|
480
|
+
FooComponent.call some: 'prop'
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Assertions on the rendered HTML
|
|
484
|
+
|
|
485
|
+
# Use a CSS selector
|
|
486
|
+
assert_selector ".foo_component span.my_class", text: 'Some Text'
|
|
487
|
+
# Check text
|
|
488
|
+
assert_text 'Amber Component is awesome!'
|
|
489
|
+
end
|
|
490
|
+
end
|
|
351
491
|
```
|
|
352
492
|
|
|
353
|
-
|
|
493
|
+
There is also a helper module which provides all of these assertions
|
|
494
|
+
under `'amber_component/test_helper'`.
|
|
354
495
|
|
|
355
|
-
```
|
|
356
|
-
|
|
496
|
+
```ruby
|
|
497
|
+
require 'amber_component/test_helper'
|
|
498
|
+
|
|
499
|
+
class MyAbstractTestCase < ::Minitest::Test
|
|
500
|
+
include ::AmberComponent::TestHelper
|
|
501
|
+
end
|
|
357
502
|
```
|
|
358
503
|
|
|
359
|
-
|
|
504
|
+
Note that this module has only been tested with minitest and rails test suites,
|
|
505
|
+
so it may require overriding or implementing a few methods to work with other test suites.
|
|
360
506
|
|
|
361
507
|
## Contribute
|
|
362
508
|
|
data/amber_component.gemspec
CHANGED
|
@@ -43,7 +43,4 @@ require_relative "lib/amber_component/version"
|
|
|
43
43
|
spec.add_dependency "activesupport", ">= 6"
|
|
44
44
|
spec.add_dependency "memery", ">= 1.4.1"
|
|
45
45
|
spec.add_dependency "tilt", ">= 2.0.10"
|
|
46
|
-
|
|
47
|
-
# For more information and examples about making a new gem, check out our
|
|
48
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
|
49
46
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const registeredControllers = {}
|
|
2
|
+
|
|
3
|
+
export function eagerLoadAmberComponentControllers(application) {
|
|
4
|
+
const paths = Object.keys(parseImportmapJson()).filter(path => path.match(new RegExp(`/controller$`)))
|
|
5
|
+
paths.forEach(path => registerControllerFromPath(path, application))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function parseImportmapJson() {
|
|
9
|
+
return JSON.parse(document.querySelector("script[type=importmap]").text).imports
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function registerControllerFromPath(path, application) {
|
|
13
|
+
const name = path
|
|
14
|
+
.replace("/controller", "")
|
|
15
|
+
.replace(/\//g, "--")
|
|
16
|
+
.replace(/_/g, "-")
|
|
17
|
+
|
|
18
|
+
if (!(name in registeredControllers)) {
|
|
19
|
+
import(path)
|
|
20
|
+
.then(module => registerController(name, module, application))
|
|
21
|
+
.catch(error => console.error(`Failed to register controller: ${name} (${path})`, error))
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function registerController(name, module, application) {
|
|
26
|
+
if (!(name in registeredControllers)) {
|
|
27
|
+
application.register(name, module.default)
|
|
28
|
+
registeredControllers[name] = true
|
|
29
|
+
}
|
|
30
|
+
}
|
data/banner.png
CHANGED
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ::AmberComponent
|
|
4
|
+
# Object which stores configuration options
|
|
5
|
+
# for this gem.
|
|
6
|
+
class Configuration
|
|
7
|
+
# @return [Array<Symbol>]
|
|
8
|
+
STIMULUS_INTEGRATIONS = %i[importmap jsbundling webpack esbuild rollup].freeze
|
|
9
|
+
|
|
10
|
+
# How Stimulus.js is bundled in this app.
|
|
11
|
+
# Possible values: `[nil, :importmap, :jsbundling, :webpack, :esbuild, :rollup]`
|
|
12
|
+
# `nil` indicates that stimulus should not be used (default behaviour).
|
|
13
|
+
#
|
|
14
|
+
# @return [Symbol, nil]
|
|
15
|
+
attr_reader :stimulus
|
|
16
|
+
|
|
17
|
+
# How Stimulus.js is bundled in this app.
|
|
18
|
+
# Possible values: `[nil, :importmap, :jsbundling, :webpack, :esbuild, :rollup]`
|
|
19
|
+
# `nil` indicates that stimulus should not be used (default behaviour).
|
|
20
|
+
#
|
|
21
|
+
# @param val [Symbol, String, nil]
|
|
22
|
+
def stimulus=(val)
|
|
23
|
+
val = val&.to_sym
|
|
24
|
+
unless val.nil? || STIMULUS_INTEGRATIONS.include?(val)
|
|
25
|
+
raise(::ArgumentError,
|
|
26
|
+
"Invalid value for `stimulus` bundling. " \
|
|
27
|
+
"Received #{val.inspect}, expected one of #{STIMULUS_INTEGRATIONS.inspect}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@stimulus = val
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def stimulus?
|
|
35
|
+
!@stimulus.nil?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
def stimulus_importmap?
|
|
40
|
+
@stimulus == :importmap
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -4,8 +4,10 @@ module ::AmberComponent
|
|
|
4
4
|
# Class which hooks into Rails
|
|
5
5
|
# and configures the application.
|
|
6
6
|
class Railtie < ::Rails::Railtie
|
|
7
|
-
initializer 'amber_component.
|
|
7
|
+
initializer 'amber_component.assets' do |app|
|
|
8
8
|
app.config.assets.paths << (app.root / 'app' / 'components')
|
|
9
|
+
app.config.assets.paths << (ROOT_GEM_PATH / 'assets' / 'javascripts')
|
|
10
|
+
app.config.assets.precompile += %w[amber_component/stimulus_loading.js]
|
|
9
11
|
|
|
10
12
|
next if ::Rails.env.production?
|
|
11
13
|
|
data/lib/amber_component.rb
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require 'active_support'
|
|
4
4
|
require 'active_support/core_ext'
|
|
5
|
+
require 'pathname'
|
|
5
6
|
|
|
7
|
+
require_relative 'amber_component/configuration'
|
|
8
|
+
|
|
9
|
+
# Root module of the `amber_component` gem.
|
|
6
10
|
module ::AmberComponent
|
|
7
11
|
class Error < ::StandardError; end
|
|
8
12
|
class MissingPropsError < Error; end
|
|
@@ -13,6 +17,22 @@ module ::AmberComponent
|
|
|
13
17
|
class EmptyViewError < Error; end
|
|
14
18
|
class UnknownViewTypeError < Error; end
|
|
15
19
|
class MultipleViewsError < Error; end
|
|
20
|
+
|
|
21
|
+
# @return [Pathname]
|
|
22
|
+
ROOT_GEM_PATH = ::Pathname.new ::File.expand_path('..', __dir__)
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# @return [Configuration]
|
|
26
|
+
def configuration
|
|
27
|
+
@configuration ||= Configuration.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @yieldparam [Configuration]
|
|
31
|
+
# @return [void]
|
|
32
|
+
def configure
|
|
33
|
+
yield configuration
|
|
34
|
+
end
|
|
35
|
+
end
|
|
16
36
|
end
|
|
17
37
|
|
|
18
38
|
require_relative 'amber_component/version'
|
|
@@ -10,8 +10,14 @@ module ::AmberComponent
|
|
|
10
10
|
desc 'Install the AmberComponent gem'
|
|
11
11
|
source_root ::File.expand_path('templates', __dir__)
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
|
|
13
|
+
# @return [Array<Symbol>]
|
|
14
|
+
STIMULUS_INTEGRATIONS = %i[stimulus importmap jsbundling webpack esbuild rollup].freeze
|
|
15
|
+
|
|
16
|
+
class_option :stimulus,
|
|
17
|
+
desc: "Configure the app to use Stimulus.js wih components to make them interactive " \
|
|
18
|
+
"[options: importmap (default), jsbundling, webpack, esbuild, rollup]"
|
|
19
|
+
|
|
20
|
+
def setup
|
|
15
21
|
copy_file 'application_component.rb', 'app/components/application_component.rb'
|
|
16
22
|
copy_file 'application_component_test_case.rb', 'test/application_component_test_case.rb'
|
|
17
23
|
append_file 'test/test_helper.rb', "require_relative 'application_component_test_case'"
|
|
@@ -23,10 +29,87 @@ module ::AmberComponent
|
|
|
23
29
|
require_components_css_in 'app/assets/stylesheets/application.css.sass'
|
|
24
30
|
require_components_css_in 'app/assets/stylesheets/application.scss.sass'
|
|
25
31
|
require_components_css_in 'app/assets/stylesheets/application.sass.scss'
|
|
32
|
+
configure_stimulus
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
private
|
|
29
36
|
|
|
37
|
+
def configure_stimulus
|
|
38
|
+
stimulus = options[:stimulus]&.to_sym
|
|
39
|
+
return unless stimulus
|
|
40
|
+
|
|
41
|
+
case stimulus
|
|
42
|
+
when :stimulus
|
|
43
|
+
if defined?(::Jsbundling)
|
|
44
|
+
stimulus_integration = :jsbundling
|
|
45
|
+
configure_stimulus_jsbundling
|
|
46
|
+
else
|
|
47
|
+
stimulus_integration = :importmap
|
|
48
|
+
configure_stimulus_importmap
|
|
49
|
+
end
|
|
50
|
+
when :importmap
|
|
51
|
+
stimulus_integration = :importmap
|
|
52
|
+
configure_stimulus_importmap
|
|
53
|
+
when :jsbundling, :webpack, :esbuild, :rollup
|
|
54
|
+
stimulus_integration = :jsbundling
|
|
55
|
+
configure_stimulus_jsbundling
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
create_file 'config/initializers/amber_component.rb', <<~RUBY
|
|
59
|
+
# frozen_string_literal: true
|
|
60
|
+
|
|
61
|
+
::AmberComponent.configure do |c|
|
|
62
|
+
c.stimulus = :#{stimulus_integration}
|
|
63
|
+
end
|
|
64
|
+
RUBY
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def configure_stimulus_importmap
|
|
68
|
+
install_importmap
|
|
69
|
+
install_stimulus
|
|
70
|
+
append_file 'config/importmap.rb', <<~RUBY
|
|
71
|
+
pin "@amber_component/stimulus_loading", to: "amber_component/stimulus_loading.js", preload: true
|
|
72
|
+
pin_all_from "app/components"
|
|
73
|
+
RUBY
|
|
74
|
+
append_file 'app/javascript/controllers/index.js', <<~JS
|
|
75
|
+
import { eagerLoadAmberComponentControllers } from "@amber_component/stimulus_loading"
|
|
76
|
+
eagerLoadAmberComponentControllers(application)
|
|
77
|
+
JS
|
|
78
|
+
append_file 'app/assets/config/manifest.js', %(//= link_tree ../../components .js\n)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def configure_stimulus_jsbundling
|
|
82
|
+
install_stimulus
|
|
83
|
+
append_file 'app/javascript/application.js', %(import "./controllers/components"\n)
|
|
84
|
+
create_file 'app/javascript/controllers/components.js', <<~JS
|
|
85
|
+
// This file has been created by `amber_component` and will
|
|
86
|
+
// register all stimulus controllers from your components
|
|
87
|
+
import { application } from "./application"
|
|
88
|
+
JS
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [void]
|
|
92
|
+
def install_importmap
|
|
93
|
+
return if ::File.exist?('config/importmap.rb') && defined?(::Importmap)
|
|
94
|
+
|
|
95
|
+
unless defined?(::Importmap)
|
|
96
|
+
system 'gem install importmap-rails'
|
|
97
|
+
gem 'importmap-rails'
|
|
98
|
+
system 'bundle install'
|
|
99
|
+
end
|
|
100
|
+
rake 'importmap:install'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [void]
|
|
104
|
+
def install_stimulus
|
|
105
|
+
return if defined?(::Stimulus)
|
|
106
|
+
|
|
107
|
+
system 'gem install stimulus-rails'
|
|
108
|
+
gem 'stimulus-rails'
|
|
109
|
+
system 'bundle install'
|
|
110
|
+
rake 'stimulus:install'
|
|
111
|
+
end
|
|
112
|
+
|
|
30
113
|
# @param file_name [String]
|
|
31
114
|
# @return [void]
|
|
32
115
|
def require_components_css_in(file_name)
|
|
@@ -40,8 +40,10 @@ class AmberComponentGenerator < ::Rails::Generators::NamedBase
|
|
|
40
40
|
template 'component_test.rb.erb', "test/components/#{file_path}_test.rb"
|
|
41
41
|
create_stylesheet
|
|
42
42
|
create_view
|
|
43
|
+
create_stimulus_controller
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
# @return [String]
|
|
45
47
|
def file_name
|
|
46
48
|
name = super
|
|
47
49
|
return name if name.end_with? '_component'
|
|
@@ -51,6 +53,29 @@ class AmberComponentGenerator < ::Rails::Generators::NamedBase
|
|
|
51
53
|
|
|
52
54
|
private
|
|
53
55
|
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def stimulus?
|
|
58
|
+
::AmberComponent.configuration.stimulus?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def stimulus_importmap?
|
|
63
|
+
::AmberComponent.configuration.stimulus_importmap?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [void]
|
|
67
|
+
def create_stimulus_controller
|
|
68
|
+
return unless stimulus?
|
|
69
|
+
|
|
70
|
+
template 'controller.js.erb', "app/components/#{file_path}/controller.js"
|
|
71
|
+
return if stimulus_importmap?
|
|
72
|
+
|
|
73
|
+
append_file 'app/javascript/controllers/components.js', <<~JS
|
|
74
|
+
import #{stimulus_controller_class_name} from "../../components/#{file_path}/controller"
|
|
75
|
+
application.register("#{stimulus_controller_id}", #{stimulus_controller_class_name})
|
|
76
|
+
JS
|
|
77
|
+
end
|
|
78
|
+
|
|
54
79
|
# @return [void]
|
|
55
80
|
def create_view
|
|
56
81
|
case @view_format
|
|
@@ -73,4 +98,14 @@ class AmberComponentGenerator < ::Rails::Generators::NamedBase
|
|
|
73
98
|
template 'style.css.erb', "app/components/#{file_path}/style.css"
|
|
74
99
|
end
|
|
75
100
|
end
|
|
101
|
+
|
|
102
|
+
# @return [String]
|
|
103
|
+
def stimulus_controller_id
|
|
104
|
+
file_path.gsub('_', '-').gsub('/', '--')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @return [String]
|
|
108
|
+
def stimulus_controller_class_name
|
|
109
|
+
file_path.gsub('/', '_').camelize
|
|
110
|
+
end
|
|
76
111
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Read more about Stimulus here https://stimulus.hotwired.dev/
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
connect() {
|
|
6
|
+
console.log("Stimulus controller '<%= stimulus_controller_id %>' is connected!")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
greet() {
|
|
10
|
+
alert("Hi there!")
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
.<%= singular_table_name %>
|
|
1
|
+
.<%= singular_table_name %><%= %({ data: { controller: "#{stimulus_controller_id}" } }) if stimulus? %>
|
|
2
2
|
%h1
|
|
3
3
|
Hello from
|
|
4
4
|
%b
|
|
5
5
|
<%= class_name %>
|
|
6
6
|
, initialized at:
|
|
7
7
|
= @time
|
|
8
|
+
<%- if stimulus? %>
|
|
9
|
+
%button{ data: { action: "click-><%= stimulus_controller_id %>#greet" }
|
|
10
|
+
Greet me
|
|
11
|
+
<% end %>
|
|
8
12
|
%p
|
|
9
13
|
= description
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
<div class=
|
|
1
|
+
<div class="<%= singular_table_name %>"<%= %( data-controller="#{stimulus_controller_id}") if stimulus? %>>
|
|
2
2
|
<h1>
|
|
3
3
|
Hello from <b><%= class_name %></b>, initialized at: <%%= @time %>
|
|
4
4
|
</h1>
|
|
5
|
+
<%- if stimulus? %>
|
|
6
|
+
<button data-action="click-><%= stimulus_controller_id %>#greet">Greet me</button>
|
|
7
|
+
<% end %>
|
|
5
8
|
<p>
|
|
6
9
|
<%%= description %>
|
|
7
10
|
</p>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
div.<%= singular_table_name %>
|
|
1
|
+
div.<%= singular_table_name %><%= %( data-controller="#{stimulus_controller_id}") if stimulus? %>
|
|
2
2
|
h1
|
|
3
3
|
| Hello from
|
|
4
4
|
b <%= class_name %>
|
|
5
5
|
| , initialized at: #{@time}
|
|
6
|
+
<%- if stimulus? %>
|
|
7
|
+
button data-action="click-><%= stimulus_controller_id %>#greet"
|
|
8
|
+
| Greet me
|
|
9
|
+
<% end %>
|
|
6
10
|
p = description
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: amber_component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ruby-Amber
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: exe
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2022-11-
|
|
13
|
+
date: 2022-11-13 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: actionview
|
|
@@ -106,11 +106,13 @@ files:
|
|
|
106
106
|
- README.md
|
|
107
107
|
- Rakefile
|
|
108
108
|
- amber_component.gemspec
|
|
109
|
+
- assets/javascripts/amber_component/stimulus_loading.js
|
|
109
110
|
- banner.png
|
|
110
111
|
- icon.png
|
|
111
112
|
- lib/amber_component.rb
|
|
112
113
|
- lib/amber_component/assets.rb
|
|
113
114
|
- lib/amber_component/base.rb
|
|
115
|
+
- lib/amber_component/configuration.rb
|
|
114
116
|
- lib/amber_component/helpers.rb
|
|
115
117
|
- lib/amber_component/helpers/class_helper.rb
|
|
116
118
|
- lib/amber_component/helpers/component_helper.rb
|
|
@@ -133,6 +135,7 @@ files:
|
|
|
133
135
|
- lib/generators/component_generator.rb
|
|
134
136
|
- lib/generators/templates/component.rb.erb
|
|
135
137
|
- lib/generators/templates/component_test.rb.erb
|
|
138
|
+
- lib/generators/templates/controller.js.erb
|
|
136
139
|
- lib/generators/templates/style.css.erb
|
|
137
140
|
- lib/generators/templates/style.sass.erb
|
|
138
141
|
- lib/generators/templates/style.scss.erb
|