view_component 2.11.1 → 2.15.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +204 -8
- data/app/controllers/view_components_controller.rb +11 -2
- data/lib/view_component.rb +1 -0
- data/lib/view_component/base.rb +12 -0
- data/lib/view_component/engine.rb +35 -12
- data/lib/view_component/preview.rb +33 -4
- data/lib/view_component/preview_template_error.rb +6 -0
- data/lib/view_component/previewable.rb +4 -1
- data/lib/view_component/render_component_helper.rb +9 -0
- data/lib/view_component/render_component_to_string_helper.rb +9 -0
- data/lib/view_component/rendering_component_helper.rb +9 -0
- data/lib/view_component/slot.rb +7 -0
- data/lib/view_component/slotable.rb +121 -0
- data/lib/view_component/test_helpers.rb +7 -2
- data/lib/view_component/version.rb +2 -2
- metadata +8 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9e01b3b2899158831379c51c72086d84ca838b829a9638eeaec381aa287fa8c8
         | 
| 4 | 
            +
              data.tar.gz: fab254768f2f88651ed2c89e9e09ef5fa7e50524dad061435e5abd4e7de1a4ae
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: b1ce90df482ab031bf57b808ec1cd0015774d3061733ae4e934d18a8b569e0aa6d22ac71107016ca0ab73f76acb720c7113d21daba2a74a5d313f53ae245e435
         | 
| 7 | 
            +
              data.tar.gz: 96e33d8a65235bf8831ce07aec2f13b5bec2617be30c2a11cca2fa969516d167be4eaf2390a6296db2f1e61f1523115e2fd53aa6af1e39a6d661f85a03baf626
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,50 @@ | |
| 1 1 | 
             
            # master
         | 
| 2 2 |  | 
| 3 | 
            +
            # 2.15.0
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Add support for templates as ViewComponent::Preview examples.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                *Juan Manuel Ramallo
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # 2.14.1
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Allow using `render_inline` in test when the render monkey patch is disabled with `config.view_component.render_monkey_patch_enabled = false` in versions of Rails < 6.1.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                *Clément Joubert*
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * Fix kwargs warnings in slotable.
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                Fixes:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                ```
         | 
| 20 | 
            +
                view_component/lib/view_component/slotable.rb:98: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
         | 
| 21 | 
            +
                view_component/test/app/components/slots_component.rb:18: warning: The called method `initialize' is defined here
         | 
| 22 | 
            +
                ```
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                *Eileen M. Uchitelle*
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            # 2.14.0
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            * Add `config.preview_paths` to support multiple locations of component preview files. Deprecate `config.preview_path`.
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                *Tomas Celizna*
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            * Only print warning about a missing capybara dependency if the `DEBUG` environment variable is set.
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                *Richard Macklin*
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            # 2.13.0
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            * Add the ability to disable the render monkey patch with `config.view_component.render_monkey_patch_enabled`. In versions of Rails < 6.1, add `render_component` and `render_component_to_string` methods which can be used for rendering components instead of `render`.
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                *Johannes Engl*
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            # 2.12.0
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            * Implement Slots as potential successor to Content Areas.
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                *Jens Ljungblad, Brian Bugh, Jon Palmer, Joel Hawksley*
         | 
| 47 | 
            +
             | 
| 3 48 | 
             
            # 2.11.1
         | 
| 4 49 |  | 
| 5 50 | 
             
            * Fix kwarg warnings in Ruby 2.7.
         | 
    
        data/README.md
    CHANGED
    
    | @@ -160,6 +160,133 @@ Returning: | |
| 160 160 | 
             
            </div>
         | 
| 161 161 | 
             
            ```
         | 
| 162 162 |  | 
| 163 | 
            +
            #### Slots (experimental)
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            _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 | 
            +
             | 
| 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.
         | 
| 170 | 
            +
             | 
| 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.
         | 
| 172 | 
            +
             | 
| 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.
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            To learn more about the design of the Slots API, see https://github.com/github/view_component/pull/348.
         | 
| 176 | 
            +
             | 
| 177 | 
            +
            ##### Defining Slots
         | 
| 178 | 
            +
             | 
| 179 | 
            +
            Slots are defined by the `with_slot` macro:
         | 
| 180 | 
            +
             | 
| 181 | 
            +
            `with_slot :header`
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            To define a collection slot, add `collection: true`:
         | 
| 184 | 
            +
             | 
| 185 | 
            +
            `with_slot :row, collection: true`
         | 
| 186 | 
            +
             | 
| 187 | 
            +
            To define a slot with a custom class, pass `class_name`:
         | 
| 188 | 
            +
             | 
| 189 | 
            +
            `with_slot :body, class_name: 'BodySlot`
         | 
| 190 | 
            +
             | 
| 191 | 
            +
            Slot classes should be subclasses of `ViewComponent::Slot`.
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            ##### Example ViewComponent with Slots
         | 
| 194 | 
            +
             | 
| 195 | 
            +
            `# box_component.rb`
         | 
