class_variants 1.0.0 → 1.1.1

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: 0c8fd556b85ee91d2d2709e66af72e1d4017c5a20ba5884c49eed0c755f1a593
4
- data.tar.gz: c6f77333c70953510eb23ab94846c2b2f9d1ae1a8b185c289ee3530060546651
3
+ metadata.gz: b8ecbdedc158b731b446bf166b1dcc6d99b4569cc90f484e95ff6c5466cc3db9
4
+ data.tar.gz: 80de309d4e820f9a3b5667f8c8b4958172906971e923beb3fd169a915cb20811
5
5
  SHA512:
6
- metadata.gz: c9f77343da5f6e521cd8a895cef3683a5fc65823b8df3f1f84d03adf38099fb6f51cfc191e24111d35f7ac95ef697e6a61b6dc37a547c50b4732e0863e78631f
7
- data.tar.gz: 5a0db3c1db7f88ccc689fbaadc514aa04d73ef405e1ae05755a55918d16d1f1179ff9c736b11402da99e57917c393fd830801f6d78b87c07964d9dc951ac3f98
6
+ metadata.gz: 3fda53be42ac74e51fb85209a24d3bbfed78101052c4bcf016f307f5c0e852adfaf1062102f5e7a80d5563c101c1e418b836eb274557fee1655679084dafe9b8
7
+ data.tar.gz: c20bcba0b71ea4b7c151e2e9a07ca6eac7508f4d938cf8c4b0923baea4dd934651582f4ba7a9eaf0fdacaa606ad71db1faff4ac31705237ca7b5fe5bd31bf26d
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Adrian Marin
3
+ Copyright (c) 2025 Adrian Marin
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ClassVariants
2
4
  module ActionView
3
5
  module Helpers
4
- def class_variants(classes, **args)
5
- ClassVariants::Instance.new classes, **args
6
+ def class_variants(...)
7
+ ClassVariants::Instance.new(...)
6
8
  end
7
9
  end
8
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ClassVariants
2
4
  class Configuration
3
5
  def process_classes_with(&block)
@@ -1,23 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ClassVariants
2
4
  module Helper
3
5
  module ClassMethods
4
6
  def class_variants(...)
5
- @_class_variants_instance = ClassVariants.build(...)
6
- end
7
-
8
- def _class_variants_instance
9
- @_class_variants_instance
7
+ singleton_class.instance_variable_get(:@_class_variants_instance).merge(...)
10
8
  end
11
9
  end
12
10
 
13
11
  def self.included(base)
14
12
  base.extend(ClassMethods)
13
+ base.singleton_class.instance_variable_set(:@_class_variants_instance, ClassVariants::Instance.new)
14
+
15
+ def base.inherited(subclass)
16
+ super if defined?(super)
17
+
18
+ subclass.singleton_class.instance_variable_set(
19
+ :@_class_variants_instance, singleton_class.instance_variable_get(:@_class_variants_instance).dup
20
+ )
21
+ end
15
22
  end
16
23
 
17
24
  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(...)
25
+ self.class.singleton_class.instance_variable_get(:@_class_variants_instance).render(...)
21
26
  end
22
27
  end
23
28
  end
@@ -1,20 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ClassVariants
2
4
  class Instance
3
- def initialize(**options, &block)
4
- raise ArgumentError, "Use of hash config and code block is not supported" if !options.empty? && block_given?
5
+ def initialize(...)
6
+ @bases = []
7
+ @variants = []
8
+ @defaults = {}
9
+ @slots = nil
5
10
 
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, {})
11
+ merge(...)
12
+ end
9
13
 
10
- instance_eval(&block) if block_given?
14
+ def dup
15
+ self.class.new.tap do |copy|
16
+ copy.instance_variable_set(:@bases, @bases.dup)
17
+ copy.instance_variable_set(:@variants, @variants.dup)
18
+ copy.instance_variable_set(:@defaults, @defaults.dup)
19
+ end
20
+ end
21
+
22
+ def merge(**options, &block)
23
+ raise ArgumentError, "Use of hash config and code block is not supported" if !options.empty? && block
24
+
25
+ (base = options.fetch(:base, nil)) && @bases << {class: base, slot: :default}
26
+ @variants += [
27
+ expand_variants(options.fetch(:variants, {})),
28
+ expand_compound_variants(options.fetch(:compound_variants, []))
29
+ ].inject(:+)
30
+ @defaults.merge!(options.fetch(:defaults, {}))
31
+
32
+ instance_eval(&block) if block
33
+
34
+ self
11
35
  end
