Upstream ApplicationComponent niceties from app to Rbexy
Opened this issue · 3 comments
In my main work project, we've developed an ApplicationComponent
base class that has a handful of features on top of Rbexy::Component
. They're more opinionated and provide more of a framework, so they should perhaps be opt-in but that's up for discussion.
Some of the things we could upstream:
Props
class ThingComponent < Rbexy::Component
prop :something, default: "value1", options: ["value1", "value2"]
end
Which can then be called as something
in the template or component class.
And are used by the caller like so:
<Thing something="value1" />
Root props
By default we delegate any given props that aren't explicitly consumed by the component to the root element of the component's template. This makes it convenient to treat component elements like a superset of HTML elements.
For example:
class HeaderComponent < Rbexy::Component
prop :title
end
With a template:
<div>
<h1>{title}</h1>
{content}
</div>
Used like so:
<Header title="Hello orld" class="my-class">
Some content here
</Header>
Would result in:
<div class="my-class">
<h1>Hello World</h1>
Some content here
</div>
This of course requires that the component template has a single root element just like React requires.
CSS Modules
We use PostCSS in our Webpack/Shakapacker setup to preprocess .scss
files in app/components/
and app/views/
, generating fully qualified names for each class in those files to keep classes local to components/views and avoid global namespace pollution.
// app/components/my_component.scss
.container {
background: blue;
}
.headline {
font-weight: bold;
}
Results in my-component--container
and my-component--headline
being output to the final asset bundle.
Our rbexy config setup will automatically namespace classes used in class=...
attributes to match the compiled CSS, so you can just use them like so:
# app/components/my_component.rbx
<div class="container">
<h1 class="headline">Hello world</h1>
<h2 class={true ? "headline" : "not-headline"}>Welcome</h2>
</div>
This naming convention also applies to Stimulus controllers.
Hotwire integration
We have some niceties for wiring up a component with a Stimulus controller:
<div {**sjs_controller(values: {...})}>
<button data-action={sjs_name("#onClick")}>Click me</button>
<div {**sjs_target("content")}>Content container<div>
</div>
And some base components for working with Turbo frames and streams.
Atomic Design
We implement a framework for organizing components inspired by Atomic Design:
app/components/
atoms/
molecules/
organisms/
With component classes, templates, CSS, and JS colocated:
app/components/atoms/
button_component.rb
button_component.rbx
button_component.scss
button_controller.js
@zacheryph curious to get your thoughts on the above and what (if any) might make sense to upstream into this project
@patbenatar woops, I missed the notification for this.
root props
I like it. naming be damned, but I was actually thinking about an attribute
method to... do this exact thing. (My idea was to expand this to allow {attributes :prop, :names}
at the top of a .rbx
file to easily add attributes without having to implement the component ruby class.
CSS modules
This definitely seems like something that you'd want people to be able to enable.
Hotwire Integration
I am only lately getting to learn Hotwire, the way stimulus handles data-
tags kind of drives me a little nuts (i find the inconsistency between data-[...]
vs data-[identifier]-[...]
to be a little crazy but meh... This I feel probably needs a few iterations before settling on sjs_
methods? I did think of having some core [rails] components that might be friendly. Ala <Turbo.Frame ...>
granted this doesn't help the stimulus case at all.
In regards to your atomic design, I was going to open a ticket to ask what you think about allowing web component'esque referencing to components. an alternative to the namespace lookup could be, using your atomic example, allow tags like <molecule-form-input>
and looking the component up based on that. This does get a little hairy in that it would probably want to require components be within a namespace, and how do you easily resolve the above to say Molecule::FormInput
vs Molecule::Form::Input
?