@yuki24/shoelace-rails
v0.1.0-beta.5
Published
Unobtrusive Javascript and Turbolinks support for shoelace.style
Downloads
1
Readme
Shoelace Rails
The shoelace-rails
gem adds useful helper methods for Shoelace.style, the design system.
Installation
This document assumes that you use the webpacker gem. You may have to tweak the examples here if you do not use it. However, the principle here should be the same regardless of the JS bundler you use.
Add this line to your application's Gemfile:
gem 'shoelace-rails', git: "https://github.com/yuki24/shoelace-rails.git"
And then execute:
$ bundle install
Additionally, you need to add the following npm packages:
yarn add @shoelace-style/shoelace @yuki24/shoelace-rails copy-webpack-plugin
Set up CSS
Asset Pipeline
Add the following lines to the app/assets/stylesheets/application.css
if you need the sprockets gem:
/*
*= require "@shoelace-style/shoelace/dist/themes/light.css
*= require "@shoelace-style/shoelace/dist/themes/dark.css
*/
Webpack & CSS Loader
Add the following lines to the app/packs/entrypoints/application.js
if you prefer the webpack and CSS loader:
import "@shoelace-style/shoelace/dist/themes/light.css"
import "@shoelace-style/shoelace/dist/themes/dark.css" // Optional dark mode
Set up Javascript
Add the code below so Shoelace's assets (mostly icons) will be copied to the final build output. This is required for both Turbolinks and Hotwire.
// config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const path = require('path')
const CopyPlugin = require('copy-webpack-plugin')
// Add shoelace icons to webpack's build process
environment.plugins.append(
'CopyPlugin',
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../../node_modules/@shoelace-style/shoelace/dist/assets'),
to: path.resolve(__dirname, '../../public/packs/js/assets')
}
]
})
)
module.exports = environment
Turbolinks 5
If you are using the traditional Turbolinks 5, import the startUjs
and
startTurbolinks
functions to activate Shoelace UJS in the app/packs/entrypoints/application.js
, or the entrypoint
file of your project:
import Turbolinks from "turbolinks"
import { setBasePath } from "@shoelace-style/shoelace"
import { startUjs, startTurbolinks, getDefaultAssetPath } from "@yuki24/shoelace-rails"
// Important! Turboinks.start() needs to be called before calling the startTurbolinks function:
Turbolinks.start()
startUjs()
startTurbolinks(Turbolinks)
setBasePath(getDefaultAssetPath())
This gem provides form helpers called sl_form_for
, sl_form_with
and sl_form_tag
. When rendering a form, try
using one of them just like you normally use form_for
:
<%= sl_form_for @user do |form| %>
...
<% end %>
Once the Shoelace UJS is activated and the form is rendered by sl_form_for
(or any equivalent form helper), form
submission should automatically be handled and you should be able to start buildgin an app like you normally do on
Rails.
Hotwire
If you are using Hotwire, import the startTurbo
function in the
app/packs/entrypoints/application.js
, or the entrypoint file of your project:
import "@hotwired/turbo-rails"
import { setBasePath } from "@shoelace-style/shoelace"
import { startTurbo, getDefaultAssetPath } from "@yuki24/shoelace-rails"
startTurbo()
setBasePath(getDefaultAssetPath())
Unlike the Turbolinks support, you have to use the other form helper, called sl_turbo_form_for
:
<%= sl_turbo_form_for @user do |form| %>
...
<% end %>
There are also corresponding sl_turbo_form_with
and sl_turbo_form_tag
methods in case you need to render a form
in different scenarios. You can still use methods like sl_form_for
, but the sl-submit
event is not automatically
handled.
View Helpers
As explained above, this gem provides drop-in replacements to. Here is a short example of how the form helper works:
<%= sl_form_for @user do |form| %>
<% # Text input: https://shoelace.style/components/input %>
<%= form.text_field :name %>
<%= form.password_field :password, placeholder: "Password Toggle", 'toggle-password': true %>
<% # Radio buttons: https://shoelace.style/components/color-picker %>
<%= form.color_field :color %>
<% # Radio buttons: https://shoelace.style/components/radio %>
<%= form.collection_radio_buttons :status, { id_1: "Option 1", id_2: "Option 2", id_3: "Option 3" }, :first, :last %>
<% # Select: https://shoelace.style/components/select %>
<%= form.collection_select :tag, { id_1: "Option 1", id_2: "Option 2", id_3: "Option 3" }, :first, :last, {}, { placeholder: "Select one" } %>
<%= form.submit %>
<% end %>
And this code will produce:
<sl-form class="new_user" id="new_user" data-remote="true" action="/" accept-charset="UTF-8" method="post">
<sl-input label="Name" type="text" name="user[name]" id="user_name"></sl-input>
<sl-input label="Password" type="password" name="user[password]" id="user_password"></sl-input>
<sl-color-picker value="#ffffff" name="user[color]" id="user_color"></sl-color-picker>
<sl-radio-group no-fieldset="true">
<sl-radio value="id_1" name="user[status]" id="user_status_id_1">Option 1</sl-radio>
<sl-radio value="id_2" name="user[status]" id="user_status_id_2">Option 2</sl-radio>
<sl-radio value="id_3" name="user[status]" id="user_status_id_3">Option 3</sl-radio>
</sl-radio-group>
<sl-select placeholder="Select one" name="user[tag]" id="user_tag">
<sl-menu-item value="id_1">Option 1</sl-menu-item>
<sl-menu-item value="id_2">Option 2</sl-menu-item>
<sl-menu-item value="id_3">Option 3</sl-menu-item>
</sl-select>
<sl-button submit="true" type="primary" data-disable-with="Create User">Create User</sl-button>
</sl-form>
How it works
Shoelace.style is built of top of the Web Components API, which provides a way to your own HTML elements and use them in any framework, or even in a static HTML.
This is a great way to build a design system, but it also comes with a few challenges. One notable challenge is form
submissions. Because of how Shoelace is built (for a good reason), the native <form>
element does not recognize
Shoelace form controls like <sl-input>
. The library solves this problem by providing a bridge that connects Shoelace
with Turbolinks and Hotwire.
How Turbolinks support works
Simply put, the Rails UJS is just a bunch of custom event handlers.
Because of the reasons described above, the normal form submit
event is not emitted by the <sl-form>
element.
Aside from that, the commonly used technique of making a POST/DELETE request with data-confirm="You are you sure?""
only targets the <a>
and <button>
tags, and it does not work with Shoelace elements like <sl-button>
. Here are
how these problems are addressed:
- Form submission: when a button within the form is clicked, Shoelace emits a
sl-submit
event with access to theformdata
object. The Shoelace UJS captures this event and does the same thing as Rails UJS. - Event listeners for Shoelace elements: the Shoelace UJS adds an event listener that is similar to the original
Rails UJS, but specifically targets the
<sl-button>
element.
Turbolinks is actually mostly compatible with Shoelace. The only notable addition is support for links within a Shadow DOM. This may not necessarily eb an issue specifically with Shoelace, and other implementations that use the Shadow DOM may benefit from this fix.
How Hotwire support works
As explained above, the normal form submit
events are not emitted. On the other hand, Hotwire is a complete write
of Turbolinks, and the hacks for Turbolinks 5 may not work for Hotwire. Thankfully, Hotwire has received a lot of
improvements, and the amount of hacks required to make it work on Rails was very minimal, most notably:
- All link transitions work with Turbo Drive with no extra code.
- Stimulus.js is fully compatible with synthetic events, which Shoelace also uses to emit its events.
Rails UJS is no longer a dependency and form submission is now also handled by Hotwire Turbo, which leaves us in a
quite good place to make Shoelace work with Rails. That means all we have to do is to translate the sl-submit
event
in to the submit
event.
In order to simulate a normal form submission performed by a form, the startTurbo()
function defines a custom
element called <sl-turbo-form>
.
This element internally maintains two things: a <sl-form>
element rendered in the element's Shadow DOM,
and a <form>
tag rendered in the light DOM.
When the sl-submit
event is captured, the element will populate the form
element in the light DOM with the
formdata
provided by the sl_submit
event, and emits an synthetic submit
event. Once this is fired, everything
will be handled by Hotwire Turbo.
Development
- Run
bundle install
- Make a change and add test coverage
- Run
bundle rails test
- Make a commit and push it to GitHub
- Send us a pull request
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/yuki24/shoelace-rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Shoelace::Rails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.