view_component 2.17.0 → 2.19.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+.
@@ -47,6 +50,10 @@ Traditional Rails views have an implicit interface, making it hard to reason abo
47
50
 
48
51
  ViewComponents use a standard Ruby initializer that clearly defines what is needed to render, making them easier (and safer) to reuse than partials.
49
52
 
53
+ #### Performance
54
+
55
+ Based on our [benchmarks](performance/benchmark.rb), ViewComponents are ~10x faster than partials.
56
+
50
57
  #### Standards
51
58
 
52
59
  Views often fail basic Ruby code quality standards: long methods, deep conditional nesting, and mystery guests abound.
@@ -63,6 +70,8 @@ Component names end in -`Component`.
63
70
 
64
71
  Component module names are plural, as for controllers and jobs: `Users::AvatarComponent`
65
72
 
73
+ Name components for what they render, not what they accept. (`AvatarComponent` instead of `UserComponent`)
74
+
66
75
  #### Quick start
67
76
 
68
77
  Use the component generator to create a new ViewComponent.
@@ -90,6 +99,7 @@ bin/rails generate component Example title content --template-engine slim
90
99
  A ViewComponent is a Ruby file and corresponding template file with the same base name:
91
100
 
92
101
  `app/components/test_component.rb`:
102
+
93
103
  ```ruby
94
104
  class TestComponent < ViewComponent::Base
95
105
  def initialize(title:)
@@ -99,6 +109,7 @@ end
99
109
  ```
100
110
 
101
111
  `app/components/test_component.html.erb`:
112
+
102
113
  ```erb
103
114
  <span title="<%= @title %>"><%= content %></span>
104
115
  ```
@@ -124,6 +135,7 @@ Content passed to a ViewComponent as a block is captured and assigned to the `co
124
135
  ViewComponents can declare additional content areas. For example:
125
136
 
126
137
  `app/components/modal_component.rb`:
138
+
127
139
  ```ruby
128
140
  class ModalComponent < ViewComponent::Base
129
141
  with_content_areas :header, :body
@@ -131,6 +143,7 @@ end
131
143
  ```
132
144
 
133
145
  `app/components/modal_component.html.erb`:
146
+
134
147
  ```erb
135
148
  <div class="modal">
136
149
  <div class="header"><%= header %></div>
@@ -164,19 +177,17 @@ Returning:
164
177
 
165
178
  _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
179
 
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.
180
+ Slots enable multiple blocks of content to be passed to a single ViewComponent, reducing the need for sub-components (e.g. ModalHeader, ModalBody).
170
181
 
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.
182
+ 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
183
 
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.
184
+ 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
185
 
175
- To learn more about the design of the Slots API, see https://github.com/github/view_component/pull/348.
186
+ 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
187
 
177
188
  ##### Defining Slots
178
189
 
179
- Slots are defined by the `with_slot` macro:
190
+ Slots are defined by `with_slot`:
180
191
 
181
192
  `with_slot :header`
182
193
 
@@ -184,15 +195,16 @@ To define a collection slot, add `collection: true`:
184
195
 
185
196
  `with_slot :row, collection: true`
186
197
 
187
- To define a slot with a custom class, pass `class_name`:
198
+ To define a slot with a custom Ruby class, pass `class_name`:
188
199
 
189
200
  `with_slot :body, class_name: 'BodySlot`
190
201
 
191
- Slot classes should be subclasses of `ViewComponent::Slot`.
202
+ _Note: Slot classes must be subclasses of `ViewComponent::Slot`._
192
203
 
193
204
  ##### Example ViewComponent with Slots
194
205
 
195
206
  `# box_component.rb`
207
+
196
208
  ```ruby
197
209
  class BoxComponent < ViewComponent::Base
198
210
  include ViewComponent::Slotable
@@ -202,12 +214,12 @@ class BoxComponent < ViewComponent::Base
202
214
  with_slot :row, collection: true, class_name: "Row"
203
215
 
204
216
  class Header < ViewComponent::Slot
205
- def initialize(class_names: "")
206
- @class_names = class_names
217
+ def initialize(classes: "")
218
+ @classes = classes
207
219
  end
208
220
 
209
- def class_names
210
- "Box-header #{@class_names}"
221
+ def classes
222
+ "Box-header #{@classes}"
211
223
  end
212
224
  end
213
225
 
@@ -237,10 +249,11 @@ end
237
249
  ```
238
250
 
