amber_component 1.0.0 → 1.1.1
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 +1 -1
- data/README.md +177 -99
- data/amber_component.gemspec +0 -3
- data/assets/javascripts/amber_component/stimulus_loading.js +30 -0
- data/lib/amber_component/configuration.rb +85 -0
- data/lib/amber_component/railtie.rb +3 -1
- data/lib/amber_component/version.rb +1 -1
- data/lib/amber_component/views.rb +1 -1
- data/lib/amber_component.rb +21 -0
- data/lib/generators/amber_component/install_generator.rb +171 -2
- data/lib/generators/amber_component_generator.rb +57 -20
- 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: 46dc65dbef14224da8e0ce373eb07528188c399444eb2b90968c1cf3dc049864
|
4
|
+
data.tar.gz: d547837963b2e6148a0724d961f3ba9fa1f540365a711d40d483a24ccdcf8909
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f060bd9c1a97475c0678ddc5c0df166bf6f05001cde7f43a41bdc130c068b2ea24c15518d384016aeef2e8ef592b2928c305a41aaaa24dcebf038783a96b57b
|
7
|
+
data.tar.gz: 8f2f8029094be9ac3eccbb55b1c46a3c1ed43bb5e7d4c55263bf9bf0a2501862589f7644b8c357ceaa1d8762ea2370125f29c85bce7299925588570ba5118860
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,13 +3,13 @@
|
|
3
3
|
[](https://codeclimate.com/github/amber-ruby/amber_component/maintainability)
|
4
4
|
[](https://github.com/amber-ruby/amber_component/actions/workflows/ci_ruby.yml)
|
5
5
|
[](https://github.com/amber-ruby/amber_component/actions/workflows/ci_ruby.yml)
|
6
|
-
[]((https://rubygems.org/gems/amber_component))
|
6
|
+
<!-- []((https://rubygems.org/gems/amber_component)) -->
|
7
7
|
|
8
8
|
<img src="banner.png" width="500px" style="margin-bottom: 2rem;"/>
|
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,44 @@ 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
|
+
`amber_component` automatically detects what kind of view and stylesheet formats your app is configured to use.
|
58
|
+
|
59
|
+
So if you've got `haml-rails`, components will be generated with `haml`. When your app uses `slim-rails`, components will be generated with `slim`. When your `Gemfile` contains `sassc-rails`, components will use `scss` etc.
|
60
|
+
|
61
|
+
All of these formats can be overridden in
|
62
|
+
an initializer or by adding arguments to the component generator.
|
63
|
+
|
64
|
+
```
|
65
|
+
app/components/
|
66
|
+
├─ [name]_component.rb
|
67
|
+
└─ [name]_component/
|
68
|
+
├─ style.css # may be .sass or .scss
|
69
|
+
├─ view.html.erb
|
70
|
+
└─ controller.js # if stimulus is configured
|
71
|
+
test/components/
|
72
|
+
└─ [name]_component_test.rb
|
73
|
+
```
|
46
74
|
|
47
75
|
An individual component which implements a button may look like this.
|
48
76
|
|
@@ -57,7 +85,9 @@ end
|
|
57
85
|
```html
|
58
86
|
<!-- app/components/button_component/view.html.erb -->
|
59
87
|
|
60
|
-
<div class="button_component"
|
88
|
+
<div class="button_component"
|
89
|
+
data-controller="button-component"
|
90
|
+
data-action="click->button-component#greet">
|
61
91
|
<%= label %>
|
62
92
|
</div>
|
63
93
|
```
|
@@ -76,6 +106,25 @@ end
|
|
76
106
|
}
|
77
107
|
```
|
78
108
|
|
109
|
+
If you used the `--stimulus` option when installing the gem, a JS controller will be generated as well.
|
110
|
+
```js
|
111
|
+
// app/components/button_component/controller.js
|
112
|
+
|
113
|
+
import { Controller } from "@hotwired/stimulus"
|
114
|
+
|
115
|
+
// Read more about Stimulus here https://stimulus.hotwired.dev/
|
116
|
+
export default class extends Controller {
|
117
|
+
connect() {
|
118
|
+
console.log("Stimulus controller 'button-component' is connected!")
|
119
|
+
}
|
120
|
+
|
121
|
+
greet() {
|
122
|
+
alert("Hi there!")
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
```
|
127
|
+
|
79
128
|
You can render this component in other components or in a Rails view.
|
80
129
|
|
81
130
|
```html
|
@@ -97,34 +146,76 @@ ButtonComponent.call label: 'Click me!'
|
|
97
146
|
#=> '<div class="button_component">Click me!</div>'
|
98
147
|
```
|
99
148
|
|
100
|
-
###
|
149
|
+
### Components with namespaces
|
101
150
|
|
102
|
-
|
103
|
-
of all ActionView helpers and Rails route helpers.
|
151
|
+
Components may be defined inside multiple modules/namespaces.
|
104
152
|
|
105
|
-
|
153
|
+
```ruby
|
154
|
+
# app/components/sign_up/button_component.rb
|
106
155
|
|
107
|
-
|
108
|
-
|
156
|
+
class SignUp::ButtonComponent < AmberComponent::Base
|
157
|
+
prop :label, required: true
|
158
|
+
end
|
159
|
+
```
|
109
160
|
|
110
|
-
|
111
|
-
|
112
|
-
<%= f.text_field :first_name %>
|
161
|
+
```html
|
162
|
+
<!-- app/components/sign_up/button_component/view.html.erb -->
|
113
163
|
|
114
|
-
|
115
|
-
|
164
|
+
<div class="sign_up_button_component">
|
165
|
+
<%= label %>
|
166
|
+
</div>
|
167
|
+
```
|
116
168
|
|
117
|
-
|
118
|
-
|
169
|
+
```scss
|
170
|
+
// app/components/sign_up/button_component/style.scss
|
119
171
|
|
120
|
-
|
121
|
-
|
172
|
+
.sign_up_button_component {
|
173
|
+
background-color: indigo;
|
174
|
+
border-radius: 1rem;
|
175
|
+
transition-duration: 500ms;
|
122
176
|
|
123
|
-
|
124
|
-
|
177
|
+
&:hover {
|
178
|
+
background-color: blue;
|
179
|
+
}
|
180
|
+
}
|
181
|
+
```
|
182
|
+
|
183
|
+
You can render such a component by calling the `::call` method
|
184
|
+
on its class, or by using the helper method defined on its parent module.
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
SignUp::ButtonComponent.call label: 'Sign up!'
|
188
|
+
SignUp.button_component label: 'Sign up!'
|
189
|
+
```
|
190
|
+
|
191
|
+
### Generating Components
|
192
|
+
|
193
|
+
You can generate new components by running
|
194
|
+
|
195
|
+
```sh
|
196
|
+
$ bin/rails generate component [name]
|
197
|
+
```
|
198
|
+
|
199
|
+
Name of the component may be PascalCased like `FooBar` or snake_cased `foo_bar`
|
200
|
+
|
201
|
+
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).
|
125
202
|
|
126
|
-
|
127
|
-
|
203
|
+
```
|
204
|
+
app/components/
|
205
|
+
├─ [name]_component.rb
|
206
|
+
└─ [name]_component/
|
207
|
+
├─ style.css # may be `.scss` or `.sass`
|
208
|
+
├─ view.html.erb # may be `.haml` or `.slim`
|
209
|
+
└─ controller.js # if stimulus is configured
|
210
|
+
test/components/
|
211
|
+
└─ [name]_component_test.rb
|
212
|
+
```
|
213
|
+
|
214
|
+
View and stylesheet formats can be overridden by providing options.
|
215
|
+
|
216
|
+
```
|
217
|
+
-v, [--view=VIEW] # Indicate what type of view should be generated eg. [:erb, :haml, :slim]
|
218
|
+
--styles, -c, [--css=CSS] # Indicate what type of styles should be generated eg. [:css, :scss, :sass]
|
128
219
|
```
|
129
220
|
|
130
221
|
### Component properties
|
@@ -155,39 +246,6 @@ CommentComponent.call body: 'Foo bar', author: User.first
|
|
155
246
|
comment_component body: 'Foo bar', author: User.first
|
156
247
|
```
|
157
248
|
|
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
249
|
### Helper methods
|
192
250
|
|
193
251
|
Defining helper methods which are available
|
@@ -239,6 +297,39 @@ end
|
|
239
297
|
</div>
|
240
298
|
```
|
241
299
|
|
300
|
+
### Overriding prop getters and setters
|
301
|
+
|
302
|
+
Getters and setters for properties are
|
303
|
+
defined in a module which means that you can override them and call them with `super`.
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
# app/components/priority_icon_component.rb
|
307
|
+
|
308
|
+
class PriorityIconComponent < ApplicationComponent
|
309
|
+
PriorityStruct = Struct.new :icon, :color
|
310
|
+
|
311
|
+
PRIORITY_MAP = {
|
312
|
+
low: PriorityStruct.new('fa-solid fa-chevrons-down', 'green'),
|
313
|
+
medium: PriorityStruct.new('fa-solid fa-chevron-up', 'yellow'),
|
314
|
+
high: PriorityStruct.new('fa-solid fa-chevrons-up', 'red')
|
315
|
+
}
|
316
|
+
|
317
|
+
prop :severity, default: -> { :low }
|
318
|
+
|
319
|
+
def severity=(val)
|
320
|
+
# super will call the original
|
321
|
+
# implementation of the setter
|
322
|
+
super(PRIORITY_MAP[val])
|
323
|
+
end
|
324
|
+
end
|
325
|
+
```
|
326
|
+
|
327
|
+
```html
|
328
|
+
<!-- app/components/priority_icon_component/view.html.erb -->
|
329
|
+
|
330
|
+
<i style="color: <%= severity&.color %>;" class="<%= severity&.icon %>"></i>
|
331
|
+
```
|
332
|
+
|
242
333
|
### Nested components
|
243
334
|
|
244
335
|
It's possible to nest components or provide
|
@@ -304,64 +395,51 @@ In general `block_given?` will return `true` when a block/nested content is pres
|
|
304
395
|
You can use it to render content conditionally based on
|
305
396
|
whether nested content is present.
|
306
397
|
|
307
|
-
###
|
308
|
-
|
309
|
-
Components may be defined inside multiple modules/namespaces.
|
398
|
+
### Rails helpers inside component templates
|
310
399
|
|
311
|
-
|
312
|
-
|
400
|
+
Component views/template files can make use
|
401
|
+
of all ActionView helpers and Rails route helpers.
|
313
402
|
|
314
|
-
|
315
|
-
prop :label, required: true
|
316
|
-
end
|
317
|
-
```
|
403
|
+
This makes component views very flexible and convenient.
|
318
404
|
|
319
|
-
```
|
320
|
-
<!-- app/components/
|
405
|
+
```erb
|
406
|
+
<!-- app/components/login_form_component/view.html.erb -->
|
321
407
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
```
|
408
|
+
<%= form_with url: sign_up_path, class: "login_form_component" do |f| %>
|
409
|
+
<%= f.label :first_name %>
|
410
|
+
<%= f.text_field :first_name %>
|
326
411
|
|
327
|
-
|
328
|
-
|
412
|
+
<%= f.label :last_name %>
|
413
|
+
<%= f.text_field :last_name %>
|
329
414
|
|
330
|
-
.
|
331
|
-
|
332
|
-
border-radius: 1rem;
|
333
|
-
transition-duration: 500ms;
|
415
|
+
<%= f.label :email, "Email Address" %>
|
416
|
+
<%= f.text_field :email %>
|
334
417
|
|
335
|
-
|
336
|
-
|
337
|
-
}
|
338
|
-
}
|
339
|
-
```
|
418
|
+
<%= f.label :password %>
|
419
|
+
<%= f.password_field :password %>
|
340
420
|
|
341
|
-
|
342
|
-
|
421
|
+
<%= f.label :password_confirmation, "Confirm Password" %>
|
422
|
+
<%= f.password_field :password_confirmation %>
|
343
423
|
|
344
|
-
|
345
|
-
|
346
|
-
SignUp.button_component label: 'Sign up!'
|
424
|
+
<%= f.submit "Create account" %>
|
425
|
+
<% end %>
|
347
426
|
```
|
348
427
|
|
349
|
-
###
|
350
|
-
|
351
|
-
You an generate new components by running
|
428
|
+
### Configuration
|
352
429
|
|
353
|
-
|
354
|
-
|
355
|
-
```
|
430
|
+
This gem can be configured in an initializer.
|
431
|
+
If you used the installer generator it should already be present.
|
356
432
|
|
357
|
-
|
433
|
+
```ruby
|
434
|
+
# config/initializers/amber_component.rb
|
358
435
|
|
359
|
-
|
360
|
-
|
436
|
+
::AmberComponent.configure do |c|
|
437
|
+
c.stimulus = nil # [nil, :importmap, :webpacker, :jsbundling, :webpack, :esbuild, :rollup]
|
438
|
+
c.stylesheet_format = :css # [:css, :scss, :sass]
|
439
|
+
c.view_format = :erb # [:erb, :haml, :slim]
|
440
|
+
end
|
361
441
|
```
|
362
442
|
|
363
|
-
This will generate a new component in `app/components/foo_bar_component.rb` along with a view, stylesheet and test file.
|
364
|
-
|
365
443
|
### Testing Components
|
366
444
|
|
367
445
|
### Rails
|
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
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ::AmberComponent
|
4
|
+
# Object which stores configuration options
|
5
|
+
# for this gem.
|
6
|
+
class Configuration
|
7
|
+
# @return [Set<Symbol>]
|
8
|
+
STIMULUS_INTEGRATIONS = ::Set[nil, :importmap, :webpacker, :jsbundling, :webpack, :esbuild, :rollup]
|
9
|
+
# @return [Set<Symbol>]
|
10
|
+
ALLOWED_STYLES = ::Set.new(%i[css scss sass])
|
11
|
+
# @return [Set<Symbol>]
|
12
|
+
ALLOWED_VIEWS = ::Set.new(%i[erb haml slim])
|
13
|
+
|
14
|
+
# How Stimulus.js is bundled in this app.
|
15
|
+
# Possible values: `[nil, :importmap, :webpacker, :jsbundling, :webpack, :esbuild, :rollup]`
|
16
|
+
# `nil` indicates that stimulus should not be used (default behaviour).
|
17
|
+
#
|
18
|
+
# @return [Symbol, nil]
|
19
|
+
attr_reader :stimulus
|
20
|
+
|
21
|
+
# The default format that the generators will use
|
22
|
+
# for the view/template file of a component.
|
23
|
+
# Possible values: `[nil, :erb, :haml, :slim]`
|
24
|
+
#
|
25
|
+
# @return [Symbol, nil]
|
26
|
+
attr_reader :view_format
|
27
|
+
|
28
|
+
# The default format that the generators will use
|
29
|
+
# for the stylesheets of a component.
|
30
|
+
# Possible values: `[nil, :css, :scss, :sass]`
|
31
|
+
#
|
32
|
+
# @return [Symbol, nil]
|
33
|
+
attr_reader :stylesheet_format
|
34
|
+
|
35
|
+
# How Stimulus.js is bundled in this app.
|
36
|
+
# Possible values: `[nil, :importmap, :webpacker, :jsbundling, :webpack, :esbuild, :rollup]`
|
37
|
+
# `nil` indicates that stimulus should not be used (default behaviour).
|
38
|
+
#
|
39
|
+
# @param val [Symbol, String, nil]
|
40
|
+
def stimulus=(val)
|
41
|
+
val = val&.to_sym
|
42
|
+
unless val.nil? || STIMULUS_INTEGRATIONS.include?(val)
|
43
|
+
raise(::ArgumentError,
|
44
|
+
"Invalid value for `#{__method__}` bundling. " \
|
45
|
+
"Received #{val.inspect}, expected one of #{STIMULUS_INTEGRATIONS.inspect}")
|
46
|
+
end
|
47
|
+
|
48
|
+
@stimulus = val
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param val [Symbol, String, nil]
|
52
|
+
def stylesheet_format=(val)
|
53
|
+
val = val&.to_sym
|
54
|
+
unless val.nil? || ALLOWED_STYLES.include?(val)
|
55
|
+
raise(::ArgumentError,
|
56
|
+
"Invalid value for `#{__method__}`. " \
|
57
|
+
"Received #{val.inspect}, expected one of #{ALLOWED_STYLES.inspect}")
|
58
|
+
end
|
59
|
+
|
60
|
+
@stylesheet_format = val
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param val [Symbol, String, nil]
|
64
|
+
def view_format=(val)
|
65
|
+
val = val&.to_sym
|
66
|
+
unless val.nil? || ALLOWED_VIEWS.include?(val)
|
67
|
+
raise(::ArgumentError,
|
68
|
+
"Invalid value for `#{__method__}`. " \
|
69
|
+
"Received #{val.inspect}, expected one of #{ALLOWED_VIEWS.inspect}")
|
70
|
+
end
|
71
|
+
|
72
|
+
@view_format = val
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Boolean]
|
76
|
+
def stimulus?
|
77
|
+
!@stimulus.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Boolean]
|
81
|
+
def stimulus_importmap?
|
82
|
+
@stimulus == :importmap
|
83
|
+
end
|
84
|
+
end
|
85
|
+
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
|
|
@@ -8,7 +8,7 @@ module ::AmberComponent
|
|
8
8
|
# @return [Set<Symbol>]
|
9
9
|
VIEW_TYPES_WITH_RUBY = ::Set[:erb, :haml, :slim].freeze
|
10
10
|
# @return [Set<Symbol>]
|
11
|
-
ALLOWED_VIEW_TYPES = ::Set[:erb, :haml, :slim, :html
|
11
|
+
ALLOWED_VIEW_TYPES = ::Set[:erb, :haml, :slim, :html].freeze
|
12
12
|
# @return [Regexp]
|
13
13
|
VIEW_FILE_REGEXP = /^view\./.freeze
|
14
14
|
|
data/lib/amber_component.rb
CHANGED
@@ -2,7 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'active_support'
|
4
4
|
require 'active_support/core_ext'
|
5
|
+
require 'pathname'
|
6
|
+
require 'set'
|
5
7
|
|
8
|
+
require_relative 'amber_component/configuration'
|
9
|
+
|
10
|
+
# Root module of the `amber_component` gem.
|
6
11
|
module ::AmberComponent
|
7
12
|
class Error < ::StandardError; end
|
8
13
|
class MissingPropsError < Error; end
|
@@ -13,6 +18,22 @@ module ::AmberComponent
|
|
13
18
|
class EmptyViewError < Error; end
|
14
19
|
class UnknownViewTypeError < Error; end
|
15
20
|
class MultipleViewsError < Error; end
|
21
|
+
|
22
|
+
# @return [Pathname]
|
23
|
+
ROOT_GEM_PATH = ::Pathname.new ::File.expand_path('..', __dir__)
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# @return [Configuration]
|
27
|
+
def configuration
|
28
|
+
@configuration ||= Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# @yieldparam [Configuration]
|
32
|
+
# @return [void]
|
33
|
+
def configure
|
34
|
+
yield configuration
|
35
|
+
end
|
36
|
+
end
|
16
37
|
end
|
17
38
|
|
18
39
|
require_relative 'amber_component/version'
|
@@ -10,8 +10,22 @@ module ::AmberComponent
|
|
10
10
|
desc 'Install the AmberComponent gem'
|
11
11
|
source_root ::File.expand_path('templates', __dir__)
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
class_option :stimulus,
|
14
|
+
desc: "Configure the app to use Stimulus.js wih components to make them interactive " \
|
15
|
+
"[options: importmap (default), webpacker (legacy), jsbundling, webpack, esbuild, rollup]"
|
16
|
+
|
17
|
+
class_option :styles,
|
18
|
+
desc: "Configure the app to generate components with a particular stylesheet format " \
|
19
|
+
"[options: css (default), scss, sass]"
|
20
|
+
|
21
|
+
class_option :views,
|
22
|
+
desc: "Configure the app to generate components with a particular view format " \
|
23
|
+
"[options: erb (default), haml, slim]"
|
24
|
+
|
25
|
+
def setup
|
26
|
+
detect_stimulus
|
27
|
+
detect_styles
|
28
|
+
detect_views
|
15
29
|
copy_file 'application_component.rb', 'app/components/application_component.rb'
|
16
30
|
copy_file 'application_component_test_case.rb', 'test/application_component_test_case.rb'
|
17
31
|
append_file 'test/test_helper.rb', "require_relative 'application_component_test_case'"
|
@@ -23,10 +37,165 @@ module ::AmberComponent
|
|
23
37
|
require_components_css_in 'app/assets/stylesheets/application.css.sass'
|
24
38
|
require_components_css_in 'app/assets/stylesheets/application.scss.sass'
|
25
39
|
require_components_css_in 'app/assets/stylesheets/application.sass.scss'
|
40
|
+
configure_stimulus
|
41
|
+
create_initializer
|
26
42
|
end
|
27
43
|
|
28
44
|
private
|
29
45
|
|
46
|
+
def detect_styles
|
47
|
+
styles_option = options[:styles]&.to_sym
|
48
|
+
if !styles_option.nil? && !Configuration::ALLOWED_STYLES.include?(styles_option)
|
49
|
+
raise ::ArgumentError, "no such `stylesheet_format` as #{styles_option.inspect}"
|
50
|
+
end
|
51
|
+
|
52
|
+
@styles =
|
53
|
+
if styles_option
|
54
|
+
styles_option
|
55
|
+
elsif defined?(::SassC)
|
56
|
+
:scss
|
57
|
+
else
|
58
|
+
:css
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def detect_views
|
63
|
+
views_option = options[:views]&.to_sym
|
64
|
+
if !views_option.nil? && !Configuration::ALLOWED_VIEWS.include?(views_option)
|
65
|
+
raise ::ArgumentError, "no such `view_format` as #{views_option.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
@views =
|
69
|
+
if views_option
|
70
|
+
views_option
|
71
|
+
elsif defined?(::Haml)
|
72
|
+
:haml
|
73
|
+
elsif defined?(::Slim)
|
74
|
+
:slim
|
75
|
+
else
|
76
|
+
:erb
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def detect_stimulus
|
81
|
+
stimulus_option = options[:stimulus]&.to_sym
|
82
|
+
return unless stimulus_option
|
83
|
+
|
84
|
+
case stimulus_option
|
85
|
+
when :stimulus
|
86
|
+
if defined?(::Jsbundling)
|
87
|
+
stimulus_jsbundling!
|
88
|
+
elsif defined?(::Webpacker)
|
89
|
+
stimulus_webpacker!
|
90
|
+
else
|
91
|
+
stimulus_importmap!
|
92
|
+
end
|
93
|
+
when :importmap
|
94
|
+
stimulus_importmap!
|
95
|
+
when :jsbundling, :webpack, :esbuild, :rollup
|
96
|
+
stimulus_jsbundling!
|
97
|
+
when :webpacker
|
98
|
+
stimulus_webpacker!
|
99
|
+
else
|
100
|
+
raise ::ArgumentError,
|
101
|
+
"no such stimulus integration as `#{options[:stimulus].inspect}`"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def assert_styles
|
106
|
+
return if options[:styles].nil?
|
107
|
+
return if options[:styles].nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
def configure_stimulus
|
111
|
+
case @stimulus
|
112
|
+
when :importmap then configure_stimulus_importmap
|
113
|
+
when :jsbundling then configure_stimulus_jsbundling
|
114
|
+
when :webpacker then configure_stimulus_webpacker
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def create_initializer
|
119
|
+
create_file 'config/initializers/amber_component.rb', <<~RUBY
|
120
|
+
# frozen_string_literal: true
|
121
|
+
|
122
|
+
::AmberComponent.configure do |c|
|
123
|
+
c.stimulus = #{@stimulus.inspect} # #{Configuration::STIMULUS_INTEGRATIONS.to_a}
|
124
|
+
c.stylesheet_format = #{@styles.inspect} # #{Configuration::ALLOWED_STYLES.to_a}
|
125
|
+
c.view_format = #{@views.inspect} # #{Configuration::ALLOWED_VIEWS.to_a}
|
126
|
+
end
|
127
|
+
RUBY
|
128
|
+
end
|
129
|
+
|
130
|
+
def stimulus_jsbundling!
|
131
|
+
@stimulus = :jsbundling
|
132
|
+
end
|
133
|
+
|
134
|
+
def stimulus_importmap!
|
135
|
+
@stimulus = :importmap
|
136
|
+
end
|
137
|
+
|
138
|
+
def stimulus_webpacker!
|
139
|
+
@stimulus = :webpacker
|
140
|
+
end
|
141
|
+
|
142
|
+
def configure_stimulus_importmap
|
143
|
+
install_importmap
|
144
|
+
install_stimulus
|
145
|
+
append_file 'config/importmap.rb', <<~RUBY
|
146
|
+
pin "@amber_component/stimulus_loading", to: "amber_component/stimulus_loading.js", preload: true
|
147
|
+
pin_all_from "app/components"
|
148
|
+
RUBY
|
149
|
+
append_file 'app/javascript/controllers/index.js', <<~JS
|
150
|
+
import { eagerLoadAmberComponentControllers } from "@amber_component/stimulus_loading"
|
151
|
+
eagerLoadAmberComponentControllers(application)
|
152
|
+
JS
|
153
|
+
append_file 'app/assets/config/manifest.js', %(//= link_tree ../../components .js\n)
|
154
|
+
end
|
155
|
+
|
156
|
+
def configure_stimulus_jsbundling
|
157
|
+
install_stimulus
|
158
|
+
append_file 'app/javascript/application.js', %(import "./controllers/components"\n)
|
159
|
+
create_file 'app/javascript/controllers/components.js', <<~JS
|
160
|
+
// This file has been created by `amber_component` and will
|
161
|
+
// register all stimulus controllers from your components
|
162
|
+
import { application } from "./application"
|
163
|
+
JS
|
164
|
+
end
|
165
|
+
|
166
|
+
def configure_stimulus_webpacker
|
167
|
+
install_stimulus
|
168
|
+
append_file 'app/javascript/packs/application.js', %(import "controllers"\n)
|
169
|
+
append_file 'app/javascript/controllers/index.js', %(import "./components"\n)
|
170
|
+
create_file 'app/javascript/controllers/components.js', <<~JS
|
171
|
+
// This file has been created by `amber_component` and will
|
172
|
+
// register all stimulus controllers from your components
|
173
|
+
import { application } from "./application"
|
174
|
+
JS
|
175
|
+
end
|
176
|
+
|
177
|
+
# @return [void]
|
178
|
+
def install_importmap
|
179
|
+
return if ::File.exist?('config/importmap.rb') && defined?(::Importmap)
|
180
|
+
|
181
|
+
unless defined?(::Importmap)
|
182
|
+
system 'gem install importmap-rails'
|
183
|
+
gem 'importmap-rails'
|
184
|
+
system 'bundle install'
|
185
|
+
end
|
186
|
+
rake 'importmap:install'
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [void]
|
190
|
+
def install_stimulus
|
191
|
+
return if defined?(::Stimulus)
|
192
|
+
|
193
|
+
system 'gem install stimulus-rails'
|
194
|
+
gem 'stimulus-rails'
|
195
|
+
system 'bundle install'
|
196
|
+
rake 'stimulus:install'
|
197
|
+
end
|
198
|
+
|
30
199
|
# @param file_name [String]
|
31
200
|
# @return [void]
|
32
201
|
def require_components_css_in(file_name)
|
@@ -7,41 +7,36 @@ class AmberComponentGenerator < ::Rails::Generators::NamedBase
|
|
7
7
|
desc 'Generate a new component'
|
8
8
|
source_root ::File.expand_path('templates', __dir__)
|
9
9
|
|
10
|
-
# @return [Array<Symbol>]
|
11
|
-
VIEW_FORMATS = %i[html erb haml slim].freeze
|
12
|
-
# @return [Array<Symbol>]
|
13
|
-
STYLE_FORMATS = %i[css scss sass].freeze
|
14
|
-
|
15
10
|
class_option :view,
|
16
11
|
aliases: ['-v'],
|
17
|
-
desc: "Indicate what type of view should be generated
|
12
|
+
desc: "Indicate what type of view should be generated " \
|
13
|
+
"eg. #{::AmberComponent::Configuration::ALLOWED_VIEWS}"
|
18
14
|
|
19
15
|
class_option :css,
|
20
|
-
aliases: ['--
|
21
|
-
desc: "Indicate what type of styles should be generated
|
16
|
+
aliases: ['--styles', '-c'],
|
17
|
+
desc: "Indicate what type of styles should be generated " \
|
18
|
+
"eg. #{::AmberComponent::Configuration::ALLOWED_STYLES}"
|
22
19
|
|
23
20
|
def generate_component
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@style_format = options[:css]&.to_sym
|
21
|
+
set_view_format
|
22
|
+
set_stylesheet_format
|
28
23
|
|
29
|
-
unless
|
30
|
-
|
31
|
-
return
|
24
|
+
unless ::AmberComponent::Configuration::ALLOWED_VIEWS.include? @view_format
|
25
|
+
raise ::ArgumentError, "No such view format as `#{@view_format}`"
|
32
26
|
end
|
33
27
|
|
34
|
-
|
35
|
-
|
36
|
-
return
|
28
|
+
unless ::AmberComponent::Configuration::ALLOWED_STYLES.include?(@stylesheet_format)
|
29
|
+
raise ::ArgumentError, "No such css/style format as `#{@stylesheet_format}`"
|
37
30
|
end
|
38
31
|
|
39
32
|
template 'component.rb.erb', "app/components/#{file_path}.rb"
|
40
33
|
template 'component_test.rb.erb', "test/components/#{file_path}_test.rb"
|
41
34
|
create_stylesheet
|
42
35
|
create_view
|
36
|
+
create_stimulus_controller
|
43
37
|
end
|
44
38
|
|
39
|
+
# @return [String]
|
45
40
|
def file_name
|
46
41
|
name = super
|
47
42
|
return name if name.end_with? '_component'
|
@@ -51,6 +46,37 @@ class AmberComponentGenerator < ::Rails::Generators::NamedBase
|
|
51
46
|
|
52
47
|
private
|
53
48
|
|
49
|
+
def set_view_format
|
50
|
+
@view_format = options[:view]&.to_sym || ::AmberComponent.configuration.view_format || :erb
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_stylesheet_format
|
54
|
+
@stylesheet_format = options[:style]&.to_sym || ::AmberComponent.configuration.stylesheet_format || :css
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean]
|
58
|
+
def stimulus?
|
59
|
+
::AmberComponent.configuration.stimulus?
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Boolean]
|
63
|
+
def stimulus_importmap?
|
64
|
+
::AmberComponent.configuration.stimulus_importmap?
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [void]
|
68
|
+
def create_stimulus_controller
|
69
|
+
return unless stimulus?
|
70
|
+
|
71
|
+
template 'controller.js.erb', "app/components/#{file_path}/controller.js"
|
72
|
+
return if stimulus_importmap?
|
73
|
+
|
74
|
+
append_file 'app/javascript/controllers/components.js', <<~JS
|
75
|
+
import #{stimulus_controller_class_name} from "../../components/#{file_path}/controller"
|
76
|
+
application.register("#{stimulus_controller_id}", #{stimulus_controller_class_name})
|
77
|
+
JS
|
78
|
+
end
|
79
|
+
|
54
80
|
# @return [void]
|
55
81
|
def create_view
|
56
82
|
case @view_format
|
@@ -65,12 +91,23 @@ class AmberComponentGenerator < ::Rails::Generators::NamedBase
|
|
65
91
|
|
66
92
|
# @return [void]
|
67
93
|
def create_stylesheet
|
68
|
-
|
94
|
+
case @stylesheet_format
|
95
|
+
when :scss
|
69
96
|
template 'style.scss.erb', "app/components/#{file_path}/style.scss"
|
70
|
-
|
97
|
+
when :sass
|
71
98
|
template 'style.sass.erb', "app/components/#{file_path}/style.sass"
|
72
99
|
else
|
73
100
|
template 'style.css.erb', "app/components/#{file_path}/style.css"
|
74
101
|
end
|
75
102
|
end
|
103
|
+
|
104
|
+
# @return [String]
|
105
|
+
def stimulus_controller_id
|
106
|
+
file_path.gsub('_', '-').gsub('/', '--')
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [String]
|
110
|
+
def stimulus_controller_class_name
|
111
|
+
file_path.gsub('/', '_').camelize
|
112
|
+
end
|
76
113
|
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: 1.
|
4
|
+
version: 1.1.1
|
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-14 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
|