View Component wrapping
TL;DR: If you want to use the auto-generated Stimulus controllers and encapsulated styling for your View Components, you need to:
- Call
enable_wrapper
within yourcomponent.rb
class- Have a single top-level element in your view (if not, a top-level
<div>
will be created automatically)- Put your top-level styling within a
._base {}
block in yourcomponent.scss
The Platform team recently released changes to teamshares-rails
that allowed almost all the
assets for a component to live in the same folder: the Ruby class, Slim template view, Stimulus controller,
preview, and stylesheet. In order to do that, we were previously wrapping every component in a
<ts-wrapper>
tag, which served as a top-level wrapper element for the Stimulus controller
and a generated CSS class that would encapsulate the component’s style rules. However, this wrapper element
caused some problems in terms of styling, since it added a new level of hierarchy to the DOM. (More details
here.)
Our new pattern (as of late January, 2024) is twofold:
1. Opt-in wrapping via enable_wrapper
View Components no longer get wrapped by default. Wrapping is now opt-in via the
enable_wrapper
class method, called within the component’s class definition:
class SharedUI::Demo::Component < SharedUI::ApplicationComponent enable_wrapper def initialize(title:) super @title = title end end
If you don’t include enable_wrapper
, the component will not receive the auto-generated Stimulus
controller and CSS class from their folder. We plan to eventually enable this for all components again,
making it opt-out rather than opt-in, but this is the intermediate step.
2. Automatic application of styles and controller
If you do enable the wrapper, the component will get wrapped with the
<ts-wrapper>
tag, which will have the generated controller and scoped CSS class applied
to it. A component with this code:
component.html.slim
.bar data-controller="some-controller" .baz Some text
component.rb
class Foo::Component < ApplicationComponent enable_wrapper def some_method ... end end
Will yield the following raw HTML:
<ts-wrapper class="c-foo" data-controller="components--foo"> <div class="bar" data-controller="some-controller"> <div class="baz">Some text content</div> </div> </ts-wrapper>