| 196 | 
            +
            ```ruby
         | 
| 197 | 
            +
            class BoxComponent < ViewComponent::Base
         | 
| 198 | 
            +
              include ViewComponent::Slotable
         | 
| 199 | 
            +
             | 
| 200 | 
            +
              with_slot :body, :footer
         | 
| 201 | 
            +
              with_slot :header, class_name: "Header"
         | 
| 202 | 
            +
              with_slot :row, collection: true, class_name: "Row"
         | 
| 203 | 
            +
             | 
| 204 | 
            +
              class Header < ViewComponent::Slot
         | 
| 205 | 
            +
                def initialize(class_names: "")
         | 
| 206 | 
            +
                  @class_names = class_names
         | 
| 207 | 
            +
                end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                def class_names
         | 
| 210 | 
            +
                  "Box-header #{@class_names}"
         | 
| 211 | 
            +
                end
         | 
| 212 | 
            +
              end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
              class Row < ViewComponent::Slot
         | 
| 215 | 
            +
                def initialize(theme: :gray)
         | 
| 216 | 
            +
                  @theme = theme
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                def theme_class_name
         | 
| 220 | 
            +
                  case @theme
         | 
| 221 | 
            +
                  when :gray
         | 
| 222 | 
            +
                    "Box-row--gray"
         | 
| 223 | 
            +
                  when :hover_gray
         | 
| 224 | 
            +
                    "Box-row--hover-gray"
         | 
| 225 | 
            +
                  when :yellow
         | 
| 226 | 
            +
                    "Box-row--yellow"
         | 
| 227 | 
            +
                  when :blue
         | 
| 228 | 
            +
                    "Box-row--blue"
         | 
| 229 | 
            +
                  when :hover_blue
         | 
| 230 | 
            +
                    "Box-row--hover-blue"
         | 
| 231 | 
            +
                  else
         | 
| 232 | 
            +
                    "Box-row--gray"
         | 
| 233 | 
            +
                  end
         | 
| 234 | 
            +
                end
         | 
| 235 | 
            +
              end
         | 
| 236 | 
            +
            end
         | 
| 237 | 
            +
            ```
         | 
| 238 | 
            +
             | 
| 239 | 
            +
            `# box_component.html.erb`
         | 
| 240 | 
            +
            ```erb
         | 
| 241 | 
            +
            <div class="Box">
         | 
| 242 | 
            +
              <% if header %>
         | 
| 243 | 
            +
                <div class="<%= header.class_names %>">
         | 
| 244 | 
            +
                  <%= header.content %>
         | 
| 245 | 
            +
                </div>
         | 
| 246 | 
            +
              <% end %>
         | 
| 247 | 
            +
              <% if body %>
         | 
| 248 | 
            +
                <div class="Box-body">
         | 
| 249 | 
            +
                  <%= body.content %>
         | 
| 250 | 
            +
                </div>
         | 
| 251 | 
            +
              <% end %>
         | 
| 252 | 
            +
              <% if rows.any? %>
         | 
| 253 | 
            +
                <ul>
         | 
| 254 | 
            +
                  <% rows.each do |row| %>
         | 
| 255 | 
            +
                    <li class="Box-row <%= row.theme_class_name %>">
         | 
| 256 | 
            +
                      <%= row.content %>
         | 
| 257 | 
            +
                    </li>
         | 
| 258 | 
            +
                  <% end %>
         | 
| 259 | 
            +
                </ul>
         | 
| 260 | 
            +
              <% end %>
         | 
| 261 | 
            +
              <% if footer %>
         | 
| 262 | 
            +
                <div class="Box-footer">
         | 
| 263 | 
            +
                  <%= footer %>
         | 
| 264 | 
            +
                </div>
         | 
| 265 | 
            +
              <% end %>
         | 
| 266 | 
            +
            </div>
         | 
| 267 | 
            +
            ```
         | 
| 268 | 
            +
             | 
| 269 | 
            +
            `# index.html.erb`
         | 
| 270 | 
            +
            ```erb
         | 
| 271 | 
            +
            <%= render(BoxComponent.new) do |component| %>
         | 
| 272 | 
            +
              <% component.slot(:header, class_names: "my-class-name") do %>
         | 
| 273 | 
            +
                This is my header!
         | 
| 274 | 
            +
              <% end %>
         | 
| 275 | 
            +
              <% component.slot(:body) do %>
         | 
| 276 | 
            +
                This is the body.
         | 
| 277 | 
            +
              <% end %>
         | 
| 278 | 
            +
              <% component.slot(:row) do %>
         | 
| 279 | 
            +
                Row one
         | 
| 280 | 
            +
              <% end %>
         | 
| 281 | 
            +
              <% component.slot(:row, theme: :yellow) do %>
         | 
| 282 | 
            +
                Yellow row
         | 
| 283 | 
            +
              <% end %>
         | 
| 284 | 
            +
              <% component.slot(:footer) do %>
         | 
| 285 | 
            +
                This is the footer.
         | 
| 286 | 
            +
              <% end %>
         | 
| 287 | 
            +
            <% end %>
         | 
| 288 | 
            +
            ```
         | 
| 289 | 
            +
             | 
| 163 290 | 
             
            ### Inline Component
         | 