239
251
  `# box_component.html.erb`
252
+
240
253
  ```erb
241
254
  <div class="Box">
242
255
  <% if header %>
243
- <div class="<%= header.class_names %>">
256
+ <div class="<%= header.classes %>">
244
257
  <%= header.content %>
245
258
  </div>
246
259
  <% end %>
@@ -260,16 +273,17 @@ end
260
273
  <% end %>
261
274
  <% if footer %>
262
275
  <div class="Box-footer">
263
- <%= footer %>
276
+ <%= footer.content %>
264
277
  </div>
265
278
  <% end %>
266
279
  </div>
267
280
  ```
268
281
 
269
282
  `# index.html.erb`
283
+
270
284
  ```erb
271
285
  <%= render(BoxComponent.new) do |component| %>
272
- <% component.slot(:header, class_names: "my-class-name") do %>
286
+ <% component.slot(:header, classes: "my-class-name") do %>
273
287
  This is my header!
274
288
  <% end %>
275
289
  <% component.slot(:body) do %>
@@ -292,6 +306,7 @@ end
292
306
  ViewComponents can render without a template file, by defining a `call` method:
293
307
 
294
308
  `app/components/inline_component.rb`:
309
+
295
310
  ```ruby
296
311
  class InlineComponent < ViewComponent::Base
297
312
  def call
@@ -318,6 +333,18 @@ class InlineVariantComponent < ViewComponent::Base
318
333
  end
319
334
  ```
320
335
 
336
+ ### Template Inheritance
337
+
338
+ Components that subclass another component inherit the parent component's
339
+ template if they don't define their own template.
340
+
341
+ ```ruby
342
+ # If `my_link_component.html.erb` is not defined the component will fall back
343
+ # to `LinkComponent`s template
344
+ class MyLinkComponent < LinkComponent
345
+ end
346
+ ```
347
+
321
348
  ### Sidecar Assets
322
349
 
323
350
  ViewComponents supports two options for defining view files.
@@ -326,7 +353,7 @@ ViewComponents supports two options for defining view files.
326
353
 
327
354
  The simplest option is to place the view next to the Ruby component:
328
355
 
329
- ```
356
+ ```console
330
357
  app/components
331
358
  ├── ...
332
359
  ├── test_component.rb
@@ -338,7 +365,7 @@ app/components
338
365
 
339
366
  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
367
 
341
- ```
368
+ ```console
342
369
  app/components
343
370
  ├── ...
344
371
  ├── example_component.rb
@@ -347,12 +374,11 @@ app/components
347
374
  | ├── example_component.html.erb
348
375
  | └── example_component.js
349
376
  ├── ...
350
-
351
377
  ```
352
378
 
353
379
  To generate a component with a sidecar directory, use the `--sidecar` flag:
354
380
 
355
- ```
381
+ ```console
356
382
  bin/rails generate component Example title content --sidecar
357
383
  invoke test_unit
358
384
  create test/components/example_component_test.rb
@@ -360,6 +386,32 @@ bin/rails generate component Example title content --sidecar
360
386
  create app/components/example_component/example_component.html.erb
361
387
  ```
362
388
 
389
+ #### Component file inside Sidecar directory
390
+
391
+ It's also possible to place the Ruby component file inside the sidecar directory, grouping all related files in the same folder:
392
+
393
+ _Note: Avoid giving your containing folder the same name as your `.rb` file or there will be a conflict between Module and Class definitions_
394
+
395
+ ```console
396
+ app/components
397
+ ├── ...
398
+ ├── example
399
+ | ├── component.rb
400
+ | ├── component.css
401
+ | ├── component.html.erb
402
+ | └── component.js
403
+ ├── ...
404
+
405
+ ```
406
+
407
+ The component can then be rendered using the folder name as a namespace:
408
+
409
+ ```erb
410
+ <%= render(Example::Component.new(title: "my title")) do %>
411
+ Hello, World!
412
+ <% end %>
413
+ ```
414
+
363
415
  ### Conditional Rendering
364
416
 
365
417
  Components can implement a `#render?` method to be called after initialization to determine if the component should render.
@@ -367,7 +419,8 @@ Components can implement a `#render?` method to be called after initialization t
367
419
  Traditionally, the logic for whether to render a view could go in either the component template:
368
420
 
369
421
  `app/components/confirm_email_component.html.erb`
370
- ```
422
+
423
+ ```erb
371
424
  <% if user.requires_confirmation? %>
