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
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
|
|
@@ -9,8 +10,8 @@ class ModalComponent
|
|
|
9
10
|
tailmix do
|
|
10
11
|
element :base, "fixed inset-0 z-50 flex items-center justify-center" do
|
|
11
12
|
dimension :open, default: true do
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
variant true, "visible opacity-100"
|
|
14
|
+
variant false, "invisible opacity-0"
|
|
14
15
|
end
|
|
15
16
|
stimulus.controller("modal").action_payload(:toggle, as: :toggle_data)
|
|
16
17
|
end
|
|
@@ -21,9 +22,12 @@ class ModalComponent
|
|
|
21
22
|
|
|
22
23
|
element :panel, "relative bg-white rounded-lg shadow-xl transition-transform transform" do
|
|
23
24
|
dimension :size, default: :md do
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
variant :sm, "w-full max-w-sm p-4" do
|
|
26
|
+
classes "dark:text-slate-400", group: :dark_mode
|
|
27
|
+
classes "one two"
|
|
28
|
+
end
|
|
29
|
+
variant :md, "w-full max-w-md p-6"
|
|
30
|
+
variant :lg, "w-full max-w-lg p-8"
|
|
27
31
|
end
|
|
28
32
|
stimulus.context("modal").target("panel")
|
|
29
33
|
end
|
|
@@ -81,12 +85,12 @@ class ModalComponent
|
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
puts "-" * 100
|
|
84
|
-
puts ModalComponent.dev.docs
|
|
85
|
-
puts ""
|
|
86
|
-
puts "Scaffolds:"
|
|
87
|
-
puts ""
|
|
88
|
-
puts ModalComponent.dev.stimulus.scaffold
|
|
89
|
-
puts ""
|
|
88
|
+
# puts ModalComponent.dev.docs
|
|
89
|
+
# puts ""
|
|
90
|
+
# puts "Scaffolds:"
|
|
91
|
+
# puts ""
|
|
92
|
+
# puts ModalComponent.dev.stimulus.scaffold
|
|
93
|
+
# puts ""
|
|
90
94
|
|
|
91
95
|
# >>>
|
|
92
96
|
#
|
|
@@ -95,12 +99,19 @@ puts ""
|
|
|
95
99
|
#
|
|
96
100
|
# Dimensions:
|
|
97
101
|
# - open (default: true)
|
|
98
|
-
# - true:
|
|
99
|
-
#
|
|
102
|
+
# - true:
|
|
103
|
+
# - classes : "visible opacity-100"
|
|
104
|
+
# - false:
|
|
105
|
+
# - classes : "invisible opacity-0"
|
|
100
106
|
# - size (default: :md)
|
|
101
|
-
# - :sm:
|
|
102
|
-
#
|
|
103
|
-
#
|
|
107
|
+
# - :sm:
|
|
108
|
+
# - classes : "w-full max-w-sm p-4"
|
|
109
|
+
# - classes (group: :dark_mode): "dark:text-slate-400"
|
|
110
|
+
# - classes : "one two"
|
|
111
|
+
# - :md:
|
|
112
|
+
# - classes : "w-full max-w-md p-6"
|
|
113
|
+
# - :lg:
|
|
114
|
+
# - classes : "w-full max-w-lg p-8"
|
|
104
115
|
#
|
|
105
116
|
# Actions:
|
|
106
117
|
# - :toggle
|
|
@@ -167,14 +178,24 @@ puts ""
|
|
|
167
178
|
|
|
168
179
|
|
|
169
180
|
|
|
170
|
-
|
|
181
|
+
modal = ModalComponent.new(size: :lg, open: true)
|
|
171
182
|
# modal.lock!
|
|
172
|
-
|
|
183
|
+
ui = modal.ui
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
173
187
|
|
|
174
188
|
# puts "Definition:"
|
|
175
|
-
#
|
|
176
|
-
# pp ModalComponent.tailmix_definition.to_h
|
|
177
|
-
# ui.overlay.each do |key, value|
|
|
178
|
-
# puts "#{key} :-> #{value.inspect.to_s[0, 75]}..."
|
|
179
|
-
# end
|
|
189
|
+
# puts JSON.pretty_generate(stringify_keys(ModalComponent.tailmix_definition.to_h))
|
|
180
190
|
# ui.action(:lock).apply!
|
|
191
|
+
|
|
192
|
+
ModalComponent.dev.elements.each do |element_name|
|
|
193
|
+
element = ui.send(element_name)
|
|
194
|
+
puts element_name
|
|
195
|
+
element.each_attribute do |attribute|
|
|
196
|
+
attribute.each do |key, value|
|
|
197
|
+
puts " #{key} :-> #{value}"
|
|
198
|
+
end
|
|
199
|
+
puts ""
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
# Stores the configuration for the Tailmix gem.
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :element_selector_attribute, :dev_mode_attributes
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@element_selector_attribute = nil
|
|
10
|
+
@dev_mode_attributes = defined?(Rails) && Rails.env.development?
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -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
|
|
@@ -10,13 +10,13 @@ module Tailmix
|
|
|
10
10
|
@commands = []
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def classes(classes_string,
|
|
13
|
+
def classes(classes_string, options = {})
|
|
14
|
+
method = options.fetch(:method, @default_method)
|
|
14
15
|
@commands << { field: :classes, method: method, payload: classes_string }
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def data(data_hash)
|
|
18
19
|
operation = data_hash.delete(:method) || @default_method
|
|
19
|
-
|
|
20
20
|
@commands << { field: :data, method: operation, payload: data_hash }
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
attr_reader :options
|
|
3
|
+
require_relative "variant_builder"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
module Tailmix
|
|
6
|
+
module Definition
|
|
7
|
+
module Contexts
|
|
8
|
+
class DimensionBuilder
|
|
9
|
+
def initialize(default: nil)
|
|
10
|
+
@variants = {}
|
|
11
|
+
@default = default
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def variant(name, classes = "", data: {}, aria: {}, &block)
|
|
15
|
+
builder = VariantBuilder.new
|
|
16
|
+
builder.classes(classes) if classes && !classes.empty?
|
|
17
|
+
builder.data(data)
|
|
18
|
+
builder.aria(aria)
|
|
19
|
+
|
|
20
|
+
builder.instance_eval(&block) if block
|
|
21
|
+
|
|
22
|
+
@variants[name] = builder.build_variant
|
|
23
|
+
end
|
|
9
24
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
def build_dimension
|
|
26
|
+
{
|
|
27
|
+
default: @default,
|
|
28
|
+
variants: @variants.freeze
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
14
32
|
end
|
|
15
33
|
end
|
|
16
34
|
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
|
|
@@ -22,17 +24,29 @@ module Tailmix
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def dimension(name, default: nil, &block)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dimensions[name.to_sym] =
|
|
27
|
+
builder = Contexts::DimensionBuilder.new(default: default)
|
|
28
|
+
builder.instance_eval(&block)
|
|
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
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Definition
|
|
5
|
+
module Contexts
|
|
6
|
+
class VariantBuilder
|
|
7
|
+
def initialize
|
|
8
|
+
@class_groups = []
|
|
9
|
+
@data = {}
|
|
10
|
+
@aria = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def classes(class_string, options = {})
|
|
14
|
+
@class_groups << { classes: class_string.to_s.split, options: options }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def data(hash)
|
|
18
|
+
@data.merge!(hash)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def aria(hash)
|
|
22
|
+
@aria.merge!(hash)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def build_variant
|
|
26
|
+
Definition::Result::Variant.new(
|
|
27
|
+
class_groups: @class_groups.freeze,
|
|
28
|
+
data: @data.freeze,
|
|
29
|
+
aria: @aria.freeze
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
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
|
|
|
@@ -63,28 +64,21 @@ module Tailmix
|
|
|
63
64
|
Result::Stimulus.new(definitions: combined_definitions)
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
private
|
|
67
|
-
|
|
68
67
|
def merge_dimensions(parent_dims, child_dims)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
all_keys = parent_dims.keys | child_dims.keys
|
|
69
|
+
|
|
70
|
+
all_keys.each_with_object({}) do |key, merged|
|
|
71
|
+
parent_val = parent_dims[key]
|
|
72
|
+
child_val = child_dims[key]
|
|
73
|
+
|
|
74
|
+
if parent_val && child_val
|
|
75
|
+
merged_variants = parent_val.fetch(:variants, {}).merge(child_val.fetch(:variants, {}))
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
child_hash.each_with_object(parent_hash.dup) do |(key, child_val), new_hash|
|
|
80
|
-
parent_val = new_hash[key]
|
|
77
|
+
default = child_val.key?(:default) ? child_val[:default] : parent_val[:default]
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
deep_merge(parent_val, child_val, &block)
|
|
84
|
-
elsif block_given? && new_hash.key?(key)
|
|
85
|
-
block.call(key, parent_val, child_val)
|
|
79
|
+
merged[key] = { default: default, variants: merged_variants }
|
|
86
80
|
else
|
|
87
|
-
child_val
|
|
81
|
+
merged[key] = parent_val || child_val
|
|
88
82
|
end
|
|
89
83
|
end
|
|
90
84
|
end
|