Not-so-ugly styles code in your Rails app
Tags: rails, viewcomponent, stimulujs, ruby
Published: 2023-05-28 12:25:52

Let's say, u already normally human and didn't use tailwindcss for your Rails app. Well, I just want to show you how I uses a feature from ruby-3.2 introduced by @zverok. It names Data and I use it together with ViewComponent and Rux to have a little less ugly code.

Gemfile:
```
group :development, :test do
  ...
  gem 'view_component', '~> 2.82'
  gem 'rux-rails', '~> 1.2', '>= 1.2.2'
  gem 'birdel', '~> 3.1'
end
```

1. Generate new component by ViewComponent or by Birdel

```
$ birdel gcom Ui::Mix::BarComponent

# app/
# ├─ components/
# │  ├─ ui/
# │  │  ├─ mix/
# │  │  │  ├─ bar_component/
# │  │  │  │  ├─ bar_component.rb
# │  │  │  │  ├─ bar_component.js
# │  │  │  │  ├─ bar_component.css
# │  │  │  │  ├─ bar_component.html.erb
# │  │  │  │  ├─ bar_component_controller.js
# │  │  │  │  └─ bar_component_actor.js
```

2. remove your `bar_component.html.erb` file

3. Write your styles into `bar_component.css`
4. Rename `bar_component.rb` file to `bar_component.rux`

```ruby
#app/components/ui/mix/bar_component/bar_component.rux
class Ui::Mix::BarComponent::BarComponent < ViewComponent::Base
  Styles = Data.define(
    :nav_bar,
    :nav_item,
    :btn,
    :btn_active,
    :nav_item__logo
  )

  attr_reader :styles
  def initialize()
  end

  def styles
    @styles ||= Styles.new(
      nav_bar:        'nav-bar',
      nav_item:       'nav-item',
      btn:            'nav-item-btn',
      btn_active:     'nav-item-btn--active',
      nav_item__logo: 'nav-item-logo'
    )
  end

  def call
    <div class={css_class} data-controller={css_class}>
      <div class={styles.nav_bar}>
        <div class="#{styles.nav_item} #{styles.nav_item__logo}">
        </div>
        <div class={styles.nav_item}>
          <div class={styles.btn} data-action="click->#{css_class}#handleLeftBtn">Left</div>
          <div class="#{styles.btn} #{styles.btn_active}">Center</div>
          <div class={styles.btn}>Right</div>
        </div>
        <div class={styles.nav_item}>
        </div>
      </div>
    </div>
  end

  def css_class
    @css_class ||= "ui--mix--blabla..."
  end
end
```
**Explanation:**

In this part we define a custom Data class named Styles with fields, which will be used as the names of classes from our style file:
```ruby
  Styles = Data.define(
    :nav_bar,
    :nav_item,
    :btn,
    :btn_active,
    :nav_item__logo
  )
```
And here we just set a value for each field of our Styles data object:
```ruby
  def styles
    @styles ||= Styles.new(
      nav_bar:        'nav-bar',
      nav_item:       'nav-item',
      btn:            'nav-item-btn',
      btn_active:     'nav-item-btn--active',
      nav_item__logo: 'nav-item-logo'
    )
  end
```

Great now we can use that `@styles` object inside `#call` method which allows us to write the same to JSX-styled syntax code. Like:
```
<div class={styles.nav_item}>
```
Example rendered HTML:
```html
<div class="nav-bar">
  <div class="nav-item nav-item-logo">
  </div>
  <div class="nav-item">
    <div class="nav-item-btn" data_action="click->ui--mix--bar-component#handleLeftBtn">
      Left
    </div>
    <div class="nav-item-btn nav-item-btn--active">
      Center
    </div>
    <div class="nav-item-btn">
      Right
    </div>
  </div>
  <div class="nav-item">
  </div>
</div>
```

Btw, Birdel (https://github.com/serhiijun/birdel) is my microframework which is not ready yet and you can help me by contributing. It's revolutionized tool for rails assets management and WebSocket components rendering which will replace sucks rails-turbo.