372
425
  <div class="alert">Please confirm your email address.</div>
373
426
  <% end %>
@@ -376,6 +429,7 @@ Traditionally, the logic for whether to render a view could go in either the com
376
429
  or the view that renders the component:
377
430
 
378
431
  `app/views/_banners.html.erb`
432
+
379
433
  ```erb
380
434
  <% if current_user.requires_confirmation? %>
381
435
  <%= render(ConfirmEmailComponent.new(user: current_user)) %>
@@ -385,6 +439,7 @@ or the view that renders the component:
385
439
  Using the `#render?` hook simplifies the view:
386
440
 
387
441
  `app/components/confirm_email_component.rb`
442
+
388
443
  ```ruby
389
444
  class ConfirmEmailComponent < ViewComponent::Base
390
445
  def initialize(user:)
@@ -398,13 +453,15 @@ end
398
453
  ```
399
454
 
400
455
  `app/components/confirm_email_component.html.erb`
401
- ```
456
+
457
+ ```erb
402
458
  <div class="banner">
403
459
  Please confirm your email address.
404
460
  </div>
405
461
  ```
406
462
 
407
463
  `app/views/_banners.html.erb`
464
+
408
465
  ```erb
409
466
  <%= render(ConfirmEmailComponent.new(user: current_user)) %>
410
467
  ```
@@ -416,6 +473,7 @@ _To assert that a component has not been rendered, use `refute_component_rendere
416
473
  Components can define a `before_render` method to be called before a component is rendered, when `helpers` is able to be used:
417
474
 
418
475
  `app/components/confirm_email_component.rb`
476
+
419
477
  ```ruby
420
478
  class MyComponent < ViewComponent::Base
421
479
  def before_render
@@ -429,11 +487,13 @@ end
429
487
  Use `with_collection` to render a ViewComponent with a collection:
430
488
 
431
489
  `app/view/products/index.html.erb`
490
+
432
491
  ``` erb
433
492
  <%= render(ProductComponent.with_collection(@products)) %>
434
493
  ```
435
494
 
436
495
  `app/components/product_component.rb`
496
+
437
497
  ``` ruby
438
498
  class ProductComponent < ViewComponent::Base
439
499
  def initialize(product:)
@@ -449,6 +509,7 @@ end
449
509
  Use `with_collection_parameter` to change the name of the collection parameter:
450
510
 
451
511
  `app/components/product_component.rb`
512
+
452
513
  ``` ruby
453
514
  class ProductComponent < ViewComponent::Base
454
515
  with_collection_parameter :item
@@ -464,11 +525,13 @@ end
464
525
  Additional arguments besides the collection are passed to each component instance:
465
526
 
466
527
  `app/view/products/index.html.erb`
528
+
467
529
  ``` erb
468
530
  <%= render(ProductComponent.with_collection(@products, notice: "hi")) %>
469
531
  ```
470
532
 
471
533
  `app/components/product_component.rb`
534
+
472
535
  ``` ruby
473
536
  class ProductComponent < ViewComponent::Base
474
537
  with_collection_parameter :item
@@ -481,6 +544,7 @@ end
481
544
  ```
482
545
 
483
546
  `app/components/product_component.html.erb`
547
+
484
548
  ``` erb
485
549
  <li>
486
550
  <h2><%= @item.name %></h2>
@@ -493,6 +557,7 @@ end
493
557
  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
558
 
495
559
  `app/components/product_component.rb`
560
+
496
561
  ``` ruby
497
562
  class ProductComponent < ViewComponent::Base
498
563
  def initialize(product:, product_counter:)
@@ -503,6 +568,7 @@ end
503
568
  ```
504
569
 
505
570
  `app/components/product_component.html.erb`
571
+
506
572
  ``` erb
507
573
  <li>
508
574
  <%= @counter %> <%= @product.name %>
@@ -551,7 +617,7 @@ class UserComponent < ViewComponent::Base
551
617
  end
552
618
  ```
553
619
 
554
- ### Testing
620
+ ### Writing tests
555
621
 
556
622
  Unit test components directly, using the `render_inline` test helper, asserting against the rendered output.
557
623
 
@@ -619,9 +685,11 @@ end
619
685
  ```
620
686
 
621
687
  ### Previewing Components
688
+
622
689
  `ViewComponent::Preview`, like `ActionMailer::Preview`, provides a way to preview components in isolation:
623
690
 
624
691
  `test/components/previews/test_component_preview.rb`
692
+
625
693
  ```ruby