12
36
 
13
37
  def render(slot = :default, **overrides)
14
38
  classes = overrides.delete(:class)
39
+ result = []
15
40
 
16
41
  # Start with our default classes
17
- result = [@base[slot]]
42
+ @bases.each do |base|
43
+ result << base[:class] if base[:slot] == slot
44
+ end
18
45
 
19
46
  # Then merge the passed in overrides on top of the defaults
20
47
  criteria = @defaults.merge(overrides)
@@ -22,9 +49,15 @@ module ClassVariants
22
49
  @variants.each do |candidate|
23
50
  next unless candidate[:slot] == slot
24
51
 
25
- if (candidate.keys - [:class, :slot]).all? { |key| criteria[key] == candidate[key] }
26
- result << candidate[:class]
52
+ match = false
53
+
54
+ candidate.each_key do |key|
55
+ next if key == :class || key == :slot
56
+ match = criteria[key] == candidate[key]
57
+ break unless match
27
58
  end
59
+
60
+ result << candidate[:class] if match
28
61
  end
29
62
 
30
63
  # add the passed in classes to the result
@@ -40,21 +73,21 @@ module ClassVariants
40
73
  private
41
74
 
42
75
  def base(klass = nil, &block)
43
- raise ArgumentError, "Use of positional argument and code block is not supported" if klass && block_given?
76
+ raise ArgumentError, "Use of positional argument and code block is not supported" if klass && block
44
77
 
45
- if block_given?
78
+ if block
46
79
  with_slots(&block).each do |slot|
47
- @base[slot[:slot]] = slot[:class]
80
+ @bases << slot
48
81
  end
49
82
  else
50
- @base[:default] = klass
83
+ @bases << {slot: :default, class: klass}
51
84
  end
52
85
  end
53
86
 
54
87
  def variant(**options, &block)
55
- raise ArgumentError, "Use of class option and code block is not supported" if options.key?(:class) && block_given?
88
+ raise ArgumentError, "Use of class option and code block is not supported" if options.key?(:class) && block
56
89
 
57
- if block_given?
90
+ if block
58
91
  with_slots(&block).each do |slot|
59
92
  @variants << options.merge(slot)
60
93
  end
@@ -74,9 +107,10 @@ module ClassVariants
74
107
  end
75
108
 
76
109
  def with_slots
77
- @slots = []
110
+ new_slots = []
111
+ @slots = new_slots
78
112
  yield
79
- @slots
113
+ new_slots
80
114
  end
81
115
 
82
116
  def expand_variants(variants)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails/railtie"
2
4
 
3
5
  module ClassVariants
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ClassVariants
2
- VERSION = "1.0.0".freeze
4
+ VERSION = "1.1.1"
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "class_variants/version"
2
4
  require "class_variants/action_view/helpers"
3
5
  require "class_variants/configuration"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ClassVariants
2
4
  module Generators
3
5
  class InstallGenerator < Rails::Generators::Base
data/readme.md CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  We ❤️ Tailwind CSS but sometimes it's difficult to manage the state of some elements using conditionals. `class_variants` is a tiny helper that should enable you to create, configure, and apply different variants of elements as classes.
4
4
 
