class_variants 0.0.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35b8ff14e7d3dfe7319d4fb4f09b78aec128422de09402e3504809ef5a0adacf
4
- data.tar.gz: f4b9bd0eec1427ba964d6affd2d3ec0e84126070faadd60cb7b6e51e7979a2ba
3
+ metadata.gz: 0c8fd556b85ee91d2d2709e66af72e1d4017c5a20ba5884c49eed0c755f1a593
4
+ data.tar.gz: c6f77333c70953510eb23ab94846c2b2f9d1ae1a8b185c289ee3530060546651
5
5
  SHA512:
6
- metadata.gz: 75f8bb22dafa4d3e67bcc198b804ce85a76705696d0f46f7d9a22fe7c2c97e7af9174304778c808cf00e502cac9f403d8b25d3c9be825b3e963bb042e24d593c
7
- data.tar.gz: 201be369574e67efe1557b2e396fb2aa1e09faad3415d0b88efe9467d6a9137ede50c7b072d3859c09e5ed63eb1df6369748a1fb247146fca451a1a823878948
6
+ metadata.gz: c9f77343da5f6e521cd8a895cef3683a5fc65823b8df3f1f84d03adf38099fb6f51cfc191e24111d35f7ac95ef697e6a61b6dc37a547c50b4732e0863e78631f
7
+ data.tar.gz: 5a0db3c1db7f88ccc689fbaadc514aa04d73ef405e1ae05755a55918d16d1f1179ff9c736b11402da99e57917c393fd830801f6d78b87c07964d9dc951ac3f98
@@ -0,0 +1,11 @@
1
+ module ClassVariants
2
+ class Configuration
3
+ def process_classes_with(&block)
4
+ if block_given?
5
+ @process_classes_with = block
6
+ else
7
+ @process_classes_with
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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
- attr_reader :base, :variants, :compound_variants, :defaults
4
-
5
- # rubocop:disable Naming/VariableName
6
- def initialize(classes = nil, base: nil, variants: {}, compoundVariants: [], compound_variants: [], defaults: {})
7
- warn <<~MSG if classes
8
- (ClassVariants) DEPRECATION WARNING: Use of positional argument for default classes is deprecated
9
- and will be removed in the next version. Use the `base` keyword argument instead.
10
- MSG
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
- selected = @defaults.merge(overrides)
20
+ criteria = @defaults.merge(overrides)
30
21
 
31
- selected.each do |variant_type, variant|
32
- # dig the classes out and add them to the result
33
- result << @variants.dig(variant_type, variant)
34
- end
22
+ @variants.each do |candidate|
23
+ next unless candidate[:slot] == slot
35
24
 
36
- @compound_variants.each do |compound_variant|
37
- if (compound_variant.keys - [:class]).all? { |key| selected[key] == compound_variant[key] }
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 expand_boolean_variants(variants)
52
- expanded = variants.map do |key, value|
53
- case value
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
- s_key = key.to_s
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
- {key => value}
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
- expanded.reduce do |output, next_variant|
63
- output.merge!(next_variant) { |_key, v1, v2| v1.merge!(v2) }
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
@@ -1,3 +1,3 @@
1
1
  module ClassVariants
2
- VERSION = "0.0.8".freeze
2
+ VERSION = "1.0.0".freeze
3
3
  end
@@ -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,2 @@
1
+ Description:
2
+ Generates initializer file for configure ClassVariants in your application.
@@ -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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ClassVariants.configure do |config|
4
+ # # allow to post process classes with an external utility like TailwindMerger
5
+ # config.process_classes_with do |classes|
6
+ # TailwindMerge::Merger.new.merge(classes)
7
+ # end
8
+ # 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
- ## Example
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
- color: {
57
- indigo: "bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500",
58
- red: "bg-red-600 hover:bg-red-700 focus:ring-red-500",
59
- blue: "bg-blue-600 hover:bg-blue-700 focus:ring-blue-500",
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
- block: "w-full justify-center",
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
- "!block": "w-auto",
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
- ### Compound Variants
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
  ![](sample.jpg)
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.8
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-10-24 00:00:00.000000000 Z
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: