class_variants 0.0.8 → 1.0.0
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/lib/class_variants/configuration.rb +11 -0
- data/lib/class_variants/helper.rb +23 -0
- data/lib/class_variants/instance.rb +80 -38
- data/lib/class_variants/version.rb +1 -1
- data/lib/class_variants.rb +10 -0
- data/lib/generators/class_variants/install/USAGE +2 -0
- data/lib/generators/class_variants/install/install_generator.rb +11 -0
- data/lib/generators/class_variants/install/templates/class_variants.rb.tt +8 -0
- data/readme.md +209 -11
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c8fd556b85ee91d2d2709e66af72e1d4017c5a20ba5884c49eed0c755f1a593
|
4
|
+
data.tar.gz: c6f77333c70953510eb23ab94846c2b2f9d1ae1a8b185c289ee3530060546651
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9f77343da5f6e521cd8a895cef3683a5fc65823b8df3f1f84d03adf38099fb6f51cfc191e24111d35f7ac95ef697e6a61b6dc37a547c50b4732e0863e78631f
|
7
|
+
data.tar.gz: 5a0db3c1db7f88ccc689fbaadc514aa04d73ef405e1ae05755a55918d16d1f1179ff9c736b11402da99e57917c393fd830801f6d78b87c07964d9dc951ac3f98
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ClassVariants
|
2
|
+
module Helper
|
3
|
+
module ClassMethods
|
4
|
+
def class_variants(...)
|
5
|
+
@_class_variants_instance = ClassVariants.build(...)
|
6
|
+
end
|
7
|
+
|
8
|
+
def _class_variants_instance
|
9
|
+
@_class_variants_instance
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
def class_variants(...)
|
18
|
+
raise "You must configure class_variants in class definition" unless self.class._class_variants_instance
|
19
|
+
|
20
|
+
self.class._class_variants_instance.render(...)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,66 +1,108 @@
|
|
1
1
|
module ClassVariants
|
2
2
|
class Instance
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
warn <<~MSG unless compoundVariants.empty?
|
13
|
-
(ClassVariants) DEPRECATION WARNING: Use of `compoundVariants` keyword argument is deprecated
|
14
|
-
and will be removed in the next version. Use the `compound_variant` instead.
|
15
|
-
MSG
|
16
|
-
|
17
|
-
@base = base || classes
|
18
|
-
@variants = expand_boolean_variants(variants)
|
19
|
-
@compound_variants = compound_variants.empty? ? compoundVariants : compound_variants
|
20
|
-
@defaults = defaults
|
3
|
+
def initialize(**options, &block)
|
4
|
+
raise ArgumentError, "Use of hash config and code block is not supported" if !options.empty? && block_given?
|
5
|
+
|
6
|
+
@base = options.empty? ? {} : {default: options.fetch(:base, nil)}
|
7
|
+
@variants = expand_variants(options.fetch(:variants, {})) + expand_compound_variants(options.fetch(:compound_variants, []))
|
8
|
+
@defaults = options.fetch(:defaults, {})
|
9
|
+
|
10
|
+
instance_eval(&block) if block_given?
|
21
11
|
end
|
22
|
-
# rubocop:enable Naming/VariableName
|
23
12
|
|
24
|
-
def render(**overrides)
|
13
|
+
def render(slot = :default, **overrides)
|
14
|
+
classes = overrides.delete(:class)
|
15
|
+
|
25
16
|
# Start with our default classes
|
26
|
-
result = [@base]
|
17
|
+
result = [@base[slot]]
|
27
18
|
|
28
19
|
# Then merge the passed in overrides on top of the defaults
|
29
|
-
|
20
|
+
criteria = @defaults.merge(overrides)
|
30
21
|
|
31
|
-
|
32
|
-
|
33
|
-
result << @variants.dig(variant_type, variant)
|
34
|
-
end
|
22
|
+
@variants.each do |candidate|
|
23
|
+
next unless candidate[:slot] == slot
|
35
24
|
|
36
|
-
|
37
|
-
|
38
|
-
result << compound_variant[:class]
|
25
|
+
if (candidate.keys - [:class, :slot]).all? { |key| criteria[key] == candidate[key] }
|
26
|
+
result << candidate[:class]
|
39
27
|
end
|
40
28
|
end
|
41
29
|
|
30
|
+
# add the passed in classes to the result
|
31
|
+
result << classes
|
32
|
+
|
42
33
|
# Compact out any nil values we may have dug up
|
43
34
|
result.compact!
|
44
35
|
|
45
36
|
# Return the final token list
|
46
|
-
result.join
|
37
|
+
with_classess_processor(result.join(" "))
|
47
38
|
end
|
48
39
|
|
49
40
|
private
|
50
41
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
42
|
+
def base(klass = nil, &block)
|
43
|
+
raise ArgumentError, "Use of positional argument and code block is not supported" if klass && block_given?
|
44
|
+
|
45
|
+
if block_given?
|
46
|
+
with_slots(&block).each do |slot|
|
47
|
+
@base[slot[:slot]] = slot[:class]
|
48
|
+
end
|
49
|
+
else
|
50
|
+
@base[:default] = klass
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def variant(**options, &block)
|
55
|
+
raise ArgumentError, "Use of class option and code block is not supported" if options.key?(:class) && block_given?
|
56
|
+
|
57
|
+
if block_given?
|
58
|
+
with_slots(&block).each do |slot|
|
59
|
+
@variants << options.merge(slot)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
@variants << options.merge(slot: :default)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def defaults(**options)
|
67
|
+
@defaults = options
|
68
|
+
end
|
69
|
+
|
70
|
+
def slot(name = :default, **options)
|
71
|
+
raise ArgumentError, "class option is required" unless options.key?(:class)
|
72
|
+
|
73
|
+
@slots << options.merge(slot: name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def with_slots
|
77
|
+
@slots = []
|
78
|
+
yield
|
79
|
+
@slots
|
80
|
+
end
|
81
|
+
|
82
|
+
def expand_variants(variants)
|
83
|
+
variants.flat_map do |property, values|
|
84
|
+
case values
|
54
85
|
when String
|
55
|
-
|
56
|
-
{s_key.delete_prefix("!").to_sym => {!s_key.start_with?("!") => value}}
|
86
|
+
{property.to_s.delete_prefix("!").to_sym => !property.to_s.start_with?("!"), :class => values, :slot => :default}
|
57
87
|
else
|
58
|
-
|
88
|
+
values.map do |key, value|
|
89
|
+
{property => key, :class => value, :slot => :default}
|
90
|
+
end
|
59
91
|
end
|
60
92
|
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def expand_compound_variants(compound_variants)
|
96
|
+
compound_variants.map do |compound_variant|
|
97
|
+
compound_variant.merge(slot: :default)
|
98
|
+
end
|
99
|
+
end
|
61
100
|
|
62
|
-
|
63
|
-
|
101
|
+
def with_classess_processor(classes)
|
102
|
+
if ClassVariants.configuration.process_classes_with.respond_to?(:call)
|
103
|
+
ClassVariants.configuration.process_classes_with.call(classes)
|
104
|
+
else
|
105
|
+
classes
|
64
106
|
end
|
65
107
|
end
|
66
108
|
end
|
data/lib/class_variants.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
1
1
|
require "class_variants/version"
|
2
2
|
require "class_variants/action_view/helpers"
|
3
|
+
require "class_variants/configuration"
|
3
4
|
require "class_variants/instance"
|
5
|
+
require "class_variants/helper"
|
4
6
|
require "class_variants/railtie" if defined?(Rails)
|
5
7
|
|
6
8
|
module ClassVariants
|
7
9
|
class << self
|
10
|
+
def configuration
|
11
|
+
@configuration ||= Configuration.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def configure(&block)
|
15
|
+
yield(configuration)
|
16
|
+
end
|
17
|
+
|
8
18
|
def build(...)
|
9
19
|
Instance.new(...)
|
10
20
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ClassVariants
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
def copy_initializer
|
7
|
+
template "class_variants.rb", "config/initializers/class_variants.rb"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/readme.md
CHANGED
@@ -38,32 +38,34 @@ We create an object from the class or helper where we define the configuration u
|
|
38
38
|
1. The `compound_variants` keyword argument where we declare the compound variants with their conditions and classes
|
39
39
|
1. The `defaults` keyword argument (optional) where we declare the default value for each variant.
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
Below we implement the [button component](https://tailwindui.com/components/application-ui/elements/buttons) from Tailwind UI.
|
41
|
+
Below we'll implement the [button component](https://tailwindui.com/components/application-ui/elements/buttons) from Tailwind UI.
|
44
42
|
|
45
43
|
```ruby
|
46
44
|
# Define the variants and defaults
|
47
45
|
button_classes = ClassVariants.build(
|
48
46
|
base: "inline-flex items-center rounded border border-transparent font-medium text-white hover:text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2",
|
49
47
|
variants: {
|
48
|
+
color: {
|
49
|
+
indigo: "bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500",
|
50
|
+
red: "bg-red-600 hover:bg-red-700 focus:ring-red-500",
|
51
|
+
blue: "bg-blue-600 hover:bg-blue-700 focus:ring-blue-500",
|
52
|
+
},
|
50
53
|
size: {
|
51
54
|
sm: "px-2.5 py-1.5 text-xs",
|
52
55
|
md: "px-3 py-2 text-sm",
|
53
56
|
lg: "px-4 py-2 text-sm",
|
54
57
|
xl: "px-4 py-2 text-base",
|
55
58
|
},
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
},
|
59
|
+
compound_variants: [
|
60
|
+
{ color: :red, border: true, class: "border-red-800" },
|
61
|
+
{ color: :blue, border: true, class: "border-blue-800" }
|
62
|
+
]
|
61
63
|
# A variant whose value is a string will be expanded into a hash that looks
|
62
64
|
# like { true => "classes" }
|
63
|
-
|
65
|
+
icon: "w-full justify-center",
|
64
66
|
# Unless the key starts with !, in which case it will expand into
|
65
67
|
# { false => "classes" }
|
66
|
-
"!
|
68
|
+
"!icon": "w-auto",
|
67
69
|
},
|
68
70
|
defaults: {
|
69
71
|
size: :md,
|
@@ -78,7 +80,7 @@ button_classes.render
|
|
78
80
|
button_classes.render(color: :red, size: :xl, icon: true)
|
79
81
|
```
|
80
82
|
|
81
|
-
|
83
|
+
## Compound Variants
|
82
84
|
|
83
85
|
```ruby
|
84
86
|
button_classes = ClassVariants.build(
|
@@ -100,6 +102,172 @@ button_classes.render(color: :red) # => "inline-flex items-center rounded bg-red
|
|
100
102
|
button_classes.render(color: :red, border: true) # => "inline-flex items-center rounded bg-red-600 border border-red-600"
|
101
103
|
```
|
102
104
|
|
105
|
+
## Override classes with `render`
|
106
|
+
|
107
|
+
We can also override the builder classes in the `render` method.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
button_classes = ClassVariants.build(
|
111
|
+
base: "inline-flex items-center rounded",
|
112
|
+
variants: { ... },
|
113
|
+
)
|
114
|
+
|
115
|
+
button_classes.render(color: :red, class: "block")
|
116
|
+
```
|
117
|
+
|
118
|
+
Now, the `block` class will be appended to the classes bus.
|
119
|
+
|
120
|
+
If you're using the [`tailwind_merge`](#tailwind_merge) plugin it will override the `inline-flex` class.
|
121
|
+
|
122
|
+
## Block configurations
|
123
|
+
|
124
|
+
You might have scenarios where you have more advanced conditionals and you'd like to configure the classes using the block notation.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
alert_classes = ClassVariants.build do
|
128
|
+
# base
|
129
|
+
base "..."
|
130
|
+
|
131
|
+
# variant
|
132
|
+
variant color: :red, class: "..."
|
133
|
+
|
134
|
+
# compound variant
|
135
|
+
variant type: :button, color: :red, class: "..."
|
136
|
+
|
137
|
+
# defaults
|
138
|
+
defaults color: :red, type: :button
|
139
|
+
end
|
140
|
+
|
141
|
+
# usage
|
142
|
+
alert_classes.render(color: :red, type: :button)
|
143
|
+
```
|
144
|
+
|
145
|
+
## Slots
|
146
|
+
|
147
|
+
You might have components which have multiple slots or places where you'd like to use conditional classes.
|
148
|
+
`class_variants` supports that through slots.
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
# Example
|
152
|
+
|
153
|
+
alert_classes = ClassVariants.build do
|
154
|
+
# base with slots
|
155
|
+
base do
|
156
|
+
slot :head, class: "..."
|
157
|
+
slot :body, class: "..."
|
158
|
+
end
|
159
|
+
|
160
|
+
# variant with slots
|
161
|
+
variant color: :red do
|
162
|
+
slot :head, class: "..."
|
163
|
+
slot :body, class: "..."
|
164
|
+
end
|
165
|
+
|
166
|
+
# compound variant with slots
|
167
|
+
variant type: :button, color: :red do
|
168
|
+
slot :head, class: "..."
|
169
|
+
slot :body, class: "..."
|
170
|
+
end
|
171
|
+
|
172
|
+
# set defaults
|
173
|
+
defaults color: :red, type: :button
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
```erb
|
178
|
+
<div>
|
179
|
+
<div class="<%= alert_classes.render(:head) %>">
|
180
|
+
Head of alert
|
181
|
+
</div>
|
182
|
+
<div class="<%= alert_classes.render(:body) %>">
|
183
|
+
Body of alert
|
184
|
+
</div>
|
185
|
+
</div>
|
186
|
+
```
|
187
|
+
|
188
|
+
## Full API
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# Configuration
|
192
|
+
alert_classes = ClassVariants.build(
|
193
|
+
base: "...",
|
194
|
+
variants: {
|
195
|
+
color: {
|
196
|
+
red: "...",
|
197
|
+
black: "..."
|
198
|
+
},
|
199
|
+
type: {
|
200
|
+
button: "...",
|
201
|
+
link: "..."
|
202
|
+
}
|
203
|
+
},
|
204
|
+
compound_variants: [],
|
205
|
+
defaults: {
|
206
|
+
color: :red,
|
207
|
+
type: :button
|
208
|
+
}
|
209
|
+
) do
|
210
|
+
# base without slots
|
211
|
+
base "..."
|
212
|
+
|
213
|
+
# base with slots
|
214
|
+
base do
|
215
|
+
slot :head, class: "..."
|
216
|
+
slot :body, class: "..."
|
217
|
+
end
|
218
|
+
|
219
|
+
# variant without slots
|
220
|
+
variant color: :red, class: "..."
|
221
|
+
|
222
|
+
# variant with slots
|
223
|
+
variant color: :red do
|
224
|
+
slot :head, class: "..."
|
225
|
+
slot :body, class: "..."
|
226
|
+
end
|
227
|
+
|
228
|
+
# compound variant without slots
|
229
|
+
variant type: :button, color: :red, class: "..."
|
230
|
+
|
231
|
+
# compound variant with slots
|
232
|
+
variant type: :button, color: :red do
|
233
|
+
slot :head, class: "..."
|
234
|
+
slot :body, class: "..."
|
235
|
+
end
|
236
|
+
|
237
|
+
# option 1 (my favorite)
|
238
|
+
defaults color: :red, type: :button
|
239
|
+
|
240
|
+
# option 2
|
241
|
+
defaults do
|
242
|
+
color :red
|
243
|
+
type :button
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Usage
|
248
|
+
|
249
|
+
# renders the defaults
|
250
|
+
alert_classes.render
|
251
|
+
|
252
|
+
# render default slot with custom variants
|
253
|
+
alert_classes.render(color: :red)
|
254
|
+
|
255
|
+
# render slot with defaults variants
|
256
|
+
alert_classes.render(:body)
|
257
|
+
|
258
|
+
# render slot with custom variants
|
259
|
+
alert_classes.render(:body, color: :red)
|
260
|
+
|
261
|
+
# if slot not exist, throw error? return empty classes?
|
262
|
+
alert_classes.render(:non_existent_slot, color: :red)
|
263
|
+
|
264
|
+
# render default slot with custom class (will be merged)
|
265
|
+
alert_classes.render(class: "...")
|
266
|
+
|
267
|
+
# render slot with custom class (will be merged)
|
268
|
+
alert_classes.render(:body, class: "...")
|
269
|
+
```
|
270
|
+
|
103
271
|
## Use with Rails
|
104
272
|
|
105
273
|
```ruby
|
@@ -135,6 +303,36 @@ end
|
|
135
303
|
<%= link_to :Avo, "https://avohq.io", class: button_classes.render(color: :red, size: :xl) %>
|
136
304
|
```
|
137
305
|
|
306
|
+
## Helper module
|
307
|
+
|
308
|
+
If you're developing something more complex you might want to use composition more. You might want to use the helper module for that.
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
class MyClass
|
312
|
+
include ClassVariants::Helper
|
313
|
+
|
314
|
+
class_variants {
|
315
|
+
base: {},
|
316
|
+
variants: {}
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
MyClass.new.class_variants(:container, color: :red, class: "shadow")
|
321
|
+
```
|
322
|
+
|
323
|
+
## `tailwind_merge`
|
324
|
+
|
325
|
+
By default, the classes are merged using `concat`, but you can use the awesome [TailwindMerge](https://github.com/gjtorikian/tailwind_merge) gem.
|
326
|
+
Install the gem using `bundle add tailwind_merge` and use this configuration to enable it.
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
ClassVariants.configure do |config|
|
330
|
+
config.process_classes_with do |classes|
|
331
|
+
TailwindMerge::Merger.new.merge(classes)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
```
|
335
|
+
|
138
336
|
### Output
|
139
337
|
|
140
338
|

|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: class_variants
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adrian Marin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Easily configure styles and apply them as classes.
|
14
14
|
email: adrian@adrianthedev.com
|
@@ -19,9 +19,14 @@ files:
|
|
19
19
|
- LICENSE
|
20
20
|
- lib/class_variants.rb
|
21
21
|
- lib/class_variants/action_view/helpers.rb
|
22
|
+
- lib/class_variants/configuration.rb
|
23
|
+
- lib/class_variants/helper.rb
|
22
24
|
- lib/class_variants/instance.rb
|
23
25
|
- lib/class_variants/railtie.rb
|
24
26
|
- lib/class_variants/version.rb
|
27
|
+
- lib/generators/class_variants/install/USAGE
|
28
|
+
- lib/generators/class_variants/install/install_generator.rb
|
29
|
+
- lib/generators/class_variants/install/templates/class_variants.rb.tt
|
25
30
|
- readme.md
|
26
31
|
homepage: https://github.com/avo-hq/class_variants
|
27
32
|
licenses:
|