tailmix 0.2.0 → 0.4.6
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/.rubocop.yml +3 -0
- data/README.md +147 -85
- data/app/javascript/tailmix/finder.js +15 -0
- data/app/javascript/tailmix/index.js +7 -0
- data/app/javascript/tailmix/mutator.js +28 -0
- data/app/javascript/tailmix/runner.js +7 -0
- data/app/javascript/tailmix/stimulus_adapter.js +37 -0
- data/examples/_modal_component.arb +36 -0
- data/examples/modal_component.rb +209 -0
- data/lib/generators/tailmix/install_generator.rb +19 -0
- data/lib/tailmix/configuration.rb +13 -0
- data/lib/tailmix/definition/context_builder.rb +39 -0
- data/lib/tailmix/definition/contexts/action_builder.rb +31 -0
- data/lib/tailmix/definition/contexts/actions/element_builder.rb +30 -0
- data/lib/tailmix/definition/contexts/attribute_builder.rb +21 -0
- data/lib/tailmix/definition/contexts/dimension_builder.rb +34 -0
- data/lib/tailmix/definition/contexts/element_builder.rb +41 -0
- data/lib/tailmix/definition/contexts/stimulus_builder.rb +101 -0
- data/lib/tailmix/definition/contexts/variant_builder.rb +35 -0
- data/lib/tailmix/definition/merger.rb +86 -0
- data/lib/tailmix/definition/result.rb +78 -0
- data/lib/tailmix/definition.rb +11 -0
- data/lib/tailmix/dev/docs.rb +88 -0
- data/lib/tailmix/dev/stimulus_generator.rb +124 -0
- data/lib/tailmix/dev/tools.rb +30 -0
- data/lib/tailmix/dsl.rb +35 -0
- data/lib/tailmix/engine.rb +17 -0
- data/lib/tailmix/html/attributes.rb +94 -0
- data/lib/tailmix/html/class_list.rb +79 -0
- data/lib/tailmix/html/data_map.rb +97 -0
- data/lib/tailmix/html/selector.rb +19 -0
- data/lib/tailmix/html/stimulus_builder.rb +65 -0
- data/lib/tailmix/runtime/action.rb +51 -0
- data/lib/tailmix/runtime/context.rb +74 -0
- data/lib/tailmix/runtime/facade_builder.rb +23 -0
- data/lib/tailmix/runtime/stimulus/compiler.rb +59 -0
- data/lib/tailmix/runtime.rb +14 -0
- data/lib/tailmix/version.rb +1 -1
- data/lib/tailmix.rb +18 -23
- metadata +37 -12
- data/examples/interactive_component.rb +0 -42
- data/examples/status_badge_component.rb +0 -44
- data/lib/tailmix/action.rb +0 -27
- data/lib/tailmix/dimension.rb +0 -18
- data/lib/tailmix/element.rb +0 -24
- data/lib/tailmix/manager.rb +0 -86
- data/lib/tailmix/part.rb +0 -39
- data/lib/tailmix/resolver.rb +0 -28
- data/lib/tailmix/schema.rb +0 -41
- data/lib/tailmix/utils.rb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6eba946c8714d0699a6d9476c1c7b31f323418fdbf6fbc449bb96fb5ffe85f2
|
|
4
|
+
data.tar.gz: 44460ef22c33497ff09002ef084691dafdedf19be942685faf8f578155f98351
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 587453b63976416198477d8b38c02ed24e7c3d48972464fa926d2ee0cad42737f324dede5e3dac92d78e584ba71cfb6552b1be7f4eb94485816a598e9d3fe5c3
|
|
7
|
+
data.tar.gz: 2d61fe96e8c2bf7ba9dc94876381bf0f5202dd0d22dd4aafe25ada2e977182b9693ae13d1f42c5d985980196ed1382587e9995fd5c614b925bab46f3159dec9f
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -1,148 +1,210 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Tailmix
|
|
2
|
+
|
|
3
|
+
**Tailmix** is a powerful, declarative, and interactive CSS class manager for building maintainable UI components in Ruby. It's designed to work seamlessly with utility-first CSS frameworks like **Tailwind CSS**, allowing you to co-locate your style logic with your component's code—in a clean, structured, and highly reusable way.
|
|
2
4
|
|
|
3
5
|
[](https://badge.fury.io/rb/tailmix)
|
|
4
6
|
[](https://github.com/alexander-s-f/tailmix/actions/workflows/main.yml)
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Inspired by modern frontend tools like CVA (Class Variance Authority), `tailmix` brings a robust styling engine to your server-side components (like those built with Arbre, ViewComponent, or Phlex).
|
|
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
|
## Philosophy
|
|
11
11
|
|
|
12
12
|
* **Co-location & Isolation:** Define all style variants for a component directly within its class. No more hunting for styles in separate files. Each component is fully self-contained.
|
|
13
|
-
* **Declarative First:** A beautiful DSL to
|
|
14
|
-
* **Imperative Power:** A rich runtime API to dynamically
|
|
13
|
+
* **Declarative First:** A beautiful DSL to declaratively describe your component's appearance based on variants like state, size, etc.
|
|
14
|
+
* **Imperative Power:** A rich runtime API to dynamically add, remove, or toggle classes, perfect for server-side updates via Hotwire/Turbo.
|
|
15
15
|
* **Framework-Agnostic:** Written in pure Ruby with zero dependencies, ready to be used in any project.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
|
-
Add
|
|
19
|
+
Add the gem to your Gemfile:
|
|
20
20
|
|
|
21
21
|
```ruby
|
|
22
22
|
gem 'tailmix'
|
|
23
|
+
````
|
|
24
|
+
|
|
25
|
+
Or install it from the command line:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle add tailmix
|
|
23
29
|
```
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
Next, run the installer to set up the JavaScript assets:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bin/rails g tailmix:install
|
|
35
|
+
```
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
## Core Concepts
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
You define your component's appearance using a simple `tailmix do ... end` DSL inside your class.
|
|
30
40
|
|
|
31
|
-
|
|
41
|
+
- `element :name, "base classes"`: Defines a logical part of your component (e.g., `:wrapper`, `:panel`, `:icon`).
|
|
42
|
+
- `dimension :name, default: :value`: Defines a variant or "dimension" (e.g., `size` or `color`).
|
|
43
|
+
- `variant :value, "classes"`: Defines the classes for a specific variant.
|
|
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.
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
* `dimension_name do ... end` (e.g., `state do`, `size do`): Defines a variant "dimension". The name can be anything you choose.
|
|
35
|
-
* `option :name, "classes", default: true`: Defines a specific option within a dimension and its corresponding CSS classes. One option per dimension can be marked as the default.
|
|
47
|
+
## Usage Example
|
|
36
48
|
|
|
37
|
-
|
|
49
|
+
Let's build a complex `ModalComponent` from scratch.
|
|
50
|
+
#### 1. Define the Component (`app/components/modal_component.rb`)
|
|
51
|
+
|
|
52
|
+
This is a plain Ruby class that contains all the style and behavior logic.
|
|
38
53
|
|
|
39
54
|
```ruby
|
|
40
|
-
class
|
|
55
|
+
class ModalComponent
|
|
41
56
|
include Tailmix
|
|
57
|
+
attr_reader :ui
|
|
42
58
|
|
|
43
59
|
tailmix do
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
option :sm, "px-2.5 py-1.5 text-xs", default: true
|
|
49
|
-
option :md, "px-3 py-2 text-sm"
|
|
50
|
-
option :lg, "px-4 py-2 text-base"
|
|
60
|
+
element :base, "fixed inset-0 z-50 flex items-center justify-center" do
|
|
61
|
+
dimension :open, default: false do
|
|
62
|
+
variant true, "visible opacity-100"
|
|
63
|
+
variant false, "invisible opacity-0"
|
|
51
64
|
end
|
|
65
|
+
stimulus.controller("modal")
|
|
66
|
+
end
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
element :overlay, "fixed inset-0 bg-black/50 transition-opacity" do
|
|
69
|
+
stimulus.context("modal").action(click: :close)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
element :panel, "relative bg-white rounded-lg shadow-xl" do
|
|
73
|
+
dimension :size, default: :md do
|
|
74
|
+
variant :sm, "w-full max-w-sm p-4"
|
|
75
|
+
variant :md, "w-full max-w-md p-6"
|
|
58
76
|
end
|
|
59
77
|
end
|
|
60
78
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
element :close_button, "absolute top-2 right-2 p-1 text-gray-400" do
|
|
80
|
+
stimulus.context("modal").action(click: :close)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
element :confirm_button, "px-4 py-2 bg-blue-600 text-white rounded-md" do
|
|
84
|
+
stimulus.controller("form-submission")
|
|
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
|
|
67
93
|
end
|
|
68
94
|
end
|
|
69
95
|
end
|
|
70
|
-
|
|
96
|
+
|
|
97
|
+
def initialize(open: false, size: :md)
|
|
98
|
+
@ui = tailmix(open: open, size: size)
|
|
99
|
+
end
|
|
71
100
|
end
|
|
72
101
|
```
|
|
73
102
|
|
|
74
|
-
|
|
103
|
+
#### 2. Use in a View (Arbre, ERB, etc.)
|
|
104
|
+
|
|
105
|
+
Thanks to Tailmix's design, you can pass `ui` objects directly to many rendering helpers.
|
|
75
106
|
|
|
76
|
-
|
|
107
|
+
##### Arbre
|
|
77
108
|
|
|
78
|
-
|
|
109
|
+
The API is seamless. The `ui` object behaves like an attributes hash automatically.
|
|
110
|
+
|
|
111
|
+
Ruby
|
|
79
112
|
|
|
80
113
|
```ruby
|
|
81
|
-
|
|
82
|
-
|
|
114
|
+
# app/views/components/my_modal.arb
|
|
115
|
+
# 1. Instantiate the component with the desired variants
|
|
116
|
+
modal = ModalComponent.new(open: true, size: :sm)
|
|
83
117
|
|
|
84
|
-
|
|
118
|
+
# 2. Render by passing the ui objects directly to Arbre's tag helpers
|
|
119
|
+
div modal.ui.base do
|
|
120
|
+
div modal.ui.overlay
|
|
85
121
|
|
|
86
|
-
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def render
|
|
92
|
-
# The manager's methods map to your elements.
|
|
93
|
-
# Ruby's `to_s` is called implicitly when rendering.
|
|
94
|
-
"<button class='#{@classes.button}'>
|
|
95
|
-
<span class='#{@classes.icon}'></span>
|
|
96
|
-
Click me
|
|
97
|
-
</button>"
|
|
122
|
+
div modal.ui.panel do
|
|
123
|
+
# ... your content ...
|
|
124
|
+
button modal.ui.confirm_button, "Confirm"
|
|
98
125
|
end
|
|
99
126
|
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
##### ERB / Rails Tag Helpers
|
|
130
|
+
|
|
131
|
+
In ERB, the idiomatic way to pass a hash-like object as attributes is with the double splat (`**`) operator.
|
|
100
132
|
|
|
101
|
-
|
|
102
|
-
button = MyButtonComponent.new
|
|
103
|
-
button.render
|
|
133
|
+
Фрагмент кода
|
|
104
134
|
|
|
105
|
-
# Renders a small danger button
|
|
106
|
-
button = MyButtonComponent.new(intent: :danger, size: :sm)
|
|
107
|
-
button.render
|
|
108
135
|
```
|
|
136
|
+
<%# app/views/pages/home.html.erb %>
|
|
137
|
+
<% modal = ModalComponent.new(open: true, size: :sm) %>
|
|
109
138
|
|
|
110
|
-
|
|
139
|
+
<%= tag.div **modal.ui.base do %>
|
|
140
|
+
<%= tag.div **modal.ui.overlay %>
|
|
111
141
|
|
|
112
|
-
|
|
142
|
+
<%= tag.div **modal.ui.panel do %>
|
|
143
|
+
<%# ... your content ... %>
|
|
144
|
+
<%= tag.button "Confirm", **modal.ui.confirm_button %>
|
|
145
|
+
<% end %>
|
|
146
|
+
<% end %>
|
|
147
|
+
```
|
|
113
148
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
149
|
+
#### 3. Bring it to Life with Stimulus
|
|
150
|
+
|
|
151
|
+
The `action_payload` helper makes it easy to connect server-side definitions with client-side behavior.
|
|
152
|
+
|
|
153
|
+
JavaScript
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
// app/javascript/controllers/form_submission_controller.js
|
|
157
|
+
import { Controller } from "@hotwired/stimulus"
|
|
158
|
+
import Tailmix from "tailmix"
|
|
159
|
+
|
|
160
|
+
export default class extends Controller {
|
|
161
|
+
static values = { pendingData: Object }
|
|
117
162
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# The `combine` method updates the declarative state
|
|
121
|
-
@classes.combine(intent: :secondary)
|
|
163
|
+
showPendingState(event) {
|
|
164
|
+
event.preventDefault()
|
|
122
165
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
166
|
+
// Instantly apply UI changes from the payload
|
|
167
|
+
Tailmix.run({
|
|
168
|
+
config: this.pendingDataValue,
|
|
169
|
+
controller: this
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ... then submit the form or send an AJAX request
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
127
176
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@classes.icon.remove("animate-spin")
|
|
132
|
-
end
|
|
133
|
-
end
|
|
177
|
+
## Developer Tools
|
|
178
|
+
|
|
179
|
+
Tailmix comes with built-in introspection tools to improve your development experience. Access them via the `.dev` namespace on your component class.
|
|
134
180
|
|
|
135
|
-
|
|
136
|
-
button.set_loading_state!
|
|
137
|
-
button.render # Renders the button in a loading state
|
|
181
|
+
#### Component Documentation
|
|
138
182
|
|
|
139
|
-
|
|
140
|
-
|
|
183
|
+
Get a cheat sheet of all available `dimensions` and `actions` right in your console.
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
puts ModalComponent.dev.docs
|
|
141
187
|
```
|
|
142
188
|
|
|
143
|
-
|
|
189
|
+
#### Stimulus Controller Generator
|
|
190
|
+
|
|
191
|
+
Tailmix can analyze your component and scaffold a perfect boilerplate Stimulus controller with all targets, values, and action methods.
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
puts ModalComponent.dev.stimulus.scaffold("modal")
|
|
195
|
+
```
|
|
144
196
|
|
|
145
|
-
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
You can configure Tailmix by creating an initializer:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
# config/initializers/tailmix.rb
|
|
203
|
+
Tailmix.configure do |config|
|
|
204
|
+
# The attribute used by the universal JS selector.
|
|
205
|
+
config.element_selector_attribute = "data-tm-el"
|
|
206
|
+
end
|
|
207
|
+
```
|
|
146
208
|
|
|
147
209
|
## Contributing
|
|
148
210
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds an element by the new Tailmix selector convention.
|
|
3
|
+
* e.g., [data-tailmix-panel]
|
|
4
|
+
* @param {HTMLElement} rootElement
|
|
5
|
+
* @param {string} name - The logical name of the element (e.g., "panel").
|
|
6
|
+
* @returns {HTMLElement|null}
|
|
7
|
+
*/
|
|
8
|
+
export function findElement(rootElement, name) {
|
|
9
|
+
const selector = `[data-tailmix-${name}]`;
|
|
10
|
+
|
|
11
|
+
if (rootElement.matches(selector)) {
|
|
12
|
+
return rootElement;
|
|
13
|
+
}
|
|
14
|
+
return rootElement.querySelector(selector);
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {HTMLElement} element
|
|
3
|
+
* @param {object} command - { field: "classes", method: "toggle", payload: "hidden" }.
|
|
4
|
+
*/
|
|
5
|
+
export function applyCommand(element, command) {
|
|
6
|
+
const { field, method, payload } = command;
|
|
7
|
+
|
|
8
|
+
if (field === 'classes') {
|
|
9
|
+
payload.split(' ').forEach(klass => {
|
|
10
|
+
if (klass) element.classList[method](klass);
|
|
11
|
+
});
|
|
12
|
+
} else if (field === 'data') {
|
|
13
|
+
for (const key in payload) {
|
|
14
|
+
const attributeName = `data-${key.replace(/_/g, '-')}`;
|
|
15
|
+
const value = payload[key];
|
|
16
|
+
|
|
17
|
+
if (method === 'remove') {
|
|
18
|
+
element.removeAttribute(attributeName);
|
|
19
|
+
} else if (method === 'toggle') {
|
|
20
|
+
element.hasAttribute(attributeName)
|
|
21
|
+
? element.removeAttribute(attributeName)
|
|
22
|
+
: element.setAttribute(attributeName, value);
|
|
23
|
+
} else { // 'add'
|
|
24
|
+
element.setAttribute(attributeName, value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { findElement as findElementByAttribute } from './finder';
|
|
2
|
+
import { applyCommand } from './mutator';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {object} params
|
|
6
|
+
* @param {object} params.config
|
|
7
|
+
* @param {StimulusController} params.controller
|
|
8
|
+
*/
|
|
9
|
+
export function runFromStimulus({ config, controller }) {
|
|
10
|
+
if (!config?.mutations) {
|
|
11
|
+
console.error("Invalid Tailmix config:", config);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const elementName in config.mutations) {
|
|
16
|
+
const targetElement = findElement(elementName, controller);
|
|
17
|
+
|
|
18
|
+
if (!targetElement) {
|
|
19
|
+
console.warn(`Tailmix: Element "${elementName}" not found.`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const commands = config.mutations[elementName];
|
|
24
|
+
|
|
25
|
+
for (const command of commands) {
|
|
26
|
+
applyCommand(targetElement, command);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function findElement(name, controller) {
|
|
32
|
+
const targetName = `${name}Target`;
|
|
33
|
+
if (controller[targetName]) {
|
|
34
|
+
return controller[targetName];
|
|
35
|
+
}
|
|
36
|
+
return findElementByAttribute(controller.element, name);
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Arbre view:
|
|
2
|
+
|
|
3
|
+
modal = SuperModalComponent.new(size: :lg, open: true)
|
|
4
|
+
|
|
5
|
+
# modal.lock!
|
|
6
|
+
ui = modal.ui
|
|
7
|
+
|
|
8
|
+
div ui.base do
|
|
9
|
+
div ui.overlay
|
|
10
|
+
div ui.open do
|
|
11
|
+
"open"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
div ui.panel do
|
|
15
|
+
button ui.close_button do
|
|
16
|
+
span "Close"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
h3 ui.title do
|
|
20
|
+
"Payment Successful"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
div ui.body do
|
|
24
|
+
"Your payment has been successfully submitted. We’ve sent you an email with all of the details of your order."
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
div ui.footer do
|
|
28
|
+
button ui.confirm_button do
|
|
29
|
+
span "Confirm Purchase"
|
|
30
|
+
div ui.spinner do
|
|
31
|
+
span "Loading..."
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/tailmix"
|
|
4
|
+
|
|
5
|
+
class ModalComponent
|
|
6
|
+
include Tailmix
|
|
7
|
+
attr_reader :ui
|
|
8
|
+
|
|
9
|
+
tailmix do
|
|
10
|
+
element :base, "fixed inset-0 z-50 flex items-center justify-center" do
|
|
11
|
+
dimension :open, default: true do
|
|
12
|
+
variant true, "visible opacity-100"
|
|
13
|
+
variant false, "invisible opacity-0"
|
|
14
|
+
end
|
|
15
|
+
stimulus.controller("modal").action_payload(:toggle, as: :toggle_data)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
element :overlay, "fixed inset-0 bg-black/50 transition-opacity" do
|
|
19
|
+
stimulus.context("modal").action(:click, :close)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
element :panel, "relative bg-white rounded-lg shadow-xl transition-transform transform" do
|
|
23
|
+
dimension :size, default: :md do
|
|
24
|
+
variant :sm, "w-full max-w-sm p-4" do
|
|
25
|
+
classes "dark:text-slate-400", group: :dark_mode
|
|
26
|
+
classes "one two"
|
|
27
|
+
end
|
|
28
|
+
variant :md, "w-full max-w-md p-6"
|
|
29
|
+
variant :lg, "w-full max-w-lg p-8"
|
|
30
|
+
end
|
|
31
|
+
stimulus.context("modal").target("panel")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
element :title, "text-lg font-semibold text-gray-900"
|
|
35
|
+
element :body, "mt-2 text-sm text-gray-600"
|
|
36
|
+
element :close_button, "absolute top-2 right-2 p-1 text-gray-400 rounded-full hover:bg-gray-100 hover:text-gray-600" do
|
|
37
|
+
stimulus.context("modal").action(:click, :close)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
element :footer, "mt-4 pt-4 border-t flex justify-end"
|
|
41
|
+
element :confirm_button, "relative inline-flex items-center px-4 py-2 bg-blue-600 text-white font-semibold rounded-md hover:bg-blue-700" do
|
|
42
|
+
stimulus.controller("form-submission")
|
|
43
|
+
.action(:click, :submit)
|
|
44
|
+
.action_payload(:enter_pending_state, as: :pending_data)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
element :spinner, "absolute inset-0 flex items-center justify-center hidden"
|
|
48
|
+
|
|
49
|
+
action :toggle, method: :toggle do
|
|
50
|
+
element :overlay do
|
|
51
|
+
classes "hidden"
|
|
52
|
+
end
|
|
53
|
+
element :panel do
|
|
54
|
+
classes "hidden"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
action :lock, method: :add do
|
|
59
|
+
element :close_button do
|
|
60
|
+
classes "hidden"
|
|
61
|
+
end
|
|
62
|
+
element :panel do
|
|
63
|
+
data locked: true, reason: "processing"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
action :enter_pending_state, method: :add do
|
|
68
|
+
element :confirm_button do
|
|
69
|
+
classes "opacity-75 cursor-not-allowed"
|
|
70
|
+
end
|
|
71
|
+
element :spinner do
|
|
72
|
+
classes "flex"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def initialize(size: :md, open: false)
|
|
78
|
+
@ui = tailmix(size: size, open: open)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def lock!
|
|
82
|
+
@ui.action(:lock).apply!
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
puts "-" * 100
|
|
87
|
+
# puts ModalComponent.dev.docs
|
|
88
|
+
# puts ""
|
|
89
|
+
# puts "Scaffolds:"
|
|
90
|
+
# puts ""
|
|
91
|
+
# puts ModalComponent.dev.stimulus.scaffold
|
|
92
|
+
# puts ""
|
|
93
|
+
|
|
94
|
+
# >>>
|
|
95
|
+
#
|
|
96
|
+
# == Tailmix Docs for ModalComponent ==
|
|
97
|
+
# Signature: `initialize(open: true, size: :md)`
|
|
98
|
+
#
|
|
99
|
+
# Dimensions:
|
|
100
|
+
# - open (default: true)
|
|
101
|
+
# - true:
|
|
102
|
+
# - classes : "visible opacity-100"
|
|
103
|
+
# - false:
|
|
104
|
+
# - classes : "invisible opacity-0"
|
|
105
|
+
# - size (default: :md)
|
|
106
|
+
# - :sm:
|
|
107
|
+
# - classes : "w-full max-w-sm p-4"
|
|
108
|
+
# - classes (group: :dark_mode): "dark:text-slate-400"
|
|
109
|
+
# - classes : "one two"
|
|
110
|
+
# - :md:
|
|
111
|
+
# - classes : "w-full max-w-md p-6"
|
|
112
|
+
# - :lg:
|
|
113
|
+
# - classes : "w-full max-w-lg p-8"
|
|
114
|
+
#
|
|
115
|
+
# Actions:
|
|
116
|
+
# - :toggle
|
|
117
|
+
# - :lock
|
|
118
|
+
# - :enter_pending_state
|
|
119
|
+
#
|
|
120
|
+
# Stimulus:
|
|
121
|
+
# - on `modal` controller:
|
|
122
|
+
# - Targets: panel
|
|
123
|
+
# - Actions: close
|
|
124
|
+
#
|
|
125
|
+
# Stimulus:
|
|
126
|
+
# - on `form-submission` controller:
|
|
127
|
+
# - Actions: submit
|
|
128
|
+
#
|
|
129
|
+
# Scaffolds:
|
|
130
|
+
#
|
|
131
|
+
# // Generated by Tailmix for the "modal" controller
|
|
132
|
+
# // Path: app/javascript/controllers/modal_controller.js
|
|
133
|
+
# import { Controller } from "@hotwired/stimulus"
|
|
134
|
+
# import Tailmix from "tailmix"
|
|
135
|
+
#
|
|
136
|
+
# export default class extends Controller {
|
|
137
|
+
# static targets = ['panel']
|
|
138
|
+
# static values = { toggleData: Object }
|
|
139
|
+
#
|
|
140
|
+
# connect() {
|
|
141
|
+
# console.log("modal controller connected to", this.element);
|
|
142
|
+
# }
|
|
143
|
+
# toggle(event) {
|
|
144
|
+
# if (event) event.preventDefault();
|
|
145
|
+
# Tailmix.run({ config: this.toggleDataValue, controller: this });
|
|
146
|
+
# }
|
|
147
|
+
#
|
|
148
|
+
# close() {
|
|
149
|
+
# console.log('modal#close fired');
|
|
150
|
+
# }
|
|
151
|
+
# }
|
|
152
|
+
# ------------------------------------------------------------
|
|
153
|
+
#
|
|
154
|
+
# // Generated by Tailmix for the "form-submission" controller
|
|
155
|
+
# // Path: app/javascript/controllers/form-submission_controller.js
|
|
156
|
+
# import { Controller } from "@hotwired/stimulus"
|
|
157
|
+
# import Tailmix from "tailmix"
|
|
158
|
+
#
|
|
159
|
+
# export default class extends Controller {
|
|
160
|
+
# static targets = []
|
|
161
|
+
# static values = { pendingData: Object }
|
|
162
|
+
#
|
|
163
|
+
# connect() {
|
|
164
|
+
# console.log("form-submission controller connected to", this.element);
|
|
165
|
+
# }
|
|
166
|
+
# enterPendingState(event) {
|
|
167
|
+
# if (event) event.preventDefault();
|
|
168
|
+
# Tailmix.run({ config: this.pendingDataValue, controller: this });
|
|
169
|
+
# }
|
|
170
|
+
#
|
|
171
|
+
# submit() {
|
|
172
|
+
# console.log('form-submission#submit fired');
|
|
173
|
+
# }
|
|
174
|
+
# }
|
|
175
|
+
# ------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
modal = ModalComponent.new(size: :lg, open: true)
|
|
181
|
+
# modal.lock!
|
|
182
|
+
ui = modal.ui
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def stringify_keys(obj)
|
|
186
|
+
case obj
|
|
187
|
+
when Hash
|
|
188
|
+
obj.transform_keys(&:to_s).transform_values { |v| stringify_keys(v) }
|
|
189
|
+
when Array
|
|
190
|
+
obj.map { |v| stringify_keys(v) }
|
|
191
|
+
else
|
|
192
|
+
obj
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# puts "Definition:"
|
|
197
|
+
# puts JSON.pretty_generate(stringify_keys(ModalComponent.tailmix_definition.to_h))
|
|
198
|
+
# ui.action(:lock).apply!
|
|
199
|
+
|
|
200
|
+
ModalComponent.dev.elements.each do |element_name|
|
|
201
|
+
element = ui.send(element_name)
|
|
202
|
+
puts element_name
|
|
203
|
+
element.each_attribute do |attribute|
|
|
204
|
+
attribute.each do |key, value|
|
|
205
|
+
puts " #{key} :-> #{value}"
|
|
206
|
+
end
|
|
207
|
+
puts ""
|
|
208
|
+
end
|
|
209
|
+
end
|