626
694
  class TestComponentPreview < ViewComponent::Preview
627
695
  def with_default_title
@@ -649,6 +717,7 @@ and <http://localhost:3000/rails/view_components/test_component/with_content_blo
649
717
  It's also possible to set dynamic values from the params by setting them as arguments:
650
718
 
651
719
  `test/components/previews/test_component_preview.rb`
720
+
652
721
  ```ruby
653
722
  class TestComponentPreview < ViewComponent::Preview
654
723
  def with_dynamic_title(title: "Test component default")
@@ -666,6 +735,7 @@ and [`content_tag`](https://api.rubyonrails.org/classes/ActionView/Helpers/TagHe
666
735
  Previews use the application layout by default, but can use a specific layout with the `layout` option:
667
736
 
668
737
  `test/components/previews/test_component_preview.rb`
738
+
669
739
  ```ruby
670
740
  class TestComponentPreview < ViewComponent::Preview
671
741
  layout "admin"
@@ -674,9 +744,19 @@ class TestComponentPreview < ViewComponent::Preview
674
744
  end
675
745
  ```
676
746
 
747
+ 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:
748
+
749
+ `config/application.rb`
750
+
751
+ ```ruby
752
+ # Set the default layout to app/views/layouts/component_preview.html.erb
753
+ config.view_component.default_preview_layout = "component_preview"
754
+ ```
755
+
677
756
  Preview classes live in `test/components/previews`, which can be configured using the `preview_paths` option:
678
757
 
679
758
  `config/application.rb`
759
+
680
760
  ```ruby
681
761
  config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
682
762
  ```
@@ -684,6 +764,7 @@ config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
684
764
  Previews are served from <http://localhost:3000/rails/view_components> by default. To use a different endpoint, set the `preview_route` option:
685
765
 
686
766
  `config/application.rb`
767
+
687
768
  ```ruby
688
769
  config.view_component.preview_route = "/previews"
689
770
  ```
@@ -695,6 +776,7 @@ This example will make the previews available from <http://localhost:3000/previe
695
776
  Given a preview `test/components/previews/cell_component_preview.rb`, template files can be defined at `test/components/previews/cell_component_preview/`:
696
777
 
697
778
  `test/components/previews/cell_component_preview.rb`
779
+
698
780
  ```ruby
699
781
  class CellComponentPreview < ViewComponent::Preview
700
782
  def default
@@ -703,6 +785,7 @@ end
703
785
  ```
704
786
 
705
787
  `test/components/previews/cell_component_preview/default.html.erb`
788
+
706
789
  ```erb
707
790
  <table class="table">
708
791
  <tbody>
@@ -717,6 +800,7 @@ To use a different location for preview templates, pass the `template` argument:
717
800
  (the path should be relative to `config.view_component.preview_path`):
718
801
 
719
802
  `test/components/previews/cell_component_preview.rb`
803
+
720
804
  ```ruby
721
805
  class CellComponentPreview < ViewComponent::Preview
722
806
  def default
@@ -728,6 +812,7 @@ end
728
812
  Values from `params` can be accessed through `locals`:
729
813
 
730
814
  `test/components/previews/cell_component_preview.rb`
815
+
731
816
  ```ruby
732
817
  class CellComponentPreview < ViewComponent::Preview
733
818
  def default(title: "Default title", subtitle: "A subtitle")
@@ -746,6 +831,7 @@ Which enables passing in a value with <http://localhost:3000/rails/components/ce
746
831
  Component tests and previews assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
747
832
 
748
833
  `config/application.rb`
834
+
749
835
  ```ruby
750
836
  config.view_component.test_controller = "BaseController"
751
837
  ```
@@ -755,6 +841,7 @@ config.view_component.test_controller = "BaseController"
755
841
  To use RSpec, add the following:
756
842
 
757
843
  `spec/rails_helper.rb`
844
+
758
845
  ```ruby
759
846
  require "view_component/test_helpers"
760
847
 
@@ -768,6 +855,7 @@ Specs created by the generator have access to test helpers like `render_inline`.
768
855
  To use component previews:
769
856
 
770
857
  `config/application.rb`
858
+
771
859
  ```ruby
772
860
  config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