| 164 291 |  | 
| 165 292 | 
             
            ViewComponents can render without a template file, by defining a `call` method:
         | 
| @@ -537,11 +664,11 @@ class TestComponentPreview < ViewComponent::Preview | |
| 537 664 | 
             
            end
         | 
| 538 665 | 
             
            ```
         | 
| 539 666 |  | 
| 540 | 
            -
            Preview classes live in `test/components/previews`, which can be configured using the ` | 
| 667 | 
            +
            Preview classes live in `test/components/previews`, which can be configured using the `preview_paths` option:
         | 
| 541 668 |  | 
| 542 669 | 
             
            `config/application.rb`
         | 
| 543 670 | 
             
            ```ruby
         | 
| 544 | 
            -
            config.view_component. | 
| 671 | 
            +
            config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
         | 
| 545 672 | 
             
            ```
         | 
| 546 673 |  | 
| 547 674 | 
             
            Previews are served from <http://localhost:3000/rails/view_components> by default. To use a different endpoint, set the `preview_route` option:
         | 
| @@ -553,6 +680,57 @@ config.view_component.preview_route = "/previews" | |
| 553 680 |  | 
| 554 681 | 
             
            This example will make the previews available from <http://localhost:3000/previews>.
         | 
| 555 682 |  | 
| 683 | 
            +
            #### Preview templates
         | 
| 684 | 
            +
             | 
| 685 | 
            +
            Given a preview `test/components/previews/cell_component_preview.rb`, template files can be defined at `test/components/previews/cell_component_preview/`:
         | 
| 686 | 
            +
             | 
| 687 | 
            +
            `test/components/previews/cell_component_preview.rb`
         | 
| 688 | 
            +
            ```ruby
         | 
| 689 | 
            +
            class CellComponentPreview < ViewComponent::Preview
         | 
| 690 | 
            +
              def default
         | 
| 691 | 
            +
              end
         | 
| 692 | 
            +
            end
         | 
| 693 | 
            +
            ```
         | 
| 694 | 
            +
             | 
| 695 | 
            +
            `test/components/previews/cell_component_preview/default.html.erb`
         | 
| 696 | 
            +
            ```erb
         | 
| 697 | 
            +
            <table class="table">
         | 
| 698 | 
            +
              <tbody>
         | 
| 699 | 
            +
                <tr>
         | 
| 700 | 
            +
                  <%= render CellComponent.new %>
         | 
| 701 | 
            +
                </tr>
         | 
| 702 | 
            +
              </tbody>
         | 
| 703 | 
            +
            </div>
         | 
| 704 | 
            +
            ```
         | 
| 705 | 
            +
             | 
| 706 | 
            +
            To use a different location for preview templates, pass the `template` argument:
         | 
| 707 | 
            +
            (the path should be relative to `config.view_component.preview_path`):
         | 
| 708 | 
            +
             | 
| 709 | 
            +
            `test/components/previews/cell_component_preview.rb`
         | 
| 710 | 
            +
            ```ruby
         | 
| 711 | 
            +
            class CellComponentPreview < ViewComponent::Preview
         | 
| 712 | 
            +
              def default
         | 
| 713 | 
            +
                render_with_template(template: 'custom_cell_component_preview/my_preview_template')
         | 
| 714 | 
            +
              end
         | 
| 715 | 
            +
            end
         | 
| 716 | 
            +
            ```
         | 
| 717 | 
            +
             | 
| 718 | 
            +
            Values from `params` can be accessed through `locals`:
         | 
| 719 | 
            +
             | 
| 720 | 
            +
            `test/components/previews/cell_component_preview.rb`
         | 
| 721 | 
            +
            ```ruby
         | 
| 722 | 
            +
            class CellComponentPreview < ViewComponent::Preview
         | 
| 723 | 
            +
              def default(title: "Default title", subtitle: "A subtitle")
         | 
| 724 | 
            +
                render_with_template(locals: {
         | 
| 725 | 
            +
                  title: title,
         | 
| 726 | 
            +
                  subtitle: subtitle
         | 
| 727 | 
            +
                })
         | 
| 728 | 
            +
              end
         | 
| 729 | 
            +
            end
         | 
| 730 | 
            +
            ```
         | 
| 731 | 
            +
             | 
| 732 | 
            +
            Which enables passing in a value with <http://localhost:3000/rails/components/cell_component/default?title=Custom+title&subtitle=Another+subtitle>.
         | 
| 733 | 
            +
             | 
| 556 734 | 
             
            #### Configuring TestController
         | 
| 557 735 |  | 
| 558 736 | 
             
            Component tests and previews assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
         | 
| @@ -581,7 +759,19 @@ To use component previews: | |
| 581 759 |  | 
| 582 760 | 
             
            `config/application.rb`
         | 
| 583 761 | 
             
            ```ruby
         | 
| 584 | 
            -
            config.view_component. | 
| 762 | 
            +
            config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
         | 
| 763 | 
            +
            ```
         | 
| 764 | 
            +
             | 
| 765 | 
            +
            ### Disabling the render monkey patch (Rails < 6.1)
         | 
| 766 | 
            +
             | 
