tailmix 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +220 -130
- data/app/javascript/tailmix/finder.js +8 -5
- data/docs/01_getting_started.md +120 -0
- data/docs/02_dsl_reference.md +266 -0
- data/docs/03_advanced_usage.md +88 -0
- data/docs/04_client_side_bridge.md +119 -0
- data/docs/05_cookbook.md +249 -0
- data/examples/button.rb +81 -0
- data/examples/helpers.rb +25 -0
- data/examples/modal_component.rb +44 -23
- data/lib/tailmix/configuration.rb +13 -0
- data/lib/tailmix/definition/context_builder.rb +3 -2
- data/lib/tailmix/definition/contexts/actions/element_builder.rb +2 -2
- data/lib/tailmix/definition/contexts/dimension_builder.rb +27 -9
- data/lib/tailmix/definition/contexts/element_builder.rb +18 -4
- data/lib/tailmix/definition/contexts/variant_builder.rb +35 -0
- data/lib/tailmix/definition/merger.rb +13 -19
- data/lib/tailmix/definition/result.rb +56 -8
- data/lib/tailmix/dev/docs.rb +39 -2
- data/lib/tailmix/dev/tools.rb +4 -0
- data/lib/tailmix/dsl.rb +35 -0
- data/lib/tailmix/html/attributes.rb +34 -11
- data/lib/tailmix/html/data_map.rb +7 -5
- data/lib/tailmix/html/selector.rb +19 -0
- data/lib/tailmix/runtime/context.rb +35 -12
- data/lib/tailmix/version.rb +1 -1
- data/lib/tailmix.rb +9 -43
- metadata +18 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 972f0dcd1667c6db95fbce07209fdf7ab3568b335fd0dab933977a641114dc6a
|
|
4
|
+
data.tar.gz: 3063e23f5422ec1fe5f20683263a6b7cfce4f9c054d393e520f962a0727d4992
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb3cfcfef0f342f12dc680a18e7a7d20b278b08699f28b09b473f8843747d147cd9a1ae56db112a86ce4c52881b609ecd6a9fe863fcdd43c9b63ff0ccc3f50cc
|
|
7
|
+
data.tar.gz: 75366e305721e6091324578ff4138f10b1d0ed70135d0094f901bb52f58580465f528731b498e23f2f61e26369dd4ad5d34425d28da81e26996c138c1d2872b9
|
data/README.md
CHANGED
|
@@ -1,214 +1,304 @@
|
|
|
1
1
|
# Tailmix
|
|
2
2
|
|
|
3
|
-
**Tailmix** is a powerful, declarative
|
|
3
|
+
**Tailmix** is a powerful, declarative engine for managing HTML attributes in Ruby UI components. It allows you to co-locate all presentational logic—including CSS classes, data attributes, and ARIA roles—directly within your component's code, creating truly self-contained and maintainable components.
|
|
4
4
|
|
|
5
|
-
[
|
|
6
|
-
[
|
|
5
|
+
[](https://badge.fury.io/rb/tailmix)
|
|
6
|
+
[](https://github.com/alexander-s-f/tailmix/actions/workflows/main.yml)
|
|
7
7
|
|
|
8
8
|
Inspired by modern frontend tools like CVA (Class Variance Authority), Tailmix brings a robust styling engine to your server-side components (built with Arbre, ViewComponent, Phlex, etc.).
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Key Features
|
|
11
11
|
|
|
12
|
-
* **
|
|
13
|
-
* **
|
|
14
|
-
* **
|
|
15
|
-
* **
|
|
12
|
+
* **Declarative DSL:** Describe your component's styles with an elegant and intuitive API.
|
|
13
|
+
* **Variants & Compound Variants:** Easily manage different component states (`size`, `color`, etc.) and their combinations.
|
|
14
|
+
* **Stimulus Bridge:** Seamlessly integrate with StimulusJS to create interactive components.
|
|
15
|
+
* **"Hot" UI Updates:** Enable optimistic UI updates with `action` and `action_payload` in tandem with Hotwire/Turbo.
|
|
16
|
+
* **Component Inheritance:** Create base components and extend them to avoid code duplication.
|
|
17
|
+
* **Developer Tools:** Get built-in documentation and code generators right in your console.
|
|
18
|
+
* **Zero Dependencies:** Pure Ruby, ready to work in any project.
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
-----
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
## Quick Links
|
|
23
|
+
|
|
24
|
+
* **[Full Documentation →](/docs)**
|
|
25
|
+
* **[DSL Reference](/docs/02_dsl_reference.md)**
|
|
26
|
+
* **[Cookbook (Recipes)](/docs/05_cookbook.md)**
|
|
20
27
|
|
|
21
|
-
```ruby
|
|
22
|
-
gem 'tailmix'
|
|
23
|
-
````
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
-----
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Add the gem to your Gemfile:
|
|
26
34
|
|
|
27
35
|
```bash
|
|
28
36
|
bundle add tailmix
|
|
29
37
|
```
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
Then, run the installer to set up the JavaScript assets (required for `action` and `Stimulus` integration):
|
|
32
40
|
|
|
33
41
|
```bash
|
|
34
42
|
bin/rails g tailmix:install
|
|
35
43
|
```
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
-----
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
## Usage
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
- `dimension :name, default: :value`: Defines a variant or "dimension" (e.g., `size` or `color`).
|
|
43
|
-
- `option :value, "classes"`: Defines the classes for a specific variant option.
|
|
44
|
-
- `action :name, method: :add | :toggle | :remove`: Defines a named set of UI mutations that can be applied on the server (`.apply!`) or passed to the client (`action_payload`).
|
|
45
|
-
- `stimulus`: A powerful nested DSL for declaratively describing Stimulus `data-*` attributes.
|
|
49
|
+
The core idea of Tailmix is to describe all variants of your component within a Ruby class.
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
### 1. Basic Example: The `Badge` Component
|
|
48
52
|
|
|
49
|
-
Let's
|
|
50
|
-
#### 1. Define the Component (`app/components/modal_component.rb`)
|
|
53
|
+
Let's create a simple, flexible `Badge` that can have different colors.
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
**Component Definition:**
|
|
53
56
|
|
|
54
57
|
```ruby
|
|
55
|
-
|
|
58
|
+
# app/components/badge_component.rb
|
|
59
|
+
class BadgeComponent
|
|
56
60
|
include Tailmix
|
|
57
|
-
attr_reader :ui
|
|
61
|
+
attr_reader :ui, :text
|
|
62
|
+
|
|
63
|
+
def initialize(text, color: :gray)
|
|
64
|
+
@ui = tailmix(color: color)
|
|
65
|
+
@text = text
|
|
66
|
+
end
|
|
58
67
|
|
|
59
68
|
tailmix do
|
|
60
|
-
element :
|
|
61
|
-
dimension :
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
element :badge, "inline-flex items-center px-2.5 py-0.5 text-xs font-medium rounded-full" do
|
|
70
|
+
dimension :color, default: :gray do
|
|
71
|
+
variant :gray, "bg-gray-100 text-gray-800"
|
|
72
|
+
variant :success, "bg-green-100 text-green-800"
|
|
73
|
+
variant :danger, "bg-red-100 text-red-800"
|
|
64
74
|
end
|
|
65
|
-
stimulus.controller("modal")
|
|
66
75
|
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
```
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
end
|
|
80
|
+
**Usage in ERB:**
|
|
81
|
+
In ERB, use the `**` operator to pass the attributes.
|
|
71
82
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
option :sm, "w-full max-w-sm p-4"
|
|
75
|
-
option :md, "w-full max-w-md p-6"
|
|
76
|
-
end
|
|
77
|
-
end
|
|
83
|
+
```erb
|
|
84
|
+
<% badge = BadgeComponent.new("Active", color: :success) %>
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
<span <%= tag.attributes **badge.ui.badge %>>
|
|
87
|
+
<%= badge.text %>
|
|
88
|
+
</span>
|
|
89
|
+
```
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.action(:click, :show_pending_state)
|
|
86
|
-
.action_payload(:show_pending_state, as: :pending_data)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
action :show_pending_state, method: :add do
|
|
90
|
-
element :confirm_button do
|
|
91
|
-
classes "is-loading"
|
|
92
|
-
data pending: true
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
91
|
+
**Usage in Arbre:**
|
|
92
|
+
Arbre was the primary inspiration for Tailmix. Integration is seamless and does not require the `**` operator.
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
```ruby
|
|
95
|
+
# my_view.arb
|
|
96
|
+
badge = BadgeComponent.new("Active", color: :success)
|
|
97
|
+
ui = badge.ui
|
|
98
|
+
|
|
99
|
+
span ui.badge do
|
|
100
|
+
text_node badge.text
|
|
100
101
|
end
|
|
101
102
|
```
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
### 2\. Adding Interactivity with Stimulus
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
Tailmix allows you to declaratively define Stimulus controllers. Let's build a component to copy text to the clipboard.
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
**Component Definition:**
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
```ruby
|
|
111
|
+
# app/components/clipboard_component.rb
|
|
112
|
+
class ClipboardComponent
|
|
113
|
+
include Tailmix
|
|
114
|
+
attr_reader :ui, :text_to_copy
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
def initialize(text_to_copy)
|
|
117
|
+
@ui = tailmix
|
|
118
|
+
@text_to_copy = text_to_copy
|
|
119
|
+
end
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
tailmix do
|
|
122
|
+
element :wrapper, "flex items-center space-x-2" do
|
|
123
|
+
stimulus.controller("clipboard") # data-controller="clipboard"
|
|
124
|
+
end
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
element :source, "p-2 border rounded-md bg-gray-50" do
|
|
127
|
+
stimulus.context("clipboard").target("source") # data-clipboard-target="source"
|
|
128
|
+
end
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
element :button, "px-3 py-2 text-sm font-medium text-white bg-blue-600 rounded-md" do
|
|
131
|
+
stimulus.context("clipboard").action(:click, :copy) # data-action="click->clipboard#copy"
|
|
132
|
+
end
|
|
125
133
|
end
|
|
126
134
|
end
|
|
127
135
|
```
|
|
128
136
|
|
|
129
|
-
|
|
137
|
+
**Stimulus Controller:**
|
|
138
|
+
Generate a template with `puts ClipboardComponent.dev.stimulus.scaffold` and fill in the logic.
|
|
130
139
|
|
|
131
|
-
|
|
140
|
+
```javascript
|
|
141
|
+
// app/javascript/controllers/clipboard_controller.js
|
|
142
|
+
import { Controller } from "@hotwired/stimulus"
|
|
132
143
|
|
|
133
|
-
|
|
144
|
+
export default class extends Controller {
|
|
145
|
+
static targets = ["source"]
|
|
134
146
|
|
|
147
|
+
copy() {
|
|
148
|
+
navigator.clipboard.writeText(this.sourceTarget.textContent)
|
|
149
|
+
// Optionally: show a success notification
|
|
150
|
+
}
|
|
151
|
+
}
|
|
135
152
|
```
|
|
136
|
-
<%# app/views/pages/home.html.erb %>
|
|
137
|
-
<% modal = ModalComponent.new(open: true, size: :sm) %>
|
|
138
153
|
|
|
139
|
-
|
|
140
|
-
<%= tag.div **modal.ui.overlay %>
|
|
154
|
+
Your component is now fully interactive, with its entire structure defined in a single Ruby file.
|
|
141
155
|
|
|
142
|
-
|
|
143
|
-
<%# ... your content ... %>
|
|
144
|
-
<%= tag.button "Confirm", **modal.ui.confirm_button %>
|
|
145
|
-
<% end %>
|
|
146
|
-
<% end %>
|
|
147
|
-
```
|
|
156
|
+
-----
|
|
148
157
|
|
|
149
|
-
|
|
158
|
+
## 3. Advanced Example: An Interactive Modal
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
Now let's see the full power of Tailmix by building a common UI pattern: a fully interactive modal component. This example combines multiple elements, shared variants, Stimulus integration, and a client-side action for optimistic updates.
|
|
152
161
|
|
|
153
|
-
|
|
162
|
+
Component Definition:
|
|
163
|
+
The `ModalComponent` definition is self-contained. It describes the structure, all possible states, and the interactive behavior of the modal.
|
|
154
164
|
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
```ruby
|
|
166
|
+
# app/components/modal_component.rb
|
|
167
|
+
class ModalComponent
|
|
168
|
+
include Tailmix
|
|
169
|
+
attr_reader :ui
|
|
159
170
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
controller: this
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// ... then submit the form or send an AJAX request
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
```
|
|
171
|
+
tailmix do
|
|
172
|
+
# A container to hold the controller and its data
|
|
173
|
+
element :container do
|
|
174
|
+
stimulus.controller("modal")
|
|
175
|
+
.action_payload(:toggle, as: :toggle_data)
|
|
176
|
+
# dynamic values:
|
|
177
|
+
.value(:user_id, method: :get_current_user_id)
|
|
178
|
+
.value(:generated_at, call: -> { Time.now.iso8601 })
|
|
179
|
+
end
|
|
176
180
|
|
|
177
|
-
|
|
181
|
+
# The button that will trigger the modal
|
|
182
|
+
element :open_button, "inline-flex text-white bg-blue-600 rounded-lg px-5 py-2.5 cursor-pointer" do
|
|
183
|
+
stimulus.context("modal").action(:click, :toggle)
|
|
184
|
+
end
|
|
178
185
|
|
|
179
|
-
|
|
186
|
+
# The modal's backdrop and wrapper, controlled by the :open dimension
|
|
187
|
+
element :base, "flex items-center justify-center" do
|
|
188
|
+
dimension :open, default: false do
|
|
189
|
+
variant true, "fixed inset-0 z-50 visible opacity-100 transition-opacity"
|
|
190
|
+
variant false, "invisible opacity-0"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
180
193
|
|
|
181
|
-
|
|
194
|
+
element :overlay, "fixed inset-0 bg-black/50" do
|
|
195
|
+
stimulus.context("modal").action(:click, :toggle)
|
|
196
|
+
end
|
|
182
197
|
|
|
183
|
-
|
|
198
|
+
# The main modal panel, with variants for both :open and :size
|
|
199
|
+
element :panel, "w-full relative bg-white rounded-lg shadow-xl" do
|
|
200
|
+
dimension :open, default: false do
|
|
201
|
+
variant true, "block"
|
|
202
|
+
variant false, "hidden"
|
|
203
|
+
end
|
|
204
|
+
dimension :size, default: :md do
|
|
205
|
+
variant :sm, "max-w-sm p-4"
|
|
206
|
+
variant :md, "max-w-md p-6"
|
|
207
|
+
end
|
|
208
|
+
stimulus.context("modal").target("panel")
|
|
209
|
+
end
|
|
184
210
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
211
|
+
element :close_button, "absolute top-2 right-2 p-1 text-gray-500 rounded-full cursor-pointer" do
|
|
212
|
+
stimulus.context("modal").action(:click, :toggle)
|
|
213
|
+
end
|
|
188
214
|
|
|
189
|
-
|
|
215
|
+
# The action that will be executed on the client-side
|
|
216
|
+
action :toggle, method: :toggle do
|
|
217
|
+
element :base do
|
|
218
|
+
classes "visible opacity-100"
|
|
219
|
+
classes "invisible opacity-0"
|
|
220
|
+
end
|
|
221
|
+
element :overlay do
|
|
222
|
+
classes "fixed inset-0 bg-black/50"
|
|
223
|
+
end
|
|
224
|
+
element :panel do
|
|
225
|
+
classes "block"
|
|
226
|
+
classes "hidden"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
190
230
|
|
|
191
|
-
|
|
231
|
+
def initialize(size: :md, open: false)
|
|
232
|
+
@ui = tailmix(size: size, open: open)
|
|
233
|
+
end
|
|
192
234
|
|
|
193
|
-
|
|
194
|
-
|
|
235
|
+
def get_current_user_id
|
|
236
|
+
123
|
|
237
|
+
end
|
|
238
|
+
end
|
|
195
239
|
```
|
|
196
240
|
|
|
197
|
-
|
|
241
|
+
**Stimulus Controller:**
|
|
242
|
+
This controller will handle the modal's open/close logic and use the `action_payload` to trigger the closing animation defined in Ruby.
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// app/javascript/controllers/modal_controller.js
|
|
246
|
+
import { Controller } from "@hotwired/stimulus"
|
|
247
|
+
import Tailmix from "tailmix"
|
|
248
|
+
|
|
249
|
+
export default class extends Controller {
|
|
250
|
+
static values = { toggleData: Object }
|
|
251
|
+
|
|
252
|
+
toggle(event) {
|
|
253
|
+
// Prevent default browser behavior, like form submissions
|
|
254
|
+
if (event) event.preventDefault();
|
|
255
|
+
|
|
256
|
+
// Run the UI mutations defined in our Ruby :toggle action
|
|
257
|
+
Tailmix.run({
|
|
258
|
+
config: this.toggleDataValue,
|
|
259
|
+
controller: this
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
198
264
|
|
|
199
|
-
|
|
265
|
+
**View Usage (Arbre):**
|
|
266
|
+
The view code is clean and readable. We instantiate the component and render its elements. The action_payload is serialized to the container element, making it available to the Stimulus controller.
|
|
200
267
|
|
|
201
268
|
```ruby
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
269
|
+
# my_view.arb
|
|
270
|
+
modal = ModalComponent.new(size: :sm)
|
|
271
|
+
ui = modal.ui
|
|
272
|
+
|
|
273
|
+
div ui.container do
|
|
274
|
+
button "Open Modal", ui.open_button
|
|
275
|
+
|
|
276
|
+
div ui.base do
|
|
277
|
+
div ui.overlay
|
|
278
|
+
div ui.panel do
|
|
279
|
+
button "Close", ui.close_button
|
|
280
|
+
h3 "Payment Successful", ui.title
|
|
281
|
+
|
|
282
|
+
div ui.body do
|
|
283
|
+
"Your payment has been successfully submitted..."
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
206
287
|
end
|
|
207
288
|
```
|
|
208
289
|
|
|
290
|
+
-----
|
|
291
|
+
|
|
292
|
+
## Developer Tools
|
|
293
|
+
|
|
294
|
+
Use the `.dev` namespace to introspect your components right from the console.
|
|
295
|
+
|
|
296
|
+
* **`YourComponent.dev.docs`**: Displays full documentation for all variants and actions.
|
|
297
|
+
* **`YourComponent.dev.stimulus.scaffold`**: Generates a Stimulus controller template.
|
|
298
|
+
|
|
209
299
|
## Contributing
|
|
210
300
|
|
|
211
|
-
Bug reports and pull requests are welcome on GitHub
|
|
301
|
+
Bug reports and pull requests are welcome on GitHub.
|
|
212
302
|
|
|
213
303
|
## License
|
|
214
304
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
const SELECTOR_ATTRIBUTE = "data-tm-el";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Finds an element by the new Tailmix selector convention.
|
|
3
|
+
* e.g., [data-tailmix-panel]
|
|
5
4
|
* @param {HTMLElement} rootElement
|
|
6
|
-
* @param {string} name
|
|
5
|
+
* @param {string} name - The logical name of the element (e.g., "panel").
|
|
7
6
|
* @returns {HTMLElement|null}
|
|
8
7
|
*/
|
|
9
8
|
export function findElement(rootElement, name) {
|
|
10
|
-
const selector = `[
|
|
9
|
+
const selector = `[data-tailmix-${name}]`;
|
|
10
|
+
|
|
11
|
+
if (rootElement.matches(selector)) {
|
|
12
|
+
return rootElement;
|
|
13
|
+
}
|
|
11
14
|
return rootElement.querySelector(selector);
|
|
12
15
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Getting Started with Tailmix
|
|
2
|
+
|
|
3
|
+
Welcome to Tailmix! This guide will walk you through installing the gem, setting up your project, and creating your first component.
|
|
4
|
+
|
|
5
|
+
## Philosophy
|
|
6
|
+
|
|
7
|
+
Tailmix is built on the idea of **co-location**. Instead of defining component styles in separate CSS, SCSS, or CSS-in-JS files, you define them directly within the Ruby class that represents your component. This creates self-contained, highly reusable, and easily maintainable UI components.
|
|
8
|
+
|
|
9
|
+
The core of Tailmix is a powerful and expressive **DSL (Domain-Specific Language)** that allows you to declaratively define how a component should look based on its properties or "variants".
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Getting started with Tailmix involves two simple steps: adding the gem and installing the JavaScript bridge.
|
|
14
|
+
|
|
15
|
+
### 1. Add the Gem
|
|
16
|
+
|
|
17
|
+
Add `tailmix` to your application's Gemfile:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle add tailmix
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Install JavaScript Assets
|
|
24
|
+
|
|
25
|
+
Run the installer to set up the necessary JavaScript files for the client-side bridge (used by actions).
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bin/rails g tailmix:install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This command will add tailmix to your importmap.rb and ensure its JavaScript is available in your application.
|
|
32
|
+
|
|
33
|
+
### Your First Component: A Badge
|
|
34
|
+
Let's create a simple BadgeComponent to see Tailmix in action.
|
|
35
|
+
|
|
36
|
+
#### 1. Define the Component Class
|
|
37
|
+
|
|
38
|
+
Create a new file in `app/components/badge_component.rb`:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# app/components/badge_component.rb
|
|
42
|
+
class BadgeComponent
|
|
43
|
+
include Tailmix
|
|
44
|
+
attr_reader :ui, :text
|
|
45
|
+
|
|
46
|
+
def initialize(text, color: :gray)
|
|
47
|
+
@ui = tailmix(color: color)
|
|
48
|
+
@text = text
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
tailmix do
|
|
52
|
+
element :badge, "inline-flex items-center px-2.5 py-0.5 text-xs font-medium rounded-full" do
|
|
53
|
+
dimension :color, default: :gray do
|
|
54
|
+
variant :gray, "bg-gray-100 text-gray-600"
|
|
55
|
+
variant :success, "bg-green-100 text-green-700"
|
|
56
|
+
variant :danger, "bg-red-100 text-red-700"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### 2. Use it in a View
|
|
64
|
+
|
|
65
|
+
Now, you can use this component in any ERB view:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<%# Create two instances of our badge with different variants %>
|
|
69
|
+
<% success_badge = BadgeComponent.new("Active", color: :success) %>
|
|
70
|
+
<% danger_badge = BadgeComponent.new("Inactive", color: :danger) %>
|
|
71
|
+
|
|
72
|
+
<span <%= tag.attributes **success_badge.ui.badge %>>
|
|
73
|
+
<%= success_badge.text %>
|
|
74
|
+
</span>
|
|
75
|
+
|
|
76
|
+
<span <%= tag.attributes **danger_badge.ui.badge %>>
|
|
77
|
+
<%= danger_badge.text %>
|
|
78
|
+
</span>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### View Usage .arb (Ruby Arbre)
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# app/views/components/_badge.arb
|
|
85
|
+
success_badge = BadgeComponent.new("Active", color: :success)
|
|
86
|
+
danger_badge = BadgeComponent.new("Inactive", color: :danger)
|
|
87
|
+
|
|
88
|
+
span success_badge.ui.badge do
|
|
89
|
+
success_badge.text
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
span danger_badge.ui.badge do
|
|
93
|
+
danger_badge.text
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### 3. The Resulting HTML
|
|
98
|
+
|
|
99
|
+
This will produce the following clean and semantic HTML:
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<span class="inline-flex ... bg-green-100 text-green-700" data-tailmix-badge="color:success">
|
|
103
|
+
Active
|
|
104
|
+
</span>
|
|
105
|
+
|
|
106
|
+
<span class="inline-flex ... bg-red-100 text-red-700" data-tailmix-badge="color:danger">
|
|
107
|
+
Inactive
|
|
108
|
+
</span>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Notice the `data-tailmix-badge` attribute, which serves as both a stable selector and a state indicator for your component.
|
|
112
|
+
|
|
113
|
+
## Next Steps
|
|
114
|
+
|
|
115
|
+
- You've successfully created your first component! To learn more about the power of Tailmix, check out these documents:
|
|
116
|
+
|
|
117
|
+
- DSL Reference: For a deep dive into every available DSL method.
|
|
118
|
+
|
|
119
|
+
- Cookbook: For practical, real-world examples of common UI components.
|
|
120
|
+
|