5
+ ![](./logo.png)
6
+
5
7
  Inspired by [variant-classnames](https://github.com/mattvalleycodes/variant-classnames) ✌️
6
8
 
7
9
  ## Quicklinks
8
10
 
9
11
  * [DRY up your tailwind CSS using this awesome gem](https://www.youtube.com/watch?v=cFcwNH6x77g)
10
12
 
11
-
12
13
  ## Installation
13
14
 
14
15
  Add this line to your application's Gemfile:
@@ -34,9 +35,9 @@ $ gem install class_variants
34
35
  We create an object from the class or helper where we define the configuration using four arguments:
35
36
 
36
37
  1. The `base` keyword argument with default classes that should be applied to each variant.
37
- 1. The `variants` keyword argument where we declare the variants with their option and classes.
38
- 1. The `compound_variants` keyword argument where we declare the compound variants with their conditions and classes
39
- 1. The `defaults` keyword argument (optional) where we declare the default value for each variant.
38
+ 2. The `variants` keyword argument where we declare the variants with their option and classes.
39
+ 3. The `compound_variants` keyword argument where we declare the compound variants with their conditions and classes
40
+ 4. The `defaults` keyword argument (optional) where we declare the default value for each variant.
40
41
 
41
42
  Below we'll implement the [button component](https://tailwindui.com/components/application-ui/elements/buttons) from Tailwind UI.
42
43
 
@@ -99,7 +100,7 @@ button_classes = ClassVariants.build(
99
100
  )
100
101
 
101
102
  button_classes.render(color: :red) # => "inline-flex items-center rounded bg-red-600"
102
- button_classes.render(color: :red, border: true) # => "inline-flex items-center rounded bg-red-600 border border-red-600"
103
+ button_classes.render(color: :red, border: true) # => "inline-flex items-center rounded bg-red-600 border border-red-800"
103
104
  ```
104
105
 
105
106
  ## Override classes with `render`
@@ -185,6 +186,14 @@ end
185
186
  </div>
186
187
  ```
187
188
 
189
+ ## Merge definitions
190
+
191
+ ```ruby
192
+ alert_classes = ClassVariants.build(base: "bg-white")
193
+ alert_classes.merge(base: "text-black")
194
+ alert_classes.render # => "bg-white text-black"
195
+ ```
196
+
188
197
  ## Full API
189
198
 
190
199
  ```ruby
@@ -272,7 +281,7 @@ alert_classes.render(:body, class: "...")
272
281
 
273
282
  ```ruby
274
283
  # Somewhere in your helpers
275
- def button_classes(classes, **args)
284
+ def button_classes
276
285
  class_variants(
277
286
  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",
278
287
  variants: {
@@ -303,6 +312,10 @@ end
303
312
  <%= link_to :Avo, "https://avohq.io", class: button_classes.render(color: :red, size: :xl) %>
304
313
  ```
305
314
 
315
+ ### Output
316
+
317
+ ### ![](sample.jpg)
318
+
306
319
  ## Helper module
307
320
 
308
321
  If you're developing something more complex you might want to use composition more. You might want to use the helper module for that.
@@ -311,54 +324,81 @@ If you're developing something more complex you might want to use composition mo
311
324
  class MyClass
312
325
  include ClassVariants::Helper
313
326
 
314
- class_variants {
315
- base: {},
316
- variants: {}
317
- }
327
+ class_variants(
328
+ base: "bg-white",
329
+ variants: {
330
+ color: {
331
+ red: "text-red",
332
+ blue: "text-blue"
333
+ }
334
+ }
335
+ )
336
+ end
337
+
338
+ MyClass.new.class_variants(color: :red, class: "shadow") # => "bg-white text-red shadow"
339
+ ```
340
+
341
+ This helper supports class inheritance, so that the subclass receives a copy of the class_variants config that the parent class had at the time of inheritance. From that point on, the settings are kept separate for both. Successive calls to class_variants helper method, will cause the configuration to be merged.
342
+
343
+ ```ruby
344
+ class A
345
+ include ClassVariants::Helper
346
+
347
+ class_variants(base: "bg-red")
348
+ end
349
+
350
+ class B < A
351
+ class_variants(base: "text-black")
318
352
  end
319
353
 
320
- MyClass.new.class_variants(:container, color: :red, class: "shadow")
354
+ A.class_variants(base: "text-white")
355
+
356
+ A.new.class_variants # => "bg-red text-white"
357
+ B.new.class_variants # => "bg-red text-black"
321
358
  ```
322
359
 
323
360
  ## `tailwind_merge`
324
361
 
325
362
  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.
363
+ Install the gem using `bundle add tailwind_merge` and use this configuration to enable it. If you're using Rails, you can put this in an initializer.
327
364
 
328
365
  ```ruby
329
366
  ClassVariants.configure do |config|
367
+ merger = TailwindMerge::Merger.new
330
368
  config.process_classes_with do |classes|
331
- TailwindMerge::Merger.new.merge(classes)
369
+ merger.merge(classes)
332
370
  end
333
371
  end
334
372
  ```
335
373
 
336
- ### Output
337
-
338
- ![](sample.jpg)
339
-
340
374
  ## Other packages
341
375
 
342
- - [`active_storage-blurhash`](https://github.com/avo-hq/active_storage-blurhash) - A plug-n-play [blurhash](https://blurha.sh/) integration for images stored in ActiveStorage
343
- - [`avo`](https://github.com/avo-hq/avo) - Build Content management systems with Ruby on Rails
344
- - [`prop_initializer`](https://github.com/avo-hq/prop_initializer) - A flexible tool for defining properties on Ruby classes.
345
- - [`stimulus-confetti`](https://github.com/avo-hq/stimulus-confetti) - The easiest way to add confetti to your StimulusJS app
376
+ - [`active_storage-blurhash`](https://github.com/avo-hq/active_storage-blurhash) - A plug-n-play [blurhash](https://blurha.sh/) integration for images stored in ActiveStorage
377
+ - [`avo`](https://github.com/avo-hq/avo) - Build internal tools, admin panels, and dashboards with Ruby on Rails
378
+ - [`marksmith`](https://github.com/avo-hq/marksmith) - GitHub-style markdown editor for Ruby and Rails
379
+ - [`prop_initializer`](https://github.com/avo-hq/prop_initializer) - A flexible tool for defining properties on Ruby classes.
380
+ - [`stimulus-confetti`](https://github.com/avo-hq/stimulus-confetti) - The easiest way to add confetti to your StimulusJS app
346
381
 
347
382
  ## Try Avo ⭐️
348
383
 
349
384
  If you enjoyed this gem try out [Avo](https://github.com/avo-hq/avo). It helps developers build Internal Tools, Admin Panels, CMSes, CRMs, and any other type of Business Apps 10x faster on top of Ruby on Rails.
350
385
 
351
- [![](./logo-on-white.png)](https://github.com/avo-hq/avo)
386
+ [![](./avo-logo.png)](https://github.com/avo-hq/avo)
387
+
388
+ ## Articles
389
+
390
+ [TIL: How to use `class_variants` with Phlex](https://henrikbjorn.medium.com/til-how-to-use-class-variants-with-phlex-8042bd4407f1)
352
391
 
353
392
  ## Contributing
354
393
 
355
394
  1. Fork it `git clone https://github.com/avo-hq/class_variants`
356
- 1. Create your feature branch `git checkout -b my-new-feature`
357
- 1. Commit your changes `git commit -am 'Add some feature'`
358
- 1. Push to the branch `git push origin my-new-feature`
359
- 1. Create new Pull Request
395
+ 2. Create your feature branch `git checkout -b my-new-feature`
396
+ 3. Commit your changes `git commit -am 'Add some feature'`
397
+ 4. Push to the branch `git push origin my-new-feature`
398
+ 5. Create new Pull Request
360
399
 
361
400
  ## License
401
+
362
402
  This package is available as open source under the terms of the MIT License.
363
403
 
364
404
  ## Cutting a release
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: class_variants
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Easily configure styles and apply them as classes.
14
13
  email: adrian@adrianthedev.com
@@ -36,7 +35,6 @@ metadata:
36
35
  source_code_uri: https://github.com/avo-hq/class_variants
37
36
  bug_tracker_uri: https://github.com/avo-hq/class_variants/issues
38
37
  changelog_uri: https://github.com/avo-hq/class_variants/releases
39
- post_install_message:
40
38
  rdoc_options: []
41
39
  require_paths:
42
40
  - lib
@@ -51,8 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
49
  - !ruby/object:Gem::Version
52
50
  version: '0'
53
51
  requirements: []
54
- rubygems_version: 3.4.10
55
- signing_key:
52
+ rubygems_version: 3.6.9
56
53
  specification_version: 4
57
54
  summary: Easily configure styles and apply them as classes.
58
55
  test_files: []