view_component 2.18.0 → 2.20.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.

Potentially problematic release.


This version of view_component might be problematic. Click here for more details.

data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # ViewComponent
2
+
2
3
  ViewComponent is a framework for building view components that are reusable, testable & encapsulated, in Ruby on Rails.
3
4
 
4
5
  ## Design philosophy
6
+
5
7
  ViewComponent is designed to integrate as seamlessly as possible [with Rails](https://rubyonrails.org/doctrine/), with the [least surprise](https://www.artima.com/intv/ruby4.html).
6
8
 
7
9
  ## Compatibility
10
+
8
11
  ViewComponent is [supported natively](https://edgeguides.rubyonrails.org/layouts_and_rendering.html#rendering-objects) in Rails 6.1, and compatible with Rails 5.0+ via an included [monkey patch](https://github.com/github/view_component/blob/master/lib/view_component/render_monkey_patch.rb).
9
12
 
10
13
  ViewComponent is tested for compatibility [with combinations of](https://github.com/github/view_component/blob/22e3d4ccce70d8f32c7375e5a5ccc3f70b22a703/.github/workflows/ruby_on_rails.yml#L10-L11) Ruby 2.4+ and Rails 5+.
@@ -14,13 +17,7 @@ ViewComponent is tested for compatibility [with combinations of](https://github.
14
17
  In `Gemfile`, add:
15
18
 
16
19
  ```ruby
17
- gem "view_component"
18
- ```
19
-
20
- In `config/application.rb`, add:
21
-
22
- ```bash
23
- require "view_component/engine"
20
+ gem "view_component", require: "view_component/engine"
24
21
  ```
25
22
 
26
23
  ## Guide
@@ -47,6 +44,10 @@ Traditional Rails views have an implicit interface, making it hard to reason abo
47
44
 
48
45
  ViewComponents use a standard Ruby initializer that clearly defines what is needed to render, making them easier (and safer) to reuse than partials.
49
46
 
47
+ #### Performance
48
+
49
+ Based on our [benchmarks](performance/benchmark.rb), ViewComponents are ~10x faster than partials.
50
+
50
51
  #### Standards
51
52
 
52
53
  Views often fail basic Ruby code quality standards: long methods, deep conditional nesting, and mystery guests abound.
@@ -63,6 +64,8 @@ Component names end in -`Component`.
63
64
 
64
65
  Component module names are plural, as for controllers and jobs: `Users::AvatarComponent`
65
66
 
67
+ Name components for what they render, not what they accept. (`AvatarComponent` instead of `UserComponent`)
68
+
66
69
  #### Quick start
67
70
 
68
71
  Use the component generator to create a new ViewComponent.
@@ -90,6 +93,7 @@ bin/rails generate component Example title content --template-engine slim
90
93
  A ViewComponent is a Ruby file and corresponding template file with the same base name:
91
94
 
92
95
  `app/components/test_component.rb`:
96
+
93
97
  ```ruby
94
98
  class TestComponent < ViewComponent::Base
95
99
  def initialize(title:)
@@ -99,6 +103,7 @@ end
99
103
  ```
100
104
 
101
105
  `app/components/test_component.html.erb`:
106
+
102
107
  ```erb
103
108
  <span title="<%= @title %>"><%= content %></span>
104
109
  ```
@@ -124,6 +129,7 @@ Content passed to a ViewComponent as a block is captured and assigned to the `co
124
129
  ViewComponents can declare additional content areas. For example:
125
130
 
126
131
  `app/components/modal_component.rb`:
132
+
127
133
  ```ruby
128
134
  class ModalComponent < ViewComponent::Base
129
135
  with_content_areas :header, :body
@@ -131,6 +137,7 @@ end
131
137
  ```
132
138
 
133
139
  `app/components/modal_component.html.erb`:
140
+
134
141
  ```erb
135
142
  <div class="modal">
136
143
  <div class="header"><%= header %></div>
@@ -164,19 +171,17 @@ Returning:
164
171
 
165
172
  _Slots are currently under development as a successor to Content Areas. The Slot APIs should be considered unfinished and subject to breaking changes in non-major releases of ViewComponent._
166
173
 
167
- Slots enable multiple blocks of content to be passed to a single ViewComponent.
168
-
169
- Slots exist in two forms: normal slots and collection slots.
174
+ Slots enable multiple blocks of content to be passed to a single ViewComponent, reducing the need for sub-components (e.g. ModalHeader, ModalBody).
170
175
 
171
- Normal slots can be rendered once per component. They expose an accessor with the name of the slot that returns an instance of `ViewComponent::Slot`, etc.
176
+ By default, slots can be rendered once per component. They provide an accessor with the name of the slot (`#header`) that returns an instance of `ViewComponent::Slot`, etc.
172
177
 
173
- Collection slots can be rendered multiple times. They expose an accessor with the pluralized name of the slot (`#rows`), which is an Array of `ViewComponent::Slot` instances.
178
+ Slots declared with `collection: true` can be rendered multiple times. They provide an accessor with the pluralized name of the slot (`#rows`), which is an Array of `ViewComponent::Slot` instances.
174
179
 
175
- To learn more about the design of the Slots API, see https://github.com/github/view_component/pull/348.
180
+ To learn more about the design of the Slots API, see [#348](https://github.com/github/view_component/pull/348) and [#325](https://github.com/github/view_component/discussions/325).
176
181
 
177
182
  ##### Defining Slots
178
183
 
179
- Slots are defined by the `with_slot` macro:
184
+ Slots are defined by `with_slot`:
180
185
 
181
186
  `with_slot :header`
182
187
 
@@ -184,15 +189,16 @@ To define a collection slot, add `collection: true`:
184
189
 
185
190
  `with_slot :row, collection: true`
186
191
 
187
- To define a slot with a custom class, pass `class_name`:
192
+ To define a slot with a custom Ruby class, pass `class_name`:
188
193
 
189
- `with_slot :body, class_name: 'BodySlot`
194
+ `with_slot :body, class_name: 'BodySlot'`
190
195
 
191
- Slot classes should be subclasses of `ViewComponent::Slot`.
196
+ _Note: Slot classes must be subclasses of `ViewComponent::Slot`._
192
197
 
193
198
  ##### Example ViewComponent with Slots
194
199
 
195
200
  `# box_component.rb`
201
+
196
202
  ```ruby
197
203
  class BoxComponent < ViewComponent::Base
198
204
  include ViewComponent::Slotable
@@ -202,12 +208,12 @@ class BoxComponent < ViewComponent::Base
202
208
  with_slot :row, collection: true, class_name: "Row"
203
209
 
204
210
  class Header < ViewComponent::Slot
205
- def initialize(class_names: "")
206
- @class_names = class_names
211
+ def initialize(classes: "")
212
+ @classes = classes
207
213
  end
208
214
 
209
- def class_names
210
- "Box-header #{@class_names}"
215
+ def classes
216
+ "Box-header #{@classes}"
211
217
  end
212
218
  end
213
219
 
@@ -237,10 +243,11 @@ end
237
243
  ```
238
244
 
239
245
  `# box_component.html.erb`
246
+
240
247
  ```erb
241
248
  <div class="Box">
242
249
  <% if header %>
243
- <div class="<%= header.class_names %>">
250
+ <div class="<%= header.classes %>">
244
251
  <%= header.content %>
245
252
  </div>
246
253
  <% end %>
@@ -260,16 +267,17 @@ end
260
267
  <% end %>
261
268
  <% if footer %>
262
269
  <div class="Box-footer">
263
- <%= footer %>
270
+ <%= footer.content %>
264
271
  </div>
265
272
  <% end %>
266
273
  </div>
267
274
  ```
268
275
 
269
276
  `# index.html.erb`
277
+
270
278
  ```erb
271
279
  <%= render(BoxComponent.new) do |component| %>
272
- <% component.slot(:header, class_names: "my-class-name") do %>
280
+ <% component.slot(:header, classes: "my-class-name") do %>
273
281
  This is my header!
274
282
  <% end %>
275
283
  <% component.slot(:body) do %>
@@ -292,6 +300,7 @@ end
292
300
  ViewComponents can render without a template file, by defining a `call` method:
293
301
 
294
302
  `app/components/inline_component.rb`:
303
+
295
304
  ```ruby
296
305
  class InlineComponent < ViewComponent::Base
297
306
  def call
@@ -318,6 +327,18 @@ class InlineVariantComponent < ViewComponent::Base
318
327
  end
319
328
  ```
320
329
 
330
+ ### Template Inheritance
331
+
332
+ Components that subclass another component inherit the parent component's
333
+ template if they don't define their own template.
334
+
335
+ ```ruby
336
+ # If `my_link_component.html.erb` is not defined the component will fall back
337
+ # to `LinkComponent`s template
338
+ class MyLinkComponent < LinkComponent
339
+ end
340
+ ```
341
+
321
342
  ### Sidecar Assets
322
343
 
323
344
  ViewComponents supports two options for defining view files.
@@ -326,7 +347,7 @@ ViewComponents supports two options for defining view files.
326
347
 
327
348
  The simplest option is to place the view next to the Ruby component:
328
349
 
329
- ```
350
+ ```console
330
351
  app/components
331
352
  ├── ...
332
353
  ├── test_component.rb
@@ -338,7 +359,7 @@ app/components
338
359
 
339
360
  As an alternative, views and other assets can be placed in a sidecar directory with the same name as the component, which can be useful for organizing views alongside other assets like Javascript and CSS.
340
361
 
341
- ```
362
+ ```console
342
363
  app/components
343
364
  ├── ...
344
365
  ├── example_component.rb
@@ -347,12 +368,11 @@ app/components
347
368
  | ├── example_component.html.erb
348
369
  | └── example_component.js
349
370
  ├── ...
350
-
351
371
  ```
352
372
 
353
373
  To generate a component with a sidecar directory, use the `--sidecar` flag:
354
374
 
355
- ```
375
+ ```console
356
376
  bin/rails generate component Example title content --sidecar
357
377
  invoke test_unit
358
378
  create test/components/example_component_test.rb
@@ -360,6 +380,32 @@ bin/rails generate component Example title content --sidecar
360
380
  create app/components/example_component/example_component.html.erb
361
381
  ```
362
382
 
383
+ #### Component file inside Sidecar directory
384
+
385
+ It's also possible to place the Ruby component file inside the sidecar directory, grouping all related files in the same folder:
386
+
387
+ _Note: Avoid giving your containing folder the same name as your `.rb` file or there will be a conflict between Module and Class definitions_
388
+
389
+ ```console
390
+ app/components
391
+ ├── ...
392
+ ├── example
393
+ | ├── component.rb
394
+ | ├── component.css
395
+ | ├── component.html.erb
396
+ | └── component.js
397
+ ├── ...
398
+
399
+ ```
400
+
401
+ The component can then be rendered using the folder name as a namespace:
402
+
403
+ ```erb
404
+ <%= render(Example::Component.new(title: "my title")) do %>
405
+ Hello, World!
406
+ <% end %>
407
+ ```
408
+
363
409
  ### Conditional Rendering
364
410
 
365
411
  Components can implement a `#render?` method to be called after initialization to determine if the component should render.
@@ -367,7 +413,8 @@ Components can implement a `#render?` method to be called after initialization t
367
413
  Traditionally, the logic for whether to render a view could go in either the component template:
368
414
 
369
415
  `app/components/confirm_email_component.html.erb`
370
- ```
416
+
417
+ ```erb
371
418
  <% if user.requires_confirmation? %>
372
419
  <div class="alert">Please confirm your email address.</div>
373
420
  <% end %>
@@ -376,6 +423,7 @@ Traditionally, the logic for whether to render a view could go in either the com
376
423
  or the view that renders the component:
377
424
 
378
425
  `app/views/_banners.html.erb`
426
+
379
427
  ```erb
380
428
  <% if current_user.requires_confirmation? %>
381
429
  <%= render(ConfirmEmailComponent.new(user: current_user)) %>
@@ -385,6 +433,7 @@ or the view that renders the component:
385
433
  Using the `#render?` hook simplifies the view:
386
434
 
387
435
  `app/components/confirm_email_component.rb`
436
+
388
437
  ```ruby
389
438
  class ConfirmEmailComponent < ViewComponent::Base
390
439
  def initialize(user:)
@@ -398,13 +447,15 @@ end
398
447
  ```
399
448
 
400
449
  `app/components/confirm_email_component.html.erb`
401
- ```
450
+
451
+ ```erb
402
452
  <div class="banner">
403
453
  Please confirm your email address.
404
454
  </div>
405
455
  ```
406
456
 
407
457
  `app/views/_banners.html.erb`
458
+
408
459
  ```erb
409
460
  <%= render(ConfirmEmailComponent.new(user: current_user)) %>
410
461
  ```
@@ -416,6 +467,7 @@ _To assert that a component has not been rendered, use `refute_component_rendere
416
467
  Components can define a `before_render` method to be called before a component is rendered, when `helpers` is able to be used:
417
468
 
418
469
  `app/components/confirm_email_component.rb`
470
+
419
471
  ```ruby
420
472
  class MyComponent < ViewComponent::Base
421
473
  def before_render
@@ -429,11 +481,13 @@ end
429
481
  Use `with_collection` to render a ViewComponent with a collection:
430
482
 
431
483
  `app/view/products/index.html.erb`
484
+
432
485
  ``` erb
433
486
  <%= render(ProductComponent.with_collection(@products)) %>
434
487
  ```
435
488
 
436
489
  `app/components/product_component.rb`
490
+
437
491
  ``` ruby
438
492
  class ProductComponent < ViewComponent::Base
439
493
  def initialize(product:)
@@ -449,6 +503,7 @@ end
449
503
  Use `with_collection_parameter` to change the name of the collection parameter:
450
504
 
451
505
  `app/components/product_component.rb`
506
+
452
507
  ``` ruby
453
508
  class ProductComponent < ViewComponent::Base
454
509
  with_collection_parameter :item
@@ -464,11 +519,13 @@ end
464
519
  Additional arguments besides the collection are passed to each component instance:
465
520
 
466
521
  `app/view/products/index.html.erb`
522
+
467
523
  ``` erb
468
524
  <%= render(ProductComponent.with_collection(@products, notice: "hi")) %>
469
525
  ```
470
526
 
471
527
  `app/components/product_component.rb`
528
+
472
529
  ``` ruby
473
530
  class ProductComponent < ViewComponent::Base
474
531
  with_collection_parameter :item
@@ -481,6 +538,7 @@ end
481
538
  ```
482
539
 
483
540
  `app/components/product_component.html.erb`
541
+
484
542
  ``` erb
485
543
  <li>
486
544
  <h2><%= @item.name %></h2>
@@ -493,6 +551,7 @@ end
493
551
  ViewComponent defines a counter variable matching the parameter name above, followed by `_counter`. To access the variable, add it to `initialize` as an argument:
494
552
 
495
553
  `app/components/product_component.rb`
554
+
496
555
  ``` ruby
497
556
  class ProductComponent < ViewComponent::Base
498
557
  def initialize(product:, product_counter:)
@@ -503,6 +562,7 @@ end
503
562
  ```
504
563
 
505
564
  `app/components/product_component.html.erb`
565
+
506
566
  ``` erb
507
567
  <li>
508
568
  <%= @counter %> <%= @product.name %>
@@ -551,7 +611,7 @@ class UserComponent < ViewComponent::Base
551
611
  end
552
612
  ```
553
613
 
554
- ### Testing
614
+ ### Writing tests
555
615
 
556
616
  Unit test components directly, using the `render_inline` test helper, asserting against the rendered output.
557
617
 
@@ -561,7 +621,7 @@ Capybara matchers are available if the gem is installed:
561
621
  require "view_component/test_case"
562
622
 
563
623
  class MyComponentTest < ViewComponent::TestCase
564
- test "render component" do
624
+ def test_render_component
565
625
  render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
566
626
 
567
627
  assert_selector("span[title='my title']", text: "Hello, World!")
@@ -572,7 +632,7 @@ end
572
632
  In the absence of `capybara`, assert against the return value of `render_inline`, which is an instance of `Nokogiri::HTML::DocumentFragment`:
573
633
 
574
634
  ```ruby
575
- test "render component" do
635
+ def test_render_component
576
636
  result = render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
577
637
 
578
638
  assert_includes result.css("span[title='my title']").to_html, "Hello, World!"
@@ -582,7 +642,7 @@ end
582
642
  Alternatively, assert against the raw output of the component, which is exposed as `rendered_component`:
583
643
 
584
644
  ```ruby
585
- test "render component" do
645
+ def test_render_component
586
646
  render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
587
647
 
588
648
  assert_includes rendered_component, "Hello, World!"
@@ -592,7 +652,7 @@ end
592
652
  To test components that use `with_content_areas`:
593
653
 
594
654
  ```ruby
595
- test "renders content_areas template with content " do
655
+ def test_renders_content_areas_template_with_content
596
656
  render_inline(ContentAreasComponent.new(footer: "Bye!")) do |component|
597
657
  component.with(:title, "Hello!")
598
658
  component.with(:body) { "Have a nice day." }
@@ -609,7 +669,7 @@ end
609
669
  Use the `with_variant` helper to test specific variants:
610
670
 
611
671
  ```ruby
612
- test "render component for tablet" do
672
+ def test_render_component_for_tablet
613
673
  with_variant :tablet do
614
674
  render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }
615
675
 
@@ -619,9 +679,11 @@ end
619
679
  ```
620
680
 
621
681
  ### Previewing Components
682
+
622
683
  `ViewComponent::Preview`, like `ActionMailer::Preview`, provides a way to preview components in isolation:
623
684
 
624
685
  `test/components/previews/test_component_preview.rb`
686
+
625
687
  ```ruby
626
688
  class TestComponentPreview < ViewComponent::Preview
627
689
  def with_default_title
@@ -649,6 +711,7 @@ and <http://localhost:3000/rails/view_components/test_component/with_content_blo
649
711
  It's also possible to set dynamic values from the params by setting them as arguments:
650
712
 
651
713
  `test/components/previews/test_component_preview.rb`
714
+
652
715
  ```ruby
653
716
  class TestComponentPreview < ViewComponent::Preview
654
717
  def with_dynamic_title(title: "Test component default")
@@ -666,6 +729,7 @@ and [`content_tag`](https://api.rubyonrails.org/classes/ActionView/Helpers/TagHe
666
729
  Previews use the application layout by default, but can use a specific layout with the `layout` option:
667
730
 
668
731
  `test/components/previews/test_component_preview.rb`
732
+
669
733
  ```ruby
670
734
  class TestComponentPreview < ViewComponent::Preview
671
735
  layout "admin"
@@ -677,14 +741,16 @@ end
677
741
  You can also set a custom layout to be used by default for previews as well as the preview index pages via the `default_preview_layout` configuration option:
678
742
 
679
743
  `config/application.rb`
744
+
680
745
  ```ruby
681
746
  # Set the default layout to app/views/layouts/component_preview.html.erb
682
- config.default_preview_layout = "component_preview"
747
+ config.view_component.default_preview_layout = "component_preview"
683
748
  ```
684
749
 
685
750
  Preview classes live in `test/components/previews`, which can be configured using the `preview_paths` option:
686
751
 
687
752
  `config/application.rb`
753
+
688
754
  ```ruby
689
755
  config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
690
756
  ```
@@ -692,6 +758,7 @@ config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
692
758
  Previews are served from <http://localhost:3000/rails/view_components> by default. To use a different endpoint, set the `preview_route` option:
693
759
 
694
760
  `config/application.rb`
761
+
695
762
  ```ruby
696
763
  config.view_component.preview_route = "/previews"
697
764
  ```
@@ -703,6 +770,7 @@ This example will make the previews available from <http://localhost:3000/previe
703
770
  Given a preview `test/components/previews/cell_component_preview.rb`, template files can be defined at `test/components/previews/cell_component_preview/`:
704
771
 
705
772
  `test/components/previews/cell_component_preview.rb`
773
+
706
774
  ```ruby
707
775
  class CellComponentPreview < ViewComponent::Preview
708
776
  def default
@@ -711,6 +779,7 @@ end
711
779
  ```
712
780
 
713
781
  `test/components/previews/cell_component_preview/default.html.erb`
782
+
714
783
  ```erb
715
784
  <table class="table">
716
785
  <tbody>
@@ -725,6 +794,7 @@ To use a different location for preview templates, pass the `template` argument:
725
794
  (the path should be relative to `config.view_component.preview_path`):
726
795
 
727
796
  `test/components/previews/cell_component_preview.rb`
797
+
728
798
  ```ruby
729
799
  class CellComponentPreview < ViewComponent::Preview
730
800
  def default
@@ -736,6 +806,7 @@ end
736
806
  Values from `params` can be accessed through `locals`:
737
807
 
738
808
  `test/components/previews/cell_component_preview.rb`
809
+
739
810
  ```ruby
740
811
  class CellComponentPreview < ViewComponent::Preview
741
812
  def default(title: "Default title", subtitle: "A subtitle")
@@ -749,11 +820,22 @@ end
749
820
 
750
821
  Which enables passing in a value with <http://localhost:3000/rails/components/cell_component/default?title=Custom+title&subtitle=Another+subtitle>.
751
822
 
823
+ #### Configuring preview controller
824
+
825
+ Previews can be extended to allow users to add authentication, authorization, before actions, or anything that the end user would need to meet their needs using the `preview_controller` option:
826
+
827
+ `config/application.rb`
828
+
829
+ ```ruby
830
+ config.view_component.preview_controller = "MyPreviewController"
831
+ ```
832
+
752
833
  #### Configuring TestController
753
834
 
754
- Component tests and previews assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
835
+ Component tests assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
755
836
 
756
837
  `config/application.rb`
838
+
757
839
  ```ruby
758
840
  config.view_component.test_controller = "BaseController"
759
841
  ```
@@ -763,6 +845,7 @@ config.view_component.test_controller = "BaseController"
763
845
  To use RSpec, add the following:
764
846
 
765
847
  `spec/rails_helper.rb`
848
+
766
849
  ```ruby
767
850
  require "view_component/test_helpers"
768
851
 
@@ -776,6 +859,7 @@ Specs created by the generator have access to test helpers like `render_inline`.
776
859
  To use component previews:
777
860
 
778
861
  `config/application.rb`
862
+
779
863
  ```ruby
780
864
  config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
781
865
  ```
@@ -788,7 +872,7 @@ In order to [avoid conflicts](https://github.com/github/view_component/issues/28
788
872
 
789
873
  With the monkey patch disabled, use `render_component` (or `render_component_to_string`) instead:
790
874
 
791
- ```
875
+ ```erb
792
876
  <%= render_component Component.new(message: "bar") %>
793
877
  ```
794
878
 
@@ -826,6 +910,7 @@ One approach is to use Web Components, which contain all Javascript functionalit
826
910
  For example:
827
911
 
828
912
  `app/components/comment_component.rb`
913
+
829
914
  ```ruby
830
915
  class CommentComponent < ViewComponent::Base
831
916
  def initialize(comment:)
@@ -855,6 +940,7 @@ end
855
940
  ```
856
941
 
857
942
  `app/components/comment_component.html.erb`
943
+
858
944
  ```erb
859
945
  <my-comment comment-id="<%= comment.id %>">
860
946
  <time slot="posted" datetime="<%= comment.created_at.iso8601 %>"><%= comment.created_at.strftime("%b %-d") %></time>
@@ -868,6 +954,7 @@ end
868
954
  ```
869
955
 
870
956
  `app/components/comment_component.js`
957
+
871
958
  ```js
872
959
  class Comment extends HTMLElement {
873
960
  styles() {
@@ -921,6 +1008,35 @@ application.load(
921
1008
 
922
1009
  This enables the creation of files such as `app/components/widget_controller.js`, where the controller identifier matches the `data-controller` attribute in the component's HTML template.
923
1010
 
1011
+ After configuring Webpack to load Stimulus controller files from the `components` directory, add the path to `resolved_paths` in `config/webpacker.yml`:
1012
+
1013
+ ```yml
1014
+ resolved_paths: ["app/components"]
1015
+ ```
1016
+
1017
+ When placing a Stimulus controller inside a sidecar directory, be aware that when referencing the controller [each forward slash in a namespaced controller file’s path becomes two dashes in its identifier](
1018
+ https://stimulusjs.org/handbook/installing#controller-filenames-map-to-identifiers):
1019
+
1020
+ ```console
1021
+ app/components
1022
+ ├── ...
1023
+ ├── example
1024
+ | ├── component.rb
1025
+ | ├── component.css
1026
+ | ├── component.html.erb
1027
+ | └── component_controller.js
1028
+ ├── ...
1029
+ ```
1030
+
1031
+ `component_controller.js`'s Stimulus identifier becomes: `example--component`:
1032
+
1033
+ ```erb
1034
+ <div data-controller="example--component">
1035
+ <input type="text">
1036
+ <button data-action="click->example--component#greet">Greet</button>
1037
+ </div>
1038
+ ```
1039
+
924
1040
  ## Frequently Asked Questions
925
1041
 
926
1042
  ### Can I use other templating languages besides ERB?
@@ -952,7 +1068,7 @@ ViewComponent is far from a novel idea! Popular implementations of view componen
952
1068
 
953
1069
  ## Contributing
954
1070
 
955
- Bug reports and pull requests are welcome on GitHub at https://github.com/github/view_component. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
1071
+ This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
956
1072
 
957
1073
  ## Contributors
958
1074
 
@@ -1003,6 +1119,11 @@ ViewComponent is built by:
1003
1119
  |@johannesengl|@czj|@mrrooijen|@bradparker|@mattbrictson|
1004
1120
  |Berlin, Germany|Paris, France|The Netherlands|Brisbane, Australia|San Francisco|
1005
1121
 
1122
+ |<img src="https://avatars.githubusercontent.com/mixergtz?s=256" alt="mixergtz" width="128" />|<img src="https://avatars.githubusercontent.com/jules2689?s=256" alt="jules2689" width="128" />|<img src="https://avatars.githubusercontent.com/g13ydson?s=256" alt="g13ydson" width="128" />|<img src="https://avatars.githubusercontent.com/swanson?s=256" alt="swanson" width="128" />|
1123
+ |:---:|:---:|:---:|:---:|
1124
+ |@mixergtz|@jules2689|@g13ydson|@swanson|
1125
+ |Medellin, Colombia|Toronto, Canada|João Pessoa, Brazil|Indianapolis, IN|
1126
+
1006
1127
  ## License
1007
1128
 
1008
1129
  ViewComponent is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).