773
861
  ```
@@ -780,7 +868,7 @@ In order to [avoid conflicts](https://github.com/github/view_component/issues/28
780
868
 
781
869
  With the monkey patch disabled, use `render_component` (or `render_component_to_string`) instead:
782
870
 
783
- ```
871
+ ```erb
784
872
  <%= render_component Component.new(message: "bar") %>
785
873
  ```
786
874
 
@@ -818,6 +906,7 @@ One approach is to use Web Components, which contain all Javascript functionalit
818
906
  For example:
819
907
 
820
908
  `app/components/comment_component.rb`
909
+
821
910
  ```ruby
822
911
  class CommentComponent < ViewComponent::Base
823
912
  def initialize(comment:)
@@ -847,6 +936,7 @@ end
847
936
  ```
848
937
 
849
938
  `app/components/comment_component.html.erb`
939
+
850
940
  ```erb
851
941
  <my-comment comment-id="<%= comment.id %>">
852
942
  <time slot="posted" datetime="<%= comment.created_at.iso8601 %>"><%= comment.created_at.strftime("%b %-d") %></time>
@@ -860,6 +950,7 @@ end
860
950
  ```
861
951
 
862
952
  `app/components/comment_component.js`
953
+
863
954
  ```js
864
955
  class Comment extends HTMLElement {
865
956
  styles() {
@@ -913,6 +1004,29 @@ application.load(
913
1004
 
914
1005
  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.
915
1006
 
1007
+ 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](
1008
+ https://stimulusjs.org/handbook/installing#controller-filenames-map-to-identifiers):
1009
+
1010
+ ```console
1011
+ app/components
1012
+ ├── ...
1013
+ ├── example
1014
+ | ├── component.rb
1015
+ | ├── component.css
1016
+ | ├── component.html.erb
1017
+ | └── component_controller.js
1018
+ ├── ...
1019
+ ```
1020
+
1021
+ `component_controller.js`'s Stimulus identifier becomes: `example--component`:
1022
+
1023
+ ```erb
1024
+ <div data-controller="example--component">
1025
+ <input type="text">
1026
+ <button data-action="click->example--component#greet">Greet</button>
1027
+ </div>
1028
+ ```
1029
+
916
1030
  ## Frequently Asked Questions
917
1031
 
918
1032
  ### Can I use other templating languages besides ERB?
@@ -944,7 +1058,7 @@ ViewComponent is far from a novel idea! Popular implementations of view componen
944
1058
 
945
1059
  ## Contributing
946
1060
 
947
- 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.
1061
+ 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.
948
1062
 
949
1063
  ## Contributors
950
1064
 
@@ -990,10 +1104,15 @@ ViewComponent is built by:
990
1104
  |@maxbeizer|@franco|@tbroad-ramsey|@jensljungblad|@bbugh|
991
1105
  |Nashville, TN|Switzerland|Spring Hill, TN|New York, NY|Austin, TX|
992
1106
 
993
- |<img src="https://avatars.githubusercontent.com/johannesengl?s=256" alt="johannesengl" width="128" />|<img src="https://avatars.githubusercontent.com/czj?s=256" alt="czj" width="128" />|<img src="https://avatars.githubusercontent.com/mrrooijen?s=256" alt="mrrooijen" width="128" />|
994
- |:---:|:---:|:---:|
995
- |@johannesengl|@czj|@mrrooijen|
996
- |Berlin, Germany|Paris, France|The Netherlands|
1107
+ |<img src="https://avatars.githubusercontent.com/johannesengl?s=256" alt="johannesengl" width="128" />|<img src="https://avatars.githubusercontent.com/czj?s=256" alt="czj" width="128" />|<img src="https://avatars.githubusercontent.com/mrrooijen?s=256" alt="mrrooijen" width="128" />|<img src="https://avatars.githubusercontent.com/bradparker?s=256" alt="bradparker" width="128" />|<img src="https://avatars.githubusercontent.com/mattbrictson?s=256" alt="mattbrictson" width="128" />|
1108
+ |:---:|:---:|:---:|:---:|:---:|
1109
+ |@johannesengl|@czj|@mrrooijen|@bradparker|@mattbrictson|
1110
+ |Berlin, Germany|Paris, France|The Netherlands|Brisbane, Australia|San Francisco|
1111
+
1112
+ |<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" />|
1113
+ |:---:|:---:|
1114
+ |@mixergtz|@jules2689|
1115
+ |Medellin, Colombia|Toronto, Canada|
997
1116
 
998
1117
  ## License
999
1118