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 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: