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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
|
|
2
|
+
# DSL Reference
|
|
3
|
+
|
|
4
|
+
The Tailmix DSL is designed to be declarative, expressive, and intuitive. All definitions are placed within a `tailmix do ... end` block inside your component class.
|
|
5
|
+
|
|
6
|
+
## `element`
|
|
7
|
+
|
|
8
|
+
The `element` method is the top-level keyword used to define a logical part or section of your component.
|
|
9
|
+
|
|
10
|
+
### Syntax
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
element :name, "optional base classes" do
|
|
14
|
+
# ... further definitions for this element
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
* **`:name` (Symbol)**: A unique name for the element, like `:wrapper`, `:title`, or `:close_button`. This name is used to access the element's attributes from the `ui` object (e.g., `ui.wrapper`).
|
|
19
|
+
* **`"base classes"` (String, optional)**: A space-separated string of CSS classes that will always be applied to this element, regardless of any variants.
|
|
20
|
+
* **`do ... end` block**: This is where you define the element's variants using `dimension`.
|
|
21
|
+
|
|
22
|
+
### Example
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
tailmix do
|
|
26
|
+
# An element with no base classes
|
|
27
|
+
element :wrapper do
|
|
28
|
+
# ...
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# An element with base classes
|
|
32
|
+
element :title, "text-lg font-bold text-gray-900"
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
-----
|
|
37
|
+
|
|
38
|
+
## `dimension`
|
|
39
|
+
|
|
40
|
+
A `dimension` defines an axis of variation for an element. It represents a property of the component that can change and affect its styling, such as `size`, `color`, or `state`. It must be defined inside an `element` block.
|
|
41
|
+
|
|
42
|
+
### Syntax
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
dimension :name, default: :value do
|
|
46
|
+
# ... variant definitions
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
* **`:name` (Symbol)**: The name of the dimension, like `:size` or `:intent`.
|
|
51
|
+
* **`default: :value` (optional)**: The default value for this dimension if none is provided when the component is initialized.
|
|
52
|
+
|
|
53
|
+
### Example
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
element :button, "rounded-lg" do
|
|
57
|
+
dimension :size, default: :md do
|
|
58
|
+
# ... variants for :sm, :md, :lg
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
dimension :intent, default: :primary do
|
|
62
|
+
# ... variants for :primary, :secondary, :danger
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
-----
|
|
68
|
+
|
|
69
|
+
## `variant`
|
|
70
|
+
|
|
71
|
+
The `variant` method defines the specific styles that should be applied when a `dimension` has a certain value. It must be defined inside a `dimension` block.
|
|
72
|
+
|
|
73
|
+
### Syntax
|
|
74
|
+
|
|
75
|
+
There are two forms for defining a variant:
|
|
76
|
+
|
|
77
|
+
**1. Simple (inline classes)**
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
variant :name, "classes to apply"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**2. Advanced (with a block)**
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
variant :name, "optional base classes for this variant" do
|
|
87
|
+
classes "...", group: :optional_label
|
|
88
|
+
data key: "value"
|
|
89
|
+
aria key: "value"
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
* **`:name` (Symbol|Boolean|String)**: The value of the dimension this variant corresponds to (e.g., `:md`, `true`).
|
|
94
|
+
* **`"classes"` (String)**: A space-separated string of CSS classes.
|
|
95
|
+
* **`do ... end` block**: For more complex definitions:
|
|
96
|
+
* **`classes`**: Can be called multiple times to logically group classes. The optional `group:` option is for documentation purposes.
|
|
97
|
+
* **`data`**: A hash to define `data-*` attributes.
|
|
98
|
+
* **`aria`**: A hash to define `aria-*` attributes for accessibility.
|
|
99
|
+
|
|
100
|
+
### Example
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
dimension :size, default: :md do
|
|
104
|
+
# Simple variant
|
|
105
|
+
variant :sm, "px-2 py-1 text-sm"
|
|
106
|
+
|
|
107
|
+
# Advanced variant with a block
|
|
108
|
+
variant :md, "px-4 py-2" do
|
|
109
|
+
classes "text-base font-semibold", group: :typography
|
|
110
|
+
data size: "medium"
|
|
111
|
+
aria pressed: "false"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
-----
|
|
117
|
+
|
|
118
|
+
## `compound_variant`
|
|
119
|
+
|
|
120
|
+
A `compound_variant` allows you to define styles that apply only when a specific **combination** of dimensions is active. This is crucial for handling interdependencies in a design system. It must be defined inside an `element` block.
|
|
121
|
+
|
|
122
|
+
### Syntax
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
compound_variant on: { dimension1: :value, dimension2: :value } do
|
|
126
|
+
# ... modifications (classes, data, aria)
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
* **`on: { ... }` (Hash)**: A hash specifying the exact conditions under which this rule should be applied.
|
|
131
|
+
* **`do ... end` block**: The modifications to apply. It uses the same `classes`, `data`, and `aria` methods as the `variant` block.
|
|
132
|
+
|
|
133
|
+
### Example
|
|
134
|
+
|
|
135
|
+
This example makes an "outline" button's text and border color match its intent.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
element :button do
|
|
139
|
+
dimension :intent, default: :primary do
|
|
140
|
+
variant :primary, "bg-blue-500 text-white border-blue-500"
|
|
141
|
+
variant :danger, "bg-red-500 text-white border-red-500"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
dimension :look, default: :fill do
|
|
145
|
+
variant :outline, "bg-transparent"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Apply these classes ONLY when look is :outline AND intent is :primary
|
|
149
|
+
compound_variant on: { look: :outline, intent: :primary } do
|
|
150
|
+
classes "text-blue-500"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Apply these classes ONLY when look is :outline AND intent is :danger
|
|
154
|
+
compound_variant on: { look: :outline, intent: :danger } do
|
|
155
|
+
classes "text-red-500"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
-----
|
|
161
|
+
|
|
162
|
+
## `action`
|
|
163
|
+
|
|
164
|
+
The `action` method defines a named set of imperative UI mutations that can be triggered at runtime. This is the core of the client-side bridge for optimistic updates with Hotwire/Turbo. It is defined at the top level of the `tailmix` block.
|
|
165
|
+
|
|
166
|
+
### Syntax
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
action :name, method: :add | :remove | :toggle do
|
|
170
|
+
element :element_to_modify do
|
|
171
|
+
classes "..."
|
|
172
|
+
data key: "value"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
* **`:name` (Symbol)**: A unique name for the action, like `:show_spinner` or `:disable_form`.
|
|
178
|
+
* **`method:`**: The default operation for all modifications within the block (`:add`, `:remove`, or `:toggle`).
|
|
179
|
+
* **`element :name do ... end`**: Specifies which element the following modifications apply to.
|
|
180
|
+
|
|
181
|
+
### Example
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
action :show_pending_state, method: :add do
|
|
185
|
+
element :submit_button do
|
|
186
|
+
classes "opacity-50 cursor-not-allowed"
|
|
187
|
+
data pending: true
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
element :spinner do
|
|
191
|
+
# You can override the default method for a specific modification
|
|
192
|
+
classes "hidden", method: :remove
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
-----
|
|
198
|
+
|
|
199
|
+
## `stimulus`
|
|
200
|
+
|
|
201
|
+
The `stimulus` DSL provides helpers for declaratively adding Stimulus `data-*` attributes. It is available inside any `element` block. The DSL is chainable and context-aware.
|
|
202
|
+
|
|
203
|
+
### Setting the Context
|
|
204
|
+
|
|
205
|
+
Most helpers require a controller context to be set first. You can do this in two ways:
|
|
206
|
+
|
|
207
|
+
* **`.controller("name")`**: This is the primary method. It both adds a `data-controller="name"` attribute and sets the context for subsequent chained calls.
|
|
208
|
+
* **`.context("name")`**: This method only sets the context for subsequent calls without adding a new `data-controller` attribute. This is useful when you want to add targets or actions to an element that is inside the scope of a controller defined on a parent element.
|
|
209
|
+
|
|
210
|
+
### Helpers (Context-Aware)
|
|
211
|
+
|
|
212
|
+
These methods must be called after `.controller()` or `.context()`.
|
|
213
|
+
|
|
214
|
+
#### **`.target("name")`**
|
|
215
|
+
|
|
216
|
+
Adds a target for the current controller context.
|
|
217
|
+
|
|
218
|
+
* **Syntax**: `.target("myTarget")`
|
|
219
|
+
* **Result**: `data-[controller-name]-target="myTarget"`
|
|
220
|
+
|
|
221
|
+
#### **`.action(event, method)` or `.action("specifier")`**
|
|
222
|
+
|
|
223
|
+
Adds an action for the current controller context. It has multiple forms for flexibility.
|
|
224
|
+
|
|
225
|
+
* **Syntax 1 (Tuple)**: `.action(:click, :open)` -\> `data-action="click->[controller-name]#open"`
|
|
226
|
+
* **Syntax 2 (Hash)**: `.action(click: :open, mouseenter: :highlight)` -\> `data-action="click->[controller-name]#open mouseenter->[controller-name]#highlight"`
|
|
227
|
+
* **Syntax 3 (Raw String)**: `.action("click->other-controller#doSomething")`
|
|
228
|
+
|
|
229
|
+
#### **`.value(:name, ...)`**
|
|
230
|
+
|
|
231
|
+
Adds a value for the current controller context. It can be a literal value, or it can be resolved dynamically from a method or a proc.
|
|
232
|
+
|
|
233
|
+
* **Syntax**:
|
|
234
|
+
* `.value(:my_value, value: "some-string")`
|
|
235
|
+
* `.value(:user_id, method: :current_user_id)`
|
|
236
|
+
* `.value(:timestamp, call: -> { Time.now.to_i })`
|
|
237
|
+
* **Result**: `data-[controller-name]-my-value-value="..."`
|
|
238
|
+
|
|
239
|
+
#### **`.action_payload(:action, as: :value_name)`**
|
|
240
|
+
|
|
241
|
+
A powerful helper that serializes a Tailmix `action` definition into a Stimulus value, making it available on the client-side.
|
|
242
|
+
|
|
243
|
+
* **Syntax**: `.action_payload(:disable_form, as: :disable_data)`
|
|
244
|
+
* **Result**: `data-[controller-name]-disable-data-value="{...json...}"`
|
|
245
|
+
|
|
246
|
+
### Example
|
|
247
|
+
|
|
248
|
+
```ruby
|
|
249
|
+
element :confirm_button, "px-4 py-2" do
|
|
250
|
+
stimulus
|
|
251
|
+
.controller("modal") # -> data-controller="modal"
|
|
252
|
+
.action(:click, :open) # -> data-action="click->modal#open"
|
|
253
|
+
.target("panel") # -> data-modal-target="panel"
|
|
254
|
+
.value(:url, value: "/items/1") # -> data-modal-url-value="/items/1"
|
|
255
|
+
.action_payload(:toggle, as: :toggle_data) # -> data-modal-toggle-data-value="{...}"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
element :overlay do
|
|
259
|
+
# Assume `modal` controller is on a parent element
|
|
260
|
+
stimulus
|
|
261
|
+
.context("modal")
|
|
262
|
+
.action(:click, :close) # -> data-action="click->modal#close"
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
-----
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Advanced Usage
|
|
2
|
+
|
|
3
|
+
This guide covers more advanced features of Tailmix, including component inheritance and using the developer tools.
|
|
4
|
+
|
|
5
|
+
## Component Inheritance
|
|
6
|
+
|
|
7
|
+
One of the most powerful features of Tailmix is the ability to inherit and extend component definitions. This allows you to create a base set of components for your design system and then specialize them for specific use cases.
|
|
8
|
+
|
|
9
|
+
When a component class inherits from another, their `tailmix` definitions are intelligently merged:
|
|
10
|
+
|
|
11
|
+
* **Base Classes**: Are combined, with duplicates removed.
|
|
12
|
+
* **Dimensions & Variants**: Are merged. If a child defines a variant with the same name as a parent (e.g., `:sm`), the child's definition will completely **override** the parent's for that specific variant. New variants are added.
|
|
13
|
+
* **Compound Variants**: Are combined. Both parent and child compound variants will be applied.
|
|
14
|
+
* **Actions**: Are merged. If a child defines an action with the same name as a parent, the child's definition **overrides** the parent's.
|
|
15
|
+
|
|
16
|
+
### Example: BaseButton and DangerButton
|
|
17
|
+
|
|
18
|
+
Let's define a `BaseButtonComponent` and then a more specific `DangerButtonComponent` that inherits from it.
|
|
19
|
+
|
|
20
|
+
**1. The Base Component**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# app/components/base_button_component.rb
|
|
24
|
+
class BaseButtonComponent
|
|
25
|
+
include Tailmix
|
|
26
|
+
attr_reader :ui
|
|
27
|
+
|
|
28
|
+
def initialize(size: :md)
|
|
29
|
+
@ui = tailmix(size: size)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
tailmix do
|
|
33
|
+
element :button, "font-semibold border rounded" do
|
|
34
|
+
dimension :size, default: :md do
|
|
35
|
+
variant :sm, "px-2.5 py-1.5 text-xs"
|
|
36
|
+
variant :md, "px-3 py-2 text-sm"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**2. The Inherited Component**
|
|
44
|
+
|
|
45
|
+
The DangerButtonComponent inherits from BaseButtonComponent and only specifies what's different.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# app/components/danger_button_component.rb
|
|
49
|
+
class DangerButtonComponent < BaseButtonComponent
|
|
50
|
+
tailmix do
|
|
51
|
+
# This adds to the parent's base classes
|
|
52
|
+
element :button, "bg-red-500 text-white border-transparent hover:bg-red-600" do
|
|
53
|
+
# This adds a new variant to the :size dimension
|
|
54
|
+
dimension :size do
|
|
55
|
+
variant :lg, "px-4 py-2 text-base"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Resulting Definition for `DangerButtonComponent`:**
|
|
63
|
+
|
|
64
|
+
The `:button` element will have the combined base classes: `"font-semibold border rounded bg-red-500 ..."`.
|
|
65
|
+
|
|
66
|
+
The `:size` dimension will now have three variants available: `:sm`, `:md` (from the parent), and `:lg` (from the child).
|
|
67
|
+
|
|
68
|
+
### Developer Tools
|
|
69
|
+
|
|
70
|
+
Tailmix includes a set of developer tools available via the `.dev` class method on your component.
|
|
71
|
+
|
|
72
|
+
`.dev.docs`
|
|
73
|
+
|
|
74
|
+
Prints a comprehensive summary of the component's definition, including its signature, dimensions, variants, compound variants, and actions. This is invaluable for understanding a component's API at a glance.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
puts DangerButtonComponent.dev.docs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`.dev.stimulus.scaffold`
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
Analyzes the component's `stimulus` definitions and generates a boilerplate Stimulus controller in your console, complete with all targets, values, and action methods.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
puts MyModalComponent.dev.stimulus.scaffold
|
|
87
|
+
```
|
|
88
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# The Client-Side Bridge
|
|
2
|
+
|
|
3
|
+
Tailmix is primarily a server-side tool, but it includes a powerful "bridge" to your client-side JavaScript, enabling dynamic and optimistic UI updates. This bridge operates on several levels.
|
|
4
|
+
|
|
5
|
+
## Level 1: The State Bridge
|
|
6
|
+
|
|
7
|
+
Every element defined in Tailmix automatically receives a `data-tailmix-*` attribute.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<button data-tailmix-button="size:md,intent:primary">
|
|
11
|
+
Click Me
|
|
12
|
+
</button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This attribute serves two purposes:
|
|
16
|
+
|
|
17
|
+
1. A Stable Selector: You can reliably select this element in your JavaScript with `document.querySelector('[data-tailmix-button]')`. This is used internally by Tailmix actions.
|
|
18
|
+
|
|
19
|
+
2. A State Indicator: The value of the attribute reflects the currently applied variants. Your JavaScript can read this to understand the element's state without needing extra data attributes.
|
|
20
|
+
|
|
21
|
+
**Example: Reading State in Stimulus**
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
// button_controller.js
|
|
25
|
+
import { Controller } from "@hotwired/stimulus"
|
|
26
|
+
|
|
27
|
+
export default class extends Controller {
|
|
28
|
+
connect() {
|
|
29
|
+
// e.g., "size:md,intent:primary"
|
|
30
|
+
const variants = this.element.dataset.tailmixButton;
|
|
31
|
+
console.log("Current button variants:", variants);
|
|
32
|
+
|
|
33
|
+
if (variants.includes("size:md")) {
|
|
34
|
+
// do something specific for medium buttons
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Level 2: The Action Bridge
|
|
41
|
+
|
|
42
|
+
This is the most powerful part of the bridge, allowing you to execute UI mutations defined in Ruby directly on the client.
|
|
43
|
+
|
|
44
|
+
1. Define an `actio`n in Ruby
|
|
45
|
+
|
|
46
|
+
First, define an `action` in your component. This action describes a set of changes to apply to different elements.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# app/components/form_component.rb
|
|
50
|
+
class FormComponent
|
|
51
|
+
include Tailmix
|
|
52
|
+
tailmix do
|
|
53
|
+
element :submit_button
|
|
54
|
+
element :spinner, "hidden"
|
|
55
|
+
|
|
56
|
+
action :show_pending_state, method: :add do
|
|
57
|
+
element :submit_button do
|
|
58
|
+
classes "opacity-50 cursor-not-allowed"
|
|
59
|
+
end
|
|
60
|
+
element :spinner do
|
|
61
|
+
classes "hidden", method: :remove # override default method
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. Expose it with action_payload
|
|
69
|
+
|
|
70
|
+
Use the `action_payload` helper in your `stimulus` block to serialize the action's definition into a Stimulus value.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# ...inside the :submit_button element
|
|
74
|
+
stimulus
|
|
75
|
+
.controller("form")
|
|
76
|
+
.action(:click, :submit)
|
|
77
|
+
.action_payload(:show_pending_state, as: :pending_data)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This will generate `data-form-pending-data-value="{...}"` containing the JSON definition of your `:show_pending_state` action.
|
|
81
|
+
|
|
82
|
+
3. Run it from Stimulus
|
|
83
|
+
|
|
84
|
+
In your Stimulus controller, import `Tailmix` and call `Tailmix.run()` with the action's config.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
// form_controller.js
|
|
88
|
+
import { Controller } from "@hotwired/stimulus"
|
|
89
|
+
import Tailmix from "tailmix"
|
|
90
|
+
|
|
91
|
+
export default class extends Controller {
|
|
92
|
+
static values = { pendingData: Object }
|
|
93
|
+
|
|
94
|
+
submit(event) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
|
|
97
|
+
// Instantly apply the UI changes defined in Ruby
|
|
98
|
+
Tailmix.run({
|
|
99
|
+
config: this.pendingDataValue,
|
|
100
|
+
controller: this
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ... now submit the form via fetch/AJAX ...
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
When `submit` is called, the button's classes will be changed and the spinner will be shown instantly, creating a fast, optimistic UI update.
|
|
109
|
+
|
|
110
|
+
## Level 3: The Definition Bridge (Future Vision)
|
|
111
|
+
|
|
112
|
+
The ultimate goal is to allow the client to have access to the full `variant` and `compound_variant` definitions. This would enable the client to dynamically switch between any variant combination without a server roundtrip, providing a SPA-like experience.
|
|
113
|
+
|
|
114
|
+
This is a complex feature planned for a future release and will likely involve passing the definitions via an inline `<script type="application/json">` tag to keep the component's HTML clean.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|