| 767 | 
            +
            In order to [avoid conflicts](https://github.com/github/view_component/issues/288) between ViewComponent and other gems that also monkey patch the `render` method, it is possible to configure ViewComponent to not include the render monkey patch:
         | 
| 768 | 
            +
             | 
| 769 | 
            +
            `config.view_component.render_monkey_patch_enabled = false # defaults to true`
         | 
| 770 | 
            +
             | 
| 771 | 
            +
            With the monkey patch disabled, use `render_component` (or  `render_component_to_string`) instead:
         | 
| 772 | 
            +
             | 
| 773 | 
            +
            ```
         | 
| 774 | 
            +
            <%= render_component Component.new(message: "bar") %>
         | 
| 585 775 | 
             
            ```
         | 
| 586 776 |  | 
| 587 777 | 
             
            ### Sidecar assets (experimental)
         | 
| @@ -731,7 +921,8 @@ ViewComponent is far from a novel idea! Popular implementations of view componen | |
| 731 921 | 
             
            ## Resources
         | 
| 732 922 |  | 
| 733 923 | 
             
            - [Encapsulating Views, RailsConf 2020](https://youtu.be/YVYRus_2KZM)
         | 
| 734 | 
            -
            - [Rethinking the View Layer with Components  | 
| 924 | 
            +
            - [Rethinking the View Layer with Components, Ruby Rogues Podcast](https://devchat.tv/ruby-rogues/rr-461-rethinking-the-view-layer-with-components-with-joel-hawksley/)
         | 
| 925 | 
            +
            - [ViewComponents in Action with Andrew Mason, Ruby on Rails Podcast](https://5by5.tv/rubyonrails/320)
         | 
| 735 926 | 
             
            - [ViewComponent at GitHub with Joel Hawksley](https://the-ruby-blend.fireside.fm/9)
         | 
| 736 927 | 
             
            - [Components, HAML vs ERB, and Design Systems](https://the-ruby-blend.fireside.fm/4)
         | 
| 737 928 | 
             
            - [Choosing the Right Tech Stack with Dave Paola](https://5by5.tv/rubyonrails/307)
         | 
| @@ -784,10 +975,15 @@ ViewComponent is built by: | |
| 784 975 | 
             
            |@simonrand|@fugufish|@cover|@franks921|@fsateler|
         | 
| 785 976 | 
             
            |Dublin, Ireland|Salt Lake City, Utah|Barcelona|South Africa|Chile|
         | 
| 786 977 |  | 
| 787 | 
            -
            |<img src="https://avatars.githubusercontent.com/maxbeizer?s=256" alt="maxbeizer" width="128" />|<img src="https://avatars.githubusercontent.com/franco?s=256" alt="franco" width="128" />|<img src="https://avatars.githubusercontent.com/tbroad-ramsey?s=256" alt="tbroad-ramsey" width="128" />|
         | 
| 788 | 
            -
             | 
| 789 | 
            -
            |@maxbeizer|@franco|@tbroad-ramsey|
         | 
| 790 | 
            -
            |Nashville, TN|Switzerland|Spring Hill, TN|
         | 
| 978 | 
            +
            |<img src="https://avatars.githubusercontent.com/maxbeizer?s=256" alt="maxbeizer" width="128" />|<img src="https://avatars.githubusercontent.com/franco?s=256" alt="franco" width="128" />|<img src="https://avatars.githubusercontent.com/tbroad-ramsey?s=256" alt="tbroad-ramsey" width="128" />|<img src="https://avatars.githubusercontent.com/jensljungblad?s=256" alt="jensljungblad" width="128" />|<img src="https://avatars.githubusercontent.com/bbugh?s=256" alt="bbugh" width="128" />|
         | 
| 979 | 
            +
            |:---:|:---:|:---:|:---:|:---:|
         | 
| 980 | 
            +
            |@maxbeizer|@franco|@tbroad-ramsey|@jensljungblad|@bbugh|
         | 
| 981 | 
            +
            |Nashville, TN|Switzerland|Spring Hill, TN|New York, NY|Austin, TX|
         | 
| 982 | 
            +
             | 
| 983 | 
            +
            |<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" />|
         | 
| 984 | 
            +
            |:---:|:---:|
         | 
| 985 | 
            +
            |@johannesengl|@czj|
         | 
| 986 | 
            +
            |Berlin, Germany|Paris, France|
         | 
| 791 987 |  | 
| 792 988 | 
             
            ## License
         | 
| 793 989 |  | 
| @@ -24,11 +24,16 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc: | |
| 24 24 | 
             
                  render "view_components/previews"
         | 
| 25 25 | 
             
                else
         | 
| 26 26 | 
             
                  prepend_application_view_paths
         | 
| 27 | 
            +
                  prepend_preview_examples_view_path
         | 
| 27 28 | 
             
                  @example_name = File.basename(params[:path])
         | 
| 28 29 | 
             
                  @render_args = @preview.render_args(@example_name, params: params.permit!)
         | 
| 29 30 | 
             
                  layout = @render_args[:layout]
         | 
| 30 | 
            -
                   | 
| 31 | 
            -
                   | 
| 31 | 
            +
                  template = @render_args[:template]
         | 
| 32 | 
            +
                  locals = @render_args[:locals]
         | 
| 33 | 
            +
                  opts = {}
         | 
| 34 | 
            +
                  opts[:layout] = layout if layout.present?
         | 
| 35 | 
            +
                  opts[:locals] = locals if locals.present?
         | 
| 36 | 
            +
                  render template, opts # rubocop:disable GitHub/RailsControllerRenderLiteral
         | 
| 32 37 | 
             
                end
         | 
| 33 38 | 
             
              end
         | 
| 34 39 |  | 
| @@ -59,4 +64,8 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc: | |
| 59 64 | 
             
              def prepend_application_view_paths
         | 
| 60 65 | 
             
                prepend_view_path Rails.root.join("app/views") if defined?(Rails.root)
         | 
| 61 66 | 
             
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              def prepend_preview_examples_view_path
         | 
| 69 | 
            +
                prepend_view_path(ViewComponent::Base.preview_paths)
         | 
| 70 | 
            +
              end
         | 
| 62 71 | 
             
            end
         | 
    
        data/lib/view_component.rb
    CHANGED
    
    
    
        data/lib/view_component/base.rb
    CHANGED
    
    | @@ -5,6 +5,7 @@ require "active_support/configurable" | |
| 5 5 | 
             
            require "view_component/collection"
         | 
| 6 6 | 
             
            require "view_component/compile_cache"
         | 
| 7 7 | 
             
            require "view_component/previewable"
         | 
| 8 | 
            +
            require "view_component/slotable"
         | 
| 8 9 |  | 
| 9 10 | 
             
            module ViewComponent
         | 
| 10 11 | 
             
              class Base < ActionView::Base
         | 
| @@ -17,6 +18,10 @@ module ViewComponent | |
| 17 18 | 
             
                class_attribute :content_areas
         | 
| 18 19 | 
             
                self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
         | 
| 19 20 |  | 
| 21 | 
            +
                # Hash of registered Slots
         | 
| 22 | 
            +
                class_attribute :slots
         | 
| 23 | 
            +
                self.slots = {}
         | 
| 24 | 
            +
             | 
| 20 25 | 
             
                # Entrypoint for rendering components.
         | 
| 21 26 | 
             
                #
         | 
| 22 27 | 
             
                # view_context: ActionView context from calling view
         | 
| @@ -157,6 +162,9 @@ module ViewComponent | |
| 157 162 | 
             
                mattr_accessor :test_controller
         | 
| 158 163 | 
             
                @@test_controller = "ApplicationController"
         | 
| 159 164 |  | 
| 165 | 
            +
                # Configure if render monkey patches should be included or not in Rails <6.1.
         | 
| 166 | 
            +
                mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
         | 
| 167 | 
            +
             | 
| 160 168 | 
             
                class << self
         | 
| 161 169 | 
             
                  attr_accessor :source_location
         | 
| 162 170 |  | 
| @@ -181,6 +189,10 @@ module ViewComponent | |
| 181 189 | 
             
                    # has been re-defined by the consuming application, likely in ApplicationComponent.
         | 
| 182 190 | 
             
                    child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
         | 
| 183 191 |  | 
| 192 | 
            +
                    # Clone slot configuration into child class
         | 
| 193 | 
            +
                    # see #test_slots_pollution
         | 
| 194 | 
            +
                    child.slots = self.slots.clone
         | 
| 195 | 
            +
             | 
| 184 196 | 
             
                    super
         | 
| 185 197 | 
             
                  end
         | 
| 186 198 |  | 
| @@ -6,15 +6,24 @@ require "view_component" | |
| 6 6 | 
             
            module ViewComponent
         | 
| 7 7 | 
             
              class Engine < Rails::Engine # :nodoc:
         | 
| 8 8 | 
             
                config.view_component = ActiveSupport::OrderedOptions.new
         | 
| 9 | 
            +
                config.view_component.preview_paths ||= []
         | 
| 9 10 |  | 
| 10 11 | 
             
                initializer "view_component.set_configs" do |app|
         | 
| 11 12 | 
             
                  options = app.config.view_component
         | 
| 12 13 |  | 
| 14 | 
            +
                  options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
         | 
| 13 15 | 
             
                  options.show_previews = Rails.env.development? if options.show_previews.nil?
         | 
| 14 16 | 
             
                  options.preview_route ||= ViewComponent::Base.preview_route
         | 
| 15 17 |  | 
| 16 18 | 
             
                  if options.show_previews
         | 
| 17 | 
            -
                    options. | 
| 19 | 
            +
                    options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    if options.preview_path.present?
         | 
| 22 | 
            +
                      ActiveSupport::Deprecation.warn(
         | 
| 23 | 
            +
                        "`preview_path` will be removed in v3.0.0. Use `preview_paths` instead."
         | 
| 24 | 
            +
                      )
         | 
| 25 | 
            +
                      options.preview_paths << options.preview_path
         | 
| 26 | 
            +
                    end
         | 
| 18 27 | 
             
                  end
         | 
| 19 28 |  | 
| 20 29 | 
             
                  ActiveSupport.on_load(:view_component) do
         | 
| @@ -42,21 +51,35 @@ module ViewComponent | |
| 42 51 | 
             
                  end
         | 
| 43 52 | 
             
                end
         | 
| 44 53 |  | 
| 45 | 
            -
                initializer "view_component.monkey_patch_render" do
         | 
| 54 | 
            +
                initializer "view_component.monkey_patch_render" do |app|
         | 
| 55 | 
            +
                  next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled
         | 
| 56 | 
            +
             | 
| 46 57 | 
             
                  ActiveSupport.on_load(:action_view) do
         | 
| 47 | 
            -
                     | 
| 48 | 
            -
             | 
| 49 | 
            -
                      ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
         | 
| 50 | 
            -
                    end
         | 
| 58 | 
            +
                    require "view_component/render_monkey_patch"
         | 
| 59 | 
            +
                    ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
         | 
| 51 60 | 
             
                  end
         | 
| 52 61 |  | 
| 53 62 | 
             
                  ActiveSupport.on_load(:action_controller) do
         | 
| 54 | 
            -
                     | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 63 | 
            +
                    require "view_component/rendering_monkey_patch"
         | 
| 64 | 
            +
                    require "view_component/render_to_string_monkey_patch"
         | 
| 65 | 
            +
                    ActionController::Base.prepend ViewComponent::RenderingMonkeyPatch
         | 
| 66 | 
            +
                    ActionController::Base.prepend ViewComponent::RenderToStringMonkeyPatch
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                initializer "view_component.include_render_component" do |app|
         | 
| 71 | 
            +
                  next if Rails.version.to_f >= 6.1
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  ActiveSupport.on_load(:action_view) do
         | 
| 74 | 
            +
                    require "view_component/render_component_helper"
         | 
| 75 | 
            +
                    ActionView::Base.include ViewComponent::RenderComponentHelper
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  ActiveSupport.on_load(:action_controller) do
         | 
| 79 | 
            +
                    require "view_component/rendering_component_helper"
         | 
| 80 | 
            +
                    require "view_component/render_component_to_string_helper"
         | 
| 81 | 
            +
                    ActionController::Base.include ViewComponent::RenderingComponentHelper
         | 
| 82 | 
            +
                    ActionController::Base.include ViewComponent::RenderComponentToStringHelper
         | 
| 60 83 | 
             
                  end
         | 
| 61 84 | 
             
                end
         | 
| 62 85 |  | 
| @@ -8,7 +8,20 @@ module ViewComponent # :nodoc: | |
| 8 8 | 
             
                extend ActiveSupport::DescendantsTracker
         | 
| 9 9 |  | 
| 10 10 | 
             
                def render(component, **args, &block)
         | 
| 11 | 
            -
                  { | 
| 11 | 
            +
                  {
         | 
| 12 | 
            +
                    args: args,
         | 
| 13 | 
            +
                    block: block,
         | 
| 14 | 
            +
                    component: component,
         | 
| 15 | 
            +
                    locals: {},
         | 
| 16 | 
            +
                    template: "view_components/preview",
         | 
| 17 | 
            +
                  }
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def render_with_template(template: nil, locals: {})
         | 
| 21 | 
            +
                  {
         | 
| 22 | 
            +
                    template: template,
         | 
| 23 | 
            +
                    locals: locals
         | 
| 24 | 
            +
                  }
         | 
| 12 25 | 
             
                end
         | 
| 13 26 |  | 
| 14 27 | 
             
                class << self
         | 
| @@ -23,6 +36,8 @@ module ViewComponent # :nodoc: | |
| 23 36 | 
             
                    example_params_names = instance_method(example).parameters.map(&:last)
         | 
| 24 37 | 
             
                    provided_params = params.slice(*example_params_names).to_h.symbolize_keys
         | 
| 25 38 | 
             
                    result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)
         | 
| 39 | 
            +
                    result ||= {}
         | 
| 40 | 
            +
                    result[:template] = preview_example_template_path(example) if result[:template].nil?
         | 
| 26 41 | 
             
                    @layout = nil unless defined?(@layout)
         | 
| 27 42 | 
             
                    result.merge(layout: @layout)
         | 
| 28 43 | 
             
                  end
         | 
| @@ -62,16 +77,30 @@ module ViewComponent # :nodoc: | |
| 62 77 | 
             
                    @layout = layout_name
         | 
| 63 78 | 
             
                  end
         | 
| 64 79 |  | 
| 80 | 
            +
                  # Returns the relative path (from preview_path) to the preview example template if the template exists
         | 
| 81 | 
            +
                  def preview_example_template_path(example)
         | 
| 82 | 
            +
                    preview_path = Array(preview_paths).detect do |preview_path|
         | 
| 83 | 
            +
                      Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    if preview_path.nil?
         | 
| 87 | 
            +
                      raise PreviewTemplateError, "preview template for example #{example} does not exist"
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
         | 
| 91 | 
            +
                    Pathname.new(path).relative_path_from(Pathname.new(preview_path)).to_s
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 65 94 | 
             
                  private
         | 
| 66 95 |  | 
| 67 96 | 
             
                  def load_previews
         | 
| 68 | 
            -
                     | 
| 97 | 
            +
                    Array(preview_paths).each do |preview_path|
         | 
| 69 98 | 
             
                      Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
         | 
| 70 99 | 
             
                    end
         | 
| 71 100 | 
             
                  end
         | 
| 72 101 |  | 
| 73 | 
            -
                  def  | 
| 74 | 
            -
                    Base. | 
| 102 | 
            +
                  def preview_paths
         | 
| 103 | 
            +
                    Base.preview_paths
         | 
| 75 104 | 
             
                  end
         | 
| 76 105 |  | 
| 77 106 | 
             
                  def show_previews
         | 
| @@ -9,8 +9,11 @@ module ViewComponent # :nodoc: | |
| 9 9 | 
             
                included do
         | 
| 10 10 | 
             
                  # Set the location of component previews through app configuration:
         | 
| 11 11 | 
             
                  #
         | 
| 12 | 
            -
                  #     config.view_component. | 
| 12 | 
            +
                  #     config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
         | 
| 13 13 | 
             
                  #
         | 
| 14 | 
            +
                  mattr_accessor :preview_paths, instance_writer: false
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # TODO: deprecated, remove in v3.0.0
         | 
| 14 17 | 
             
                  mattr_accessor :preview_path, instance_writer: false
         | 
| 15 18 |  | 
| 16 19 | 
             
                  # Enable or disable component previews through app configuration:
         | 
| @@ -0,0 +1,121 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "active_support/concern"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require "view_component/slot"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module ViewComponent
         | 
| 8 | 
            +
              module Slotable
         | 
| 9 | 
            +
                extend ActiveSupport::Concern
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                class_methods do
         | 
| 12 | 
            +
                  # support initializing slots as:
         | 
| 13 | 
            +
                  #
         | 
| 14 | 
            +
                  # with_slot(
         | 
| 15 | 
            +
                  #   :header,
         | 
| 16 | 
            +
                  #   collection: true|false,
         | 
| 17 | 
            +
                  #   class_name: "Header" # class name string, used to instantiate Slot
         | 
| 18 | 
            +
                  # )
         | 
| 19 | 
            +
                  def with_slot(*slot_names, collection: false, class_name: nil)
         | 
| 20 | 
            +
                    slot_names.each do |slot_name|
         | 
| 21 | 
            +
                      # Ensure slot_name is not already declared
         | 
| 22 | 
            +
                      if self.slots.key?(slot_name)
         | 
| 23 | 
            +
                        raise ArgumentError.new("#{slot_name} slot declared multiple times")
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      # Ensure slot name is not :content
         | 
| 27 | 
            +
                      if slot_name == :content
         | 
| 28 | 
            +
                        raise ArgumentError.new ":content is a reserved slot name. Please use another name, such as ':body'"
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      # Set the name of the method used to access the Slot(s)
         | 
| 32 | 
            +
                      accessor_name =
         | 
| 33 | 
            +
                        if collection
         | 
| 34 | 
            +
                          # If Slot is a collection, set the accessor
         | 
| 35 | 
            +
                          # name to the pluralized form of the slot name
         | 
| 36 | 
            +
                          # For example: :tab => :tabs
         | 
| 37 | 
            +
                          ActiveSupport::Inflector.pluralize(slot_name)
         | 
| 38 | 
            +
                        else
         | 
| 39 | 
            +
                          slot_name
         | 
| 40 | 
            +
                        end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      instance_variable_name = "@#{accessor_name}"
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      # If the slot is a collection, define an accesor that defaults to an empty array
         | 
| 45 | 
            +
                      if collection
         | 
| 46 | 
            +
                        class_eval <<-RUBY
         | 
| 47 | 
            +
                          def #{accessor_name}
         | 
| 48 | 
            +
                            #{instance_variable_name} ||= []
         | 
| 49 | 
            +
                          end
         | 
| 50 | 
            +
                        RUBY
         | 
| 51 | 
            +
                      else
         | 
| 52 | 
            +
                        attr_reader accessor_name
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      # Default class_name to ViewComponent::Slot
         | 
| 56 | 
            +
                      class_name = "ViewComponent::Slot" unless class_name.present?
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                      # Register the slot on the component
         | 
| 59 | 
            +
                      self.slots[slot_name] = {
         | 
| 60 | 
            +
                        class_name: class_name,
         | 
| 61 | 
            +
                        instance_variable_name: instance_variable_name,
         | 
| 62 | 
            +
                        collection: collection
         | 
| 63 | 
            +
                      }
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # Build a Slot instance on a component,
         | 
| 69 | 
            +
                # exposing it for use inside the
         | 
| 70 | 
            +
                # component template.
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # slot: Name of Slot, in symbol form
         | 
| 73 | 
            +
                # **args: Arguments to be passed to Slot initializer
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                # For example:
         | 
| 76 | 
            +
                # <%= render(SlotsComponent.new) do |component| %>
         | 
| 77 | 
            +
                #   <% component.slot(:footer, class_names: "footer-class") do %>
         | 
| 78 | 
            +
                #     <p>This is my footer!</p>
         | 
| 79 | 
            +
                #   <% end %>
         | 
| 80 | 
            +
                # <% end %>
         | 
| 81 | 
            +
                #
         | 
| 82 | 
            +
                def slot(slot_name, **args, &block)
         | 
| 83 | 
            +
                  # Raise ArgumentError if `slot` does not exist
         | 
| 84 | 
            +
                  unless slots.keys.include?(slot_name)
         | 
| 85 | 
            +
                    raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'"
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  slot = slots[slot_name]
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  # The class name of the Slot, such as Header
         | 
| 91 | 
            +
                  slot_class = self.class.const_get(slot[:class_name])
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  unless slot_class <= ViewComponent::Slot
         | 
| 94 | 
            +
                    raise ArgumentError.new "#{slot[:class_name]} must inherit from ViewComponent::Slot"
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  # Instantiate Slot class, accommodating Slots that don't accept arguments
         | 
| 98 | 
            +
                  slot_instance = args.present? ? slot_class.new(**args) : slot_class.new
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  # Capture block and assign to slot_instance#content
         | 
| 101 | 
            +
                  slot_instance.content = view_context.capture(&block) if block_given?
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  if slot[:collection]
         | 
| 104 | 
            +
                    # Initialize instance variable as an empty array
         | 
| 105 | 
            +
                    # if slot is a collection and has yet to be initialized
         | 
| 106 | 
            +
                    unless instance_variable_defined?(slot[:instance_variable_name])
         | 
| 107 | 
            +
                      instance_variable_set(slot[:instance_variable_name], [])
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                    # Append Slot instance to collection accessor Array
         | 
| 111 | 
            +
                    instance_variable_get(slot[:instance_variable_name]) << slot_instance
         | 
| 112 | 
            +
                  else
         | 
| 113 | 
            +
                     # Assign the Slot instance to the slot accessor
         | 
| 114 | 
            +
                    instance_variable_set(slot[:instance_variable_name], slot_instance)
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  # Return nil, as this method should not output anything to the view itself.
         | 
| 118 | 
            +
                  nil
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
              end
         | 
| 121 | 
            +
            end
         | 
| @@ -14,13 +14,18 @@ module ViewComponent | |
| 14 14 | 
             
                    assert_no_selector("body")
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 | 
             
                rescue LoadError
         | 
| 17 | 
            -
                  warn "WARNING in `ViewComponent::TestHelpers`: You must add `capybara` to your Gemfile to use Capybara assertions."
         | 
| 17 | 
            +
                  warn "WARNING in `ViewComponent::TestHelpers`: You must add `capybara` to your Gemfile to use Capybara assertions." if ENV["DEBUG"]
         | 
| 18 18 | 
             
                end
         | 
| 19 19 |  | 
| 20 20 | 
             
                attr_reader :rendered_component
         | 
| 21 21 |  | 
| 22 22 | 
             
                def render_inline(component, **args, &block)
         | 
| 23 | 
            -
                  @rendered_component = | 
| 23 | 
            +
                  @rendered_component =
         | 
| 24 | 
            +
                    if Rails.version.to_f >= 6.1
         | 
| 25 | 
            +
                      controller.view_context.render(component, args, &block)
         | 
| 26 | 
            +
                    else
         | 
| 27 | 
            +
                      controller.view_context.render_component(component, &block)
         | 
| 28 | 
            +
                    end
         | 
| 24 29 |  | 
| 25 30 | 
             
                  Nokogiri::HTML.fragment(@rendered_component)
         | 
| 26 31 | 
             
                end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: view_component
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.15.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - GitHub Open Source
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2020- | 
| 11 | 
            +
            date: 2020-07-13 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activesupport
         | 
| @@ -203,10 +203,16 @@ files: | |
| 203 203 | 
             
            - lib/view_component/compile_cache.rb
         | 
| 204 204 | 
             
            - lib/view_component/engine.rb
         | 
| 205 205 | 
             
            - lib/view_component/preview.rb
         | 
| 206 | 
            +
            - lib/view_component/preview_template_error.rb
         | 
| 206 207 | 
             
            - lib/view_component/previewable.rb
         | 
| 208 | 
            +
            - lib/view_component/render_component_helper.rb
         | 
| 209 | 
            +
            - lib/view_component/render_component_to_string_helper.rb
         | 
| 207 210 | 
             
            - lib/view_component/render_monkey_patch.rb
         | 
| 208 211 | 
             
            - lib/view_component/render_to_string_monkey_patch.rb
         | 
| 212 | 
            +
            - lib/view_component/rendering_component_helper.rb
         | 
| 209 213 | 
             
            - lib/view_component/rendering_monkey_patch.rb
         | 
| 214 | 
            +
            - lib/view_component/slot.rb
         | 
| 215 | 
            +
            - lib/view_component/slotable.rb
         | 
| 210 216 | 
             
            - lib/view_component/template_error.rb
         | 
| 211 217 | 
             
            - lib/view_component/test_case.rb
         | 
| 212 218 | 
             
            - lib/view_component/test_helpers.rb
         |