patbenatar / rbexy

A Ruby template language and component framework inspired by JSX and React
MIT License
34 stars 5 forks source link

Upstream ApplicationComponent niceties from app to Rbexy #106

Open patbenatar opened 2 months ago

patbenatar commented 2 months ago

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
patbenatar commented 2 months ago

@zacheryph curious to get your thoughts on the above and what (if any) might make sense to upstream into this project

zacheryph commented 1 month ago

@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.

zacheryph commented 1 month ago

@patbenatar

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?