tailmix 0.4.6 → 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/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 +2 -10
- data/lib/tailmix/definition/context_builder.rb +3 -2
- data/lib/tailmix/definition/contexts/element_builder.rb +15 -1
- data/lib/tailmix/definition/merger.rb +2 -1
- data/lib/tailmix/definition/result.rb +4 -3
- data/lib/tailmix/dev/docs.rb +31 -0
- data/lib/tailmix/runtime/context.rb +16 -1
- data/lib/tailmix/version.rb +1 -1
- metadata +14 -7
data/docs/05_cookbook.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Cookbook
|
|
2
|
+
|
|
3
|
+
This document contains practical recipes for building common UI components with Tailmix.
|
|
4
|
+
|
|
5
|
+
## Alert Component
|
|
6
|
+
|
|
7
|
+
Alerts are used to communicate a state that affects the entire system, feature, or page.
|
|
8
|
+
|
|
9
|
+
### 1. Component Definition
|
|
10
|
+
|
|
11
|
+
Here's the Ruby code for a flexible `AlertComponent`.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# app/components/alert_component.rb
|
|
15
|
+
class AlertComponent
|
|
16
|
+
include Tailmix
|
|
17
|
+
attr_reader :ui, :icon_svg, :message
|
|
18
|
+
|
|
19
|
+
def initialize(intent: :info, message:)
|
|
20
|
+
@ui = tailmix(intent: intent)
|
|
21
|
+
@message = message
|
|
22
|
+
@icon_svg = fetch_icon(intent) # Logic to get the correct SVG icon
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def fetch_icon(intent)
|
|
28
|
+
# ...
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
tailmix do
|
|
32
|
+
element :wrapper, "flex items-center p-4 text-sm border rounded-lg" do
|
|
33
|
+
dimension :intent, default: :info do
|
|
34
|
+
variant :info, "text-blue-800 bg-blue-50 border-blue-300"
|
|
35
|
+
variant :success, "text-green-800 bg-green-50 border-green-300"
|
|
36
|
+
variant :warning, "text-yellow-800 bg-yellow-50 border-yellow-300"
|
|
37
|
+
variant :danger, "text-red-800 bg-red-50 border-red-300"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
element :icon, "flex-shrink-0 w-5 h-5"
|
|
42
|
+
element :message_area, "ml-3"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### View Usage (ERB)
|
|
48
|
+
Instantiate the component in your controller or view and use the ui helper to render the elements.
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<%# Success Alert %>
|
|
52
|
+
<% success_alert = AlertComponent.new(intent: :success, message: "Your profile has been updated.") %>
|
|
53
|
+
|
|
54
|
+
<div <%= tag.attributes **success_alert.ui.wrapper %>>
|
|
55
|
+
<div <%= tag.attributes **success_alert.ui.icon %>>
|
|
56
|
+
<%= success_alert.icon_svg.html_safe %>
|
|
57
|
+
</div>
|
|
58
|
+
<div <%= tag.attributes **success_alert.ui.message_area %>>
|
|
59
|
+
<%= success_alert.message %>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
<%# Danger Alert %>
|
|
65
|
+
<% danger_alert = AlertComponent.new(intent: :danger, message: "Failed to delete the record.") %>
|
|
66
|
+
|
|
67
|
+
<div <%= tag.attributes **danger_alert.ui.wrapper %>>
|
|
68
|
+
<div <%= tag.attributes **danger_alert.ui.icon %>>
|
|
69
|
+
<%= danger_alert.icon_svg.html_safe %>
|
|
70
|
+
</div>
|
|
71
|
+
<div <%= tag.attributes **danger_alert.ui.message_area %>>
|
|
72
|
+
<%= danger_alert.message %>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### View Usage .arb (Ruby Arbre)
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# Success Alert
|
|
81
|
+
success_alert = AlertComponent.new(intent: :success, message: "Your profile has been updated.")
|
|
82
|
+
ui = success_alert.ui
|
|
83
|
+
|
|
84
|
+
div ui.wrapper do
|
|
85
|
+
div ui.icon do
|
|
86
|
+
success_alert.icon_svg.html_safe
|
|
87
|
+
end
|
|
88
|
+
div ui.message_area do
|
|
89
|
+
success_alert.message
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Danger Alert
|
|
94
|
+
danger_alert = AlertComponent.new(intent: :danger, message: "Failed to delete the record.")
|
|
95
|
+
ui = danger_alert.ui
|
|
96
|
+
|
|
97
|
+
div ui.wrapper do
|
|
98
|
+
div ui.icon do
|
|
99
|
+
danger_alert.icon_svg.html_safe
|
|
100
|
+
end
|
|
101
|
+
div ui.message_area do
|
|
102
|
+
danger_alert.message
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Badge Component
|
|
108
|
+
|
|
109
|
+
Badges are used for labeling, categorization, or highlighting small pieces of information. This recipe shows how to combine multiple dimensions like `size` and `color`.
|
|
110
|
+
|
|
111
|
+
### 1. Component Definition
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# app/components/badge_component.rb
|
|
115
|
+
class BadgeComponent
|
|
116
|
+
include Tailmix
|
|
117
|
+
attr_reader :ui, :text
|
|
118
|
+
|
|
119
|
+
def initialize(text, color: :gray, size: :sm)
|
|
120
|
+
@ui = tailmix(color: color, size: size)
|
|
121
|
+
@text = text
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
tailmix do
|
|
125
|
+
element :badge, "inline-flex items-center font-medium rounded-full" do
|
|
126
|
+
dimension :color, default: :gray do
|
|
127
|
+
variant :gray, "bg-gray-100 text-gray-600"
|
|
128
|
+
variant :success, "bg-green-100 text-green-700"
|
|
129
|
+
variant :warning, "bg-yellow-100 text-yellow-700"
|
|
130
|
+
variant :danger, "bg-red-100 text-red-700"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
dimension :size, default: :sm do
|
|
134
|
+
variant :sm, "px-2.5 py-0.5 text-xs"
|
|
135
|
+
variant :md, "px-3 py-1 text-sm"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### View Usage (ERB)
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<%# A medium-sized success badge %>
|
|
146
|
+
<% success_badge = BadgeComponent.new("Active", color: :success, size: :md) %>
|
|
147
|
+
<span <%= tag.attributes **success_badge.ui.badge %>>
|
|
148
|
+
<%= success_badge.text %>
|
|
149
|
+
</span>
|
|
150
|
+
|
|
151
|
+
<%# A small danger badge %>
|
|
152
|
+
<% danger_badge = BadgeComponent.new("Inactive", color: :danger, size: :sm) %>
|
|
153
|
+
<span <%= tag.attributes **danger_badge.ui.badge %>>
|
|
154
|
+
<%= danger_badge.text %>
|
|
155
|
+
</span>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### View Usage .arb (Ruby Arbre)
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# A medium-sized success badge
|
|
162
|
+
success_badge = BadgeComponent.new("Active", color: :success, size: :md)
|
|
163
|
+
ui = success_badge.ui
|
|
164
|
+
|
|
165
|
+
span ui.badge do
|
|
166
|
+
success_badge.text
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# A small danger badge
|
|
170
|
+
danger_badge = BadgeComponent.new("Inactive", color: :danger, size: :sm)
|
|
171
|
+
ui = danger_badge.ui
|
|
172
|
+
|
|
173
|
+
span ui.badge do
|
|
174
|
+
danger_badge.text
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Card Component
|
|
179
|
+
|
|
180
|
+
Cards are flexible containers for content. This recipe shows how to build a component with multiple parts (`header`, `body`, `footer`) using multiple `element` definitions.
|
|
181
|
+
|
|
182
|
+
### 1. Component Definition
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
# app/components/card_component.rb
|
|
186
|
+
class CardComponent
|
|
187
|
+
include Tailmix
|
|
188
|
+
attr_reader :ui
|
|
189
|
+
|
|
190
|
+
# We can control the footer's top border with an option
|
|
191
|
+
def initialize(with_divider: true)
|
|
192
|
+
@ui = tailmix(with_divider: with_divider)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
tailmix do
|
|
196
|
+
element :wrapper, "bg-white border rounded-lg shadow-sm"
|
|
197
|
+
element :header, "p-4 border-b"
|
|
198
|
+
element :body, "p-4"
|
|
199
|
+
element :footer, "p-4 bg-gray-50 rounded-b-lg" do
|
|
200
|
+
dimension :with_divider, default: true do
|
|
201
|
+
variant true, "border-t"
|
|
202
|
+
variant false, "" # No border
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### View Usage (ERB)
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<% card = CardComponent.new %>
|
|
213
|
+
|
|
214
|
+
<div <%= tag.attributes **card.ui.wrapper %>>
|
|
215
|
+
<div <%= tag.attributes **card.ui.header %>>
|
|
216
|
+
<h3 class="text-lg font-medium">Card Title</h3>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div <%= tag.attributes **card.ui.body %>>
|
|
220
|
+
<p>This is the main content of the card. It can contain any information you need to display.</p>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div <%= tag.attributes **card.ui.footer %>>
|
|
224
|
+
<button type="button">Action Button</button>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### View Usage .arb (Ruby Arbre)
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
# Card
|
|
233
|
+
card = CardComponent.new
|
|
234
|
+
ui = card.ui
|
|
235
|
+
|
|
236
|
+
div ui.wrapper do
|
|
237
|
+
div ui.header do
|
|
238
|
+
h3 "Card Title"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
div ui.body do
|
|
242
|
+
para "This is the main content of the card. It can contain any information you need to display."
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
div ui.footer do
|
|
246
|
+
button "Action Button"
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
```
|
data/examples/button.rb
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/tailmix"
|
|
4
|
+
require_relative "helpers"
|
|
5
|
+
|
|
6
|
+
class Button
|
|
7
|
+
include Tailmix
|
|
8
|
+
|
|
9
|
+
tailmix do
|
|
10
|
+
element :button do
|
|
11
|
+
dimension :intent, default: :primary do
|
|
12
|
+
variant :primary, "bg-blue-500"
|
|
13
|
+
variant :danger, "bg-red-500"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
dimension :size, default: :medium do
|
|
17
|
+
variant :medium, "p-4"
|
|
18
|
+
variant :small, "p-2"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
compound_variant on: { intent: :danger, size: :small } do
|
|
22
|
+
classes "font-bold"
|
|
23
|
+
data special: "true"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_reader :ui
|
|
29
|
+
def initialize(intent: :primary, size: :medium)
|
|
30
|
+
@ui = tailmix(intent: intent, size: size)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
puts "-" * 100
|
|
36
|
+
puts Button.dev.docs
|
|
37
|
+
|
|
38
|
+
# == Tailmix Docs for Button ==
|
|
39
|
+
# Signature: `initialize(intent: :primary, size: :medium)`
|
|
40
|
+
#
|
|
41
|
+
# Dimensions:
|
|
42
|
+
# - intent (default: :primary)
|
|
43
|
+
# - :primary:
|
|
44
|
+
# - classes : "bg-blue-500"
|
|
45
|
+
# - :danger:
|
|
46
|
+
# - classes : "bg-red-500"
|
|
47
|
+
# - size (default: :medium)
|
|
48
|
+
# - :medium:
|
|
49
|
+
# - classes : "p-4"
|
|
50
|
+
# - :small:
|
|
51
|
+
# - classes : "p-2"
|
|
52
|
+
#
|
|
53
|
+
# Compound Variants:
|
|
54
|
+
# - on element `:button`:
|
|
55
|
+
# - on: { intent: :danger, size: :small }
|
|
56
|
+
# - classes : "font-bold"
|
|
57
|
+
# - data: {:special=>"true"}
|
|
58
|
+
#
|
|
59
|
+
# No actions defined.
|
|
60
|
+
#
|
|
61
|
+
# button
|
|
62
|
+
# classes :-> bg-red-500 p-4
|
|
63
|
+
# data :-> {}
|
|
64
|
+
# aria :-> {}
|
|
65
|
+
# tailmix :-> {"data-tailmix-button"=>"intent:danger,size:medium"}
|
|
66
|
+
|
|
67
|
+
not_compound_component = Button.new(intent: :danger, size: :medium)
|
|
68
|
+
print_component_ui(not_compound_component)
|
|
69
|
+
# button
|
|
70
|
+
# classes :-> bg-red-500 p-4
|
|
71
|
+
# data :-> {}
|
|
72
|
+
# aria :-> {}
|
|
73
|
+
# tailmix :-> {"data-tailmix-button"=>"intent:danger,size:medium"}
|
|
74
|
+
|
|
75
|
+
compound_component = Button.new(intent: :danger, size: :small)
|
|
76
|
+
print_component_ui(compound_component)
|
|
77
|
+
# button
|
|
78
|
+
# classes :-> bg-red-500 p-2 font-bold
|
|
79
|
+
# data :-> {"data-special"=>"true"}
|
|
80
|
+
# aria :-> {}
|
|
81
|
+
# tailmix :-> {"data-tailmix-button"=>"intent:danger,size:small"}
|
data/examples/helpers.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
def stringify_keys(obj)
|
|
4
|
+
case obj
|
|
5
|
+
when Hash
|
|
6
|
+
obj.transform_keys(&:to_s).transform_values { |v| stringify_keys(v) }
|
|
7
|
+
when Array
|
|
8
|
+
obj.map { |v| stringify_keys(v) }
|
|
9
|
+
else
|
|
10
|
+
obj
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def print_component_ui(component_instance)
|
|
15
|
+
component_instance.class.dev.elements.each do |element_name|
|
|
16
|
+
element = component_instance.ui.send(element_name)
|
|
17
|
+
puts element_name
|
|
18
|
+
element.each_attribute do |attribute|
|
|
19
|
+
attribute.each do |key, value|
|
|
20
|
+
puts " #{key} :-> #{value}"
|
|
21
|
+
end
|
|
22
|
+
puts ""
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/examples/modal_component.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../lib/tailmix"
|
|
4
|
+
require_relative "helpers"
|
|
4
5
|
|
|
5
6
|
class ModalComponent
|
|
6
7
|
include Tailmix
|
|
@@ -182,16 +183,7 @@ modal = ModalComponent.new(size: :lg, open: true)
|
|
|
182
183
|
ui = modal.ui
|
|
183
184
|
|
|
184
185
|
|
|
185
|
-
|
|
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
|
|
186
|
+
|
|
195
187
|
|
|
196
188
|
# puts "Definition:"
|
|
197
189
|
# puts JSON.pretty_generate(stringify_keys(ModalComponent.tailmix_definition.to_h))
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "contexts/action_builder"
|
|
4
4
|
require_relative "contexts/element_builder"
|
|
5
|
+
require_relative "contexts/variant_builder"
|
|
5
6
|
|
|
6
7
|
module Tailmix
|
|
7
8
|
module Definition
|
|
@@ -31,9 +32,9 @@ module Tailmix
|
|
|
31
32
|
def build_definition
|
|
32
33
|
Definition::Result::Context.new(
|
|
33
34
|
elements: @elements.transform_values(&:build_definition).freeze,
|
|
34
|
-
actions: @actions.transform_values(&:build_definition).freeze
|
|
35
|
+
actions: @actions.transform_values(&:build_definition).freeze,
|
|
35
36
|
)
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
|
-
end
|
|
40
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "attribute_builder"
|
|
4
4
|
require_relative "stimulus_builder"
|
|
5
5
|
require_relative "dimension_builder"
|
|
6
|
+
require_relative "variant_builder"
|
|
6
7
|
|
|
7
8
|
module Tailmix
|
|
8
9
|
module Definition
|
|
@@ -11,6 +12,7 @@ module Tailmix
|
|
|
11
12
|
def initialize(name)
|
|
12
13
|
@name = name
|
|
13
14
|
@dimensions = {}
|
|
15
|
+
@compound_variants = []
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def attributes
|
|
@@ -27,12 +29,24 @@ module Tailmix
|
|
|
27
29
|
@dimensions[name.to_sym] = builder.build_dimension
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
def compound_variant(on:, &block)
|
|
33
|
+
builder = VariantBuilder.new
|
|
34
|
+
builder.instance_eval(&block)
|
|
35
|
+
|
|
36
|
+
@compound_variants << {
|
|
37
|
+
on: on,
|
|
38
|
+
modifications: builder.build_variant
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
30
43
|
def build_definition
|
|
31
44
|
Definition::Result::Element.new(
|
|
32
45
|
name: @name,
|
|
33
46
|
attributes: attributes.build_definition,
|
|
34
47
|
stimulus: stimulus.build_definition,
|
|
35
|
-
dimensions: @dimensions.freeze
|
|
48
|
+
dimensions: @dimensions.freeze,
|
|
49
|
+
compound_variants: @compound_variants.freeze
|
|
36
50
|
)
|
|
37
51
|
end
|
|
38
52
|
end
|
|
@@ -47,7 +47,8 @@ module Tailmix
|
|
|
47
47
|
name: parent_el.name,
|
|
48
48
|
attributes: merge_attributes(parent_el.attributes, child_el.attributes),
|
|
49
49
|
dimensions: merge_dimensions(parent_el.dimensions, child_el.dimensions),
|
|
50
|
-
stimulus: merge_stimulus(parent_el.stimulus, child_el.stimulus)
|
|
50
|
+
stimulus: merge_stimulus(parent_el.stimulus, child_el.stimulus),
|
|
51
|
+
compound_variants: parent_el.compound_variants + child_el.compound_variants
|
|
51
52
|
)
|
|
52
53
|
end
|
|
53
54
|
|
|
@@ -7,12 +7,12 @@ module Tailmix
|
|
|
7
7
|
def to_h
|
|
8
8
|
{
|
|
9
9
|
elements: elements.transform_values(&:to_h),
|
|
10
|
-
actions: actions.transform_values(&:to_h)
|
|
10
|
+
actions: actions.transform_values(&:to_h),
|
|
11
11
|
}
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
Element = Struct.new(:name, :attributes, :dimensions, :stimulus, keyword_init: true) do
|
|
15
|
+
Element = Struct.new(:name, :attributes, :dimensions, :stimulus, :compound_variants, keyword_init: true) do
|
|
16
16
|
def to_h
|
|
17
17
|
{
|
|
18
18
|
name: name,
|
|
@@ -29,7 +29,8 @@ module Tailmix
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end,
|
|
32
|
-
stimulus: stimulus.to_h
|
|
32
|
+
stimulus: stimulus.to_h,
|
|
33
|
+
compound_variants: compound_variants
|
|
33
34
|
}
|
|
34
35
|
end
|
|
35
36
|
end
|
data/lib/tailmix/dev/docs.rb
CHANGED
|
@@ -19,6 +19,8 @@ module Tailmix
|
|
|
19
19
|
|
|
20
20
|
output << generate_dimensions_docs
|
|
21
21
|
output << ""
|
|
22
|
+
output << generate_compound_variants_docs # <-- Наш новый метод
|
|
23
|
+
output << ""
|
|
22
24
|
output << generate_actions_docs
|
|
23
25
|
output << ""
|
|
24
26
|
output << generate_stimulus_docs
|
|
@@ -60,6 +62,35 @@ module Tailmix
|
|
|
60
62
|
output.join("\n")
|
|
61
63
|
end
|
|
62
64
|
|
|
65
|
+
def generate_compound_variants_docs
|
|
66
|
+
output = []
|
|
67
|
+
|
|
68
|
+
compound_variants_by_element = @definition.elements.values.select do |el|
|
|
69
|
+
el.compound_variants.any?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if compound_variants_by_element.any?
|
|
73
|
+
output << "Compound Variants:"
|
|
74
|
+
compound_variants_by_element.each do |element|
|
|
75
|
+
output << " - on element `:#{element.name}`:"
|
|
76
|
+
element.compound_variants.each do |cv|
|
|
77
|
+
conditions = cv[:on].map { |k, v| "#{k}: :#{v}" }.join(", ")
|
|
78
|
+
output << " - on: { #{conditions} }"
|
|
79
|
+
|
|
80
|
+
modifications = cv[:modifications]
|
|
81
|
+
modifications.class_groups.each do |group|
|
|
82
|
+
label = group[:options][:group] ? "(group: :#{group[:options][:group]})" : ""
|
|
83
|
+
output << " - classes #{label}: \"#{group[:classes].join(' ')}\""
|
|
84
|
+
end
|
|
85
|
+
output << " - data: #{modifications.data.inspect}" if modifications.data.any?
|
|
86
|
+
output << " - aria: #{modifications.aria.inspect}" if modifications.aria.any?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
output.join("\n")
|
|
92
|
+
end
|
|
93
|
+
|
|
63
94
|
def generate_actions_docs
|
|
64
95
|
output = []
|
|
65
96
|
actions = @definition.actions
|
|
@@ -46,7 +46,7 @@ module Tailmix
|
|
|
46
46
|
{ class: element_def.attributes.classes },
|
|
47
47
|
element_name: element_def.name,
|
|
48
48
|
variant_string: variant_string,
|
|
49
|
-
|
|
49
|
+
)
|
|
50
50
|
|
|
51
51
|
element_def.dimensions.each do |name, dim_def|
|
|
52
52
|
value = dimensions.fetch(name, dim_def[:default])
|
|
@@ -60,6 +60,21 @@ module Tailmix
|
|
|
60
60
|
attributes.aria.merge!(variant_def.aria)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
element_def.compound_variants.each do |cv|
|
|
64
|
+
conditions = cv[:on]
|
|
65
|
+
modifications = cv[:modifications]
|
|
66
|
+
|
|
67
|
+
match = conditions.all? do |key, value|
|
|
68
|
+
dimensions[key] == value
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if match
|
|
72
|
+
attributes.classes.add(modifications.classes)
|
|
73
|
+
attributes.data.merge!(modifications.data)
|
|
74
|
+
attributes.aria.merge!(modifications.aria)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
63
78
|
Stimulus::Compiler.call(
|
|
64
79
|
definition: element_def.stimulus,
|
|
65
80
|
data_map: attributes.data,
|
data/lib/tailmix/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tailmix
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Fokin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-08-
|
|
11
|
+
date: 2025-08-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -52,10 +52,10 @@ dependencies:
|
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '3.0'
|
|
55
|
-
description: Tailmix provides a powerful DSL to define component
|
|
56
|
-
variants and
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
description: Tailmix provides a powerful DSL to define component attribute schemas,
|
|
56
|
+
including variants, compound variants, and states. It enables clean, co-located
|
|
57
|
+
presentational logic (CSS classes, data attributes, ARIA roles) and offers a rich
|
|
58
|
+
runtime API for dynamic manipulation, perfect for Hotwire/Turbo.
|
|
59
59
|
email:
|
|
60
60
|
- alexander.s.fokin@gmail.com
|
|
61
61
|
executables: []
|
|
@@ -73,7 +73,14 @@ files:
|
|
|
73
73
|
- app/javascript/tailmix/mutator.js
|
|
74
74
|
- app/javascript/tailmix/runner.js
|
|
75
75
|
- app/javascript/tailmix/stimulus_adapter.js
|
|
76
|
+
- docs/01_getting_started.md
|
|
77
|
+
- docs/02_dsl_reference.md
|
|
78
|
+
- docs/03_advanced_usage.md
|
|
79
|
+
- docs/04_client_side_bridge.md
|
|
80
|
+
- docs/05_cookbook.md
|
|
76
81
|
- examples/_modal_component.arb
|
|
82
|
+
- examples/button.rb
|
|
83
|
+
- examples/helpers.rb
|
|
77
84
|
- examples/modal_component.rb
|
|
78
85
|
- lib/generators/tailmix/install_generator.rb
|
|
79
86
|
- lib/tailmix.rb
|
|
@@ -132,5 +139,5 @@ requirements: []
|
|
|
132
139
|
rubygems_version: 3.5.22
|
|
133
140
|
signing_key:
|
|
134
141
|
specification_version: 4
|
|
135
|
-
summary: A declarative
|
|
142
|
+
summary: A declarative, state-driven attribute manager for Ruby UI components.
|
|
136
143
|
test_files: []
|