clsx-rails 2.0.0 → 3.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: 5ac79817d0d2f653dbc76e4f3d044c6d599f342acecc1c6d6c6639dbbdec1ecf
4
- data.tar.gz: ce38f0509202614f8aeab6f16f0079eb93d686e6c5c42d48170af8d50ccd801b
3
+ metadata.gz: 2da83a9921c3d24cf5c9743bcaf1a11a63bae43f38f7ba197116436f8b2471d5
4
+ data.tar.gz: 280c9dfc3fc501ce352d755d620f49ec4e58b8c6de7e976487cd80ccf358e700
5
5
  SHA512:
6
- metadata.gz: 0c8e9e1d7100c07f7336b9a8ac4fd699b4a1a90f78bd4c2fa51cdd931583948913d9622e901a17f3fa03104fbebca4f92ac07df50287a1f4209bb531cf4886fe
7
- data.tar.gz: 155e24fb75550a7a12e050fc20089fc9677e0435d7b08f0412d65b19a727f7fbca2cb86cbb0d7cf6df44b037e4e515efa947c42d2da9f020805ac8c4461d7e58
6
+ metadata.gz: 33c8950cb1a10e94b22fc4a442dba3181dab708f7d1d6abec2bbd0b42aae009832aebcbd1c963c6050fa0ba397f4e4f960743675679d7ad46c507a3b2d9efb52
7
+ data.tar.gz: 47bb02df172c9c3d98251234112eaaef97b150d39b49c1e51a1fc5caf6d41c310719af75de32be86a15e7cb3a6062dd67fdbbe90e2c9765d9f3907f51399737f
data/CHANGELOG.md CHANGED
@@ -4,54 +4,54 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
6
6
 
7
- ## Unreleased changes
7
+ ## Unreleased
8
8
 
9
- ## v2.0.0 (2025-01-11)
9
+ ## v3.0.0 (2026-02-13)
10
+
11
+ ### Added
12
+ - Codecov badge and configuration
10
13
 
11
- ### Breaking Changes
12
- - Drop Rails 6.1 and 7.0 support, require Rails 7.1+
14
+ ### Changed
15
+ - BREAKING: Use clsx-ruby gem as core engine instead of bundled implementation
16
+ - BREAKING: Require Ruby 3.2+ (was 3.1+) and Rails 7.2+ (was 7.1+)
17
+ - Version constant moved from `Clsx::VERSION` to `Clsx::Rails::VERSION`
18
+ - Benchmarks now compare against Rails `class_names` instead of internal versions
13
19
 
14
- ### Features
15
- - Add Ruby 3.4 support
16
- - Add Rails 8.0, 8.1, and edge support
20
+ ### Removed
21
+ - Bundled clsx algorithm (now provided by clsx-ruby dependency)
17
22
 
18
- ### Performance Improvements
23
+ ## v2.0.0 (2025-01-11)
24
+
25
+ ### Changed
26
+ - BREAKING: Drop Rails 6.1 and 7.0 support, require Rails 7.1+
19
27
  - Rewrite algorithm for 2-5x performance improvement
20
28
  - Add fast-paths for single string, string array, and simple hash
21
29
  - Use Hash-based deduplication instead of Array + `uniq!`
22
30
  - Use `Symbol#name` instead of `to_s` for faster symbol conversion
23
31
  - Use direct class comparison for type checking
24
32
  - Remove unused `require 'set'`
25
-
26
- ### Chore
27
33
  - Refactor benchmark infrastructure (data.rb, original.rb, quick.rb, run.rb)
28
- - Add CLAUDE.md for AI coding assistants
34
+
35
+ ### Added
36
+ - Ruby 3.4 support
37
+ - Rails 8.0, 8.1, and edge support
38
+ - CLAUDE.md for AI coding assistants
39
+
40
+ ### Tests
41
+ - Improve test coverage to 100% line and 100% branch coverage
29
42
 
30
43
  ## v1.0.1 (2024-03-04)
31
44
 
32
- ### Performance Improvements
45
+ ### Changed
33
46
  - Speeds up the performance by 2x [`32236ed`](https://github.com/svyatov/clsx-rails/commit/32236ed)
34
-
35
- ### Chore
36
47
  - Fixes CI action [`f1b948c`](https://github.com/svyatov/clsx-rails/commit/f1b948c)
37
48
  - Upload code coverage to CodeCov for the latest combination of Ruby and ActionView only [`4e5d768`](https://github.com/svyatov/clsx-rails/commit/4e5d768)
38
49
 
39
- ### Documentation
40
- - Changelog update + version bump [`13b408d`](https://github.com/svyatov/clsx-rails/commit/13b408d)
50
+ ### Added
41
51
  - Adds information about supported Ruby and Rails version [skip ci] [`2e6483f`](https://github.com/svyatov/clsx-rails/commit/2e6483f)
42
52
  - Adds link to the CodeCov badge, switch to Conventional Commits [`b48cc84`](https://github.com/svyatov/clsx-rails/commit/b48cc84)
43
-
44
- ### Other
45
- - Updates CI badge [skip ci] [`a829613`](https://github.com/svyatov/clsx-rails/commit/a829613)
46
53
  - Adds code coverage tracking [`0c5d34c`](https://github.com/svyatov/clsx-rails/commit/0c5d34c)
47
- - Ignore ruby-head in the CI matrix [`f3ab4df`](https://github.com/svyatov/clsx-rails/commit/f3ab4df)
48
- - Better name for the GitHub Action job [`a28adb7`](https://github.com/svyatov/clsx-rails/commit/a28adb7)
49
- - Adds badges, fixes rubocop configuration for CI [`56fab44`](https://github.com/svyatov/clsx-rails/commit/56fab44)
50
- - Create dependabot.yml [`ed1e0eb`](https://github.com/svyatov/clsx-rails/commit/ed1e0eb)
51
- - Fixes GitHub Actions [`f58a4b2`](https://github.com/svyatov/clsx-rails/commit/f58a4b2)
52
54
 
53
55
  ## v1.0.0 (2024-03-03)
54
56
 
55
- ### Other
56
57
  - Initial commit [`f65b9b8`](https://github.com/svyatov/clsx-rails/commit/f65b9b8)
57
-
data/README.md CHANGED
@@ -1,18 +1,10 @@
1
1
  # clsx-rails [![Gem Version](https://img.shields.io/gem/v/clsx-rails)](https://rubygems.org/gems/clsx-rails) [![Codecov](https://img.shields.io/codecov/c/github/svyatov/clsx-rails)](https://app.codecov.io/gh/svyatov/clsx-rails) [![CI](https://github.com/svyatov/clsx-rails/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/svyatov/clsx-rails/actions?query=workflow%3ACI) [![GitHub License](https://img.shields.io/github/license/svyatov/clsx-rails)](LICENSE.txt)
2
2
 
3
- > A tiny Rails view helper for constructing CSS class strings conditionally.
3
+ > Rails view helper for constructing CSS class strings conditionally. Powered by [clsx-ruby](https://github.com/svyatov/clsx-ruby).
4
4
 
5
- This gem is essentially a clone if the [clsx](https://github.com/lukeed/clsx) npm package.
6
- It provides a simple way to conditionally apply classes to HTML elements in Rails views.
7
- It is especially useful when you have a lot of conditional classes and you want to keep your views clean and readable.
5
+ Automatically adds `clsx` and `cn` helpers to all Rails views. The fastest alternative to Rails `class_names`.
8
6
 
9
- ## Supported Ruby and Rails versions
10
-
11
- Ruby 3.1+ and Rails 7.1+ are supported.
12
-
13
- ## Installation
14
-
15
- Install the gem and add to the application's Gemfile by executing:
7
+ ## Quick Start
16
8
 
17
9
  ```bash
18
10
  bundle add clsx-rails
@@ -21,14 +13,46 @@ bundle add clsx-rails
21
13
  Or add it manually to the Gemfile:
22
14
 
23
15
  ```ruby
24
- gem 'clsx-rails', '~> 1.0'
16
+ gem 'clsx-rails', '~> 3.0'
25
17
  ```
26
18
 
27
- ## Usage
19
+ That's it — `clsx` and `cn` are now available in all your views:
20
+
21
+ ```erb
22
+ <%= tag.div class: clsx('btn', 'btn-primary', active: @active) do %>
23
+ Click me
24
+ <% end %>
25
+ ```
26
+
27
+ ## Why clsx-rails over Rails `class_names`?
28
+
29
+ ### Faster
30
+
31
+ **3-8x faster** than Rails `class_names` across every scenario:
32
+
33
+ | Scenario | clsx | Rails `class_names` | Speedup |
34
+ |---|---|---|---|
35
+ | Single string | 7.6M i/s | 911K i/s | **8.5x** |
36
+ | String + hash | 2.4M i/s | 580K i/s | **4.1x** |
37
+ | String array | 1.4M i/s | 357K i/s | **4.0x** |
38
+ | Multiple strings | 1.5M i/s | 414K i/s | **3.7x** |
39
+ | Hash | 2.2M i/s | 670K i/s | **3.3x** |
40
+ | Mixed types | 852K i/s | 367K i/s | **2.3x** |
28
41
 
29
- The `clsx` helper method can be used in views to conditionally apply classes to HTML elements.
30
- You can also use a slightly more conise `cn` alias.
31
- It accepts a variety of arguments and returns a string of unique classes.
42
+ <sup>Ruby 4.0.1, Apple M1 Pro. Reproduce: `bundle exec ruby benchmark/run.rb`</sup>
43
+
44
+ ### More features
45
+
46
+ | Feature | clsx-rails | Rails `class_names` |
47
+ |---|---|---|
48
+ | Conditional classes | yes | yes |
49
+ | Auto-deduplication | yes | yes |
50
+ | 3-8x faster | yes | no |
51
+ | Returns `nil` when empty | yes | no (returns `""`) |
52
+ | Complex hash keys | yes | no |
53
+ | Short `cn` alias | yes | no |
54
+
55
+ ## Usage
32
56
 
33
57
  ```ruby
34
58
  # Strings (variadic)
@@ -36,7 +60,7 @@ clsx('foo', true && 'bar', 'baz')
36
60
  # => 'foo bar baz'
37
61
 
38
62
  # Hashes
39
- cn({ foo: true, bar: false, baz: a_method_that_returns_true })
63
+ cn(foo: true, bar: false, baz: a_method_that_returns_true)
40
64
  # => 'foo baz'
41
65
 
42
66
  # Hashes (variadic)
@@ -52,124 +76,130 @@ clsx(['foo'], ['', nil, false, 'bar'], [['baz', [['hello'], 'there']]])
52
76
  # => 'foo bar baz hello there'
53
77
 
54
78
  # Kitchen sink (with nesting)
55
- cn('foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya');
79
+ cn('foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya')
56
80
  # => 'foo bar hello world cya'
57
81
  ```
58
82
 
83
+ ### ERB
84
+
59
85
  ```erb
60
- <%= tag.div class: clsx('foo', 'baz', 'is-active' => @active) do %>
86
+ <%= tag.div class: clsx('foo', 'baz', 'is-active': @active) do %>
61
87
  Hello, world!
62
88
  <% end %>
63
89
 
64
- <div class="<%= clsx('foo', 'baz', 'is-active' => @active) %>">
90
+ <div class="<%= clsx('foo', 'baz', 'is-active': @active) %>">
65
91
  Hello, world!
66
92
  </div>
67
93
  ```
68
94
 
95
+ ### HAML
96
+
69
97
  ```haml
70
- %div{class: clsx('foo', 'baz', 'is-active' => @active)}
98
+ %div{class: clsx('foo', 'baz', 'is-active': @active)}
71
99
  Hello, world!
72
100
  ```
73
101
 
102
+ ### Slim
103
+
74
104
  ```slim
75
- div class=clsx('foo', 'baz', 'is-active' => @active)
105
+ div class=clsx('foo', 'baz', 'is-active': @active)
76
106
  | Hello, world!
77
107
  ```
78
108
 
79
- So the basic idea is to get rid of constructions like this in your views:
109
+ ## Framework Examples
80
110
 
81
- ```erb
82
- <% classes = ['foo', 'baz'] %>
83
- <% classes << 'is-active' if @active %>
111
+ ### ViewComponent
84
112
 
85
- <div class="<%= classes.join(' ') %>">
86
- Hello, world!
87
- </div>
113
+ ```ruby
114
+ class AlertComponent < ViewComponent::Base
115
+ def initialize(variant: :info, dismissible: false)
116
+ @variant = variant
117
+ @dismissible = dismissible
118
+ end
119
+
120
+ def classes
121
+ clsx("alert", "alert-#{@variant}", dismissible: @dismissible)
122
+ end
123
+ end
88
124
  ```
89
125
 
90
- or like this:
126
+ ### Phlex
91
127
 
92
- ```erb
93
- <div class="<%= ['foo', 'baz', @active ? 'is-active' : nil].compact.join(' ') %>">
94
- Hello, world!
95
- </div>
128
+ ```ruby
129
+ class Badge < Phlex::HTML
130
+ include Clsx::Helper
131
+
132
+ def initialize(color: :blue, pill: false)
133
+ @color = color
134
+ @pill = pill
135
+ end
136
+
137
+ def view_template
138
+ span(class: clsx("badge", "badge-#{@color}", pill: @pill)) { yield }
139
+ end
140
+ end
96
141
  ```
97
142
 
98
- or like this:
143
+ ### Tailwind CSS
99
144
 
100
- ```erb
101
- <div class="foo bar <%= 'is-active' if @active %>">
102
- Hello, world!
103
- </div>
145
+ ```ruby
146
+ class NavLink < ViewComponent::Base
147
+ def initialize(active: false)
148
+ @active = active
149
+ end
150
+
151
+ def classes
152
+ clsx(
153
+ 'px-3 py-2 rounded-md text-sm font-medium transition-colors',
154
+ 'text-white bg-indigo-600': @active,
155
+ 'text-gray-300 hover:text-white hover:bg-gray-700': !@active
156
+ )
157
+ end
158
+ end
104
159
  ```
105
160
 
106
- ## Differences from the original `clsx` package
107
-
108
- This gem reproduces the functionality of the original `clsx` package, but with nuances of Ruby and Rails in mind.
161
+ ## Differences from JavaScript clsx
109
162
 
110
- The main differences are:
111
-
112
- 1. falsy values in Ruby are only `false` and `nil`, so the `clsx` method will not accept `0`, `''`, `[]`, `{}`, etc. as falsy values.
113
- ```ruby
114
- clsx('foo' => 0, bar: []) # => 'foo bar'
115
- ```
116
-
117
- 2. `clsx-rails` supports complex hash keys, like `{ %[foo bar] => true }`, which will be converted to `foo bar` in the resulting string.
118
- Meaning, if it's a valid input for the `clsx-rails`, it's a valid hash key.
163
+ 1. **Returns `nil`** when no classes apply (not an empty string). Rails tag helpers skip `nil`, preventing empty `class=""` attributes:
119
164
  ```ruby
120
- clsx([{ foo: true }, 'bar'] => true) # => 'foo bar'
121
- ```
122
-
123
- 3. `clsx-rails` will ignore objects that are `blank?`, boolean (`true` or `false`), or an instance of `Proc` (so, procs and lambdas).
124
- ```ruby
125
- clsx('', [], {}, proc {}, -> {}, nil, false, true) # => nil
126
- ```
127
-
128
- 4. `clsx-rails` returns `nil` if there are no classes to apply, instead of an empty string.
129
- The reason for that is not to pollute the HTML with empty `class` attributes when using Rails tag helpers: Rails will not render the `class` attribute if it's `nil`.
165
+ clsx(nil, false) # => nil
166
+ ```
167
+
168
+ 2. **Deduplication** duplicate classes are automatically removed:
130
169
  ```ruby
131
- clsx(nil, false) # => nil
170
+ clsx('foo', 'foo') # => 'foo'
132
171
  ```
133
- Although, it won't help if you're using it directly in erb:
134
- ```erb
135
- <div class="<%= clsx(nil, false) %>">
136
- Hello, world!
137
- </div>
172
+
173
+ 3. **Falsy values** — in Ruby only `false` and `nil` are falsy, so `0`, `''`, `[]`, `{}` are all truthy:
174
+ ```ruby
175
+ clsx('foo' => 0, bar: []) # => 'foo bar'
138
176
  ```
139
- This code will render `<div class="">Hello, world!</div>` anyway.
140
-
141
- 5. `clsx-rails` eliminates duplicate classes:
177
+
178
+ 4. **Complex hash keys** — any valid `clsx` input works as a hash key:
142
179
  ```ruby
143
- clsx('foo', 'foo') # => 'foo'
180
+ clsx([{ foo: true }, 'bar'] => true) # => 'foo bar'
144
181
  ```
145
182
 
146
- ## Development
183
+ 5. **Ignored values** — boolean `true` and `Proc`/lambda objects are silently ignored:
184
+ ```ruby
185
+ clsx('', proc {}, -> {}, nil, false, true) # => nil
186
+ ```
147
187
 
148
- After checking out the repo, run `bin/setup` to install dependencies. Then, run
149
- `rake test` to run the tests. You can also run `bin/console` for an interactive
150
- prompt that will allow you to experiment.
188
+ ## Looking for a framework-agnostic version?
151
189
 
152
- To install this gem onto your local machine, run `bundle exec rake install`. To
153
- release a new version, update the version number in `version.rb`, and then run
154
- `bundle exec rake release`, which will create a git tag for the version, push
155
- git commits and the created tag, and push the `.gem` file to
156
- [rubygems.org](https://rubygems.org).
190
+ See [clsx-ruby](https://github.com/svyatov/clsx-ruby) works with Rails, Sinatra, Hanami, or plain Ruby.
157
191
 
158
- There is a simple benchmark script in the `benchmark` directory.
159
- You can run it with `bundle exec ruby benchmark/run.rb`.
160
- I've added it for easier performance testing when making changes to the gem.
192
+ ## Supported Versions
161
193
 
162
- ## Conventional Commits
194
+ Ruby 3.2+ and Rails 7.2+.
163
195
 
164
- This project uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages.
196
+ ## Development
165
197
 
166
- Types of commits are:
167
- - `feat`: a new feature
168
- - `fix`: a bug fix
169
- - `perf`: code that improves performance
170
- - `chore`: updating build tasks, configs, formatting etc; no code change
171
- - `docs`: changes to documentation
172
- - `refactor`: refactoring code
198
+ ```bash
199
+ bin/setup # install dependencies
200
+ bundle exec rake test # run tests
201
+ bundle exec ruby benchmark/run.rb # run benchmarks
202
+ ```
173
203
 
174
204
  ## Contributing
175
205
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clsx
4
- VERSION = '2.0.0'
4
+ module Rails
5
+ VERSION = '3.0.0'
6
+ end
5
7
  end
data/lib/clsx-rails.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'clsx'
3
4
  require 'active_support'
4
5
  require 'action_view'
5
6
 
6
- require_relative 'clsx/version'
7
- require_relative 'clsx/helper'
7
+ require_relative 'clsx/rails/version'
8
8
 
9
9
  # :nodoc:
10
10
  module Clsx
11
- ActiveSupport.on_load(:action_view) { include Helper }
11
+ module Rails; end # :nodoc:
12
+ ActiveSupport.on_load(:action_view) { include Clsx::Helper }
12
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clsx-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
@@ -15,15 +15,30 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.1'
18
+ version: '7.2'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '7.1'
26
- description: A tiny Rails view helper for constructing CSS class strings conditionally
25
+ version: '7.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: clsx-ruby
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.1'
40
+ description: Adds clsx and cn helpers to all Rails views for constructing CSS class
41
+ strings conditionally
27
42
  email:
28
43
  - leonid@svyatov.com
29
44
  executables: []
@@ -31,12 +46,10 @@ extensions: []
31
46
  extra_rdoc_files: []
32
47
  files:
33
48
  - CHANGELOG.md
34
- - CLAUDE.md
35
49
  - LICENSE.txt
36
50
  - README.md
37
51
  - lib/clsx-rails.rb
38
- - lib/clsx/helper.rb
39
- - lib/clsx/version.rb
52
+ - lib/clsx/rails/version.rb
40
53
  homepage: https://github.com/svyatov/clsx-rails
41
54
  licenses:
42
55
  - MIT
@@ -52,14 +65,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
52
65
  requirements:
53
66
  - - ">="
54
67
  - !ruby/object:Gem::Version
55
- version: 3.1.0
68
+ version: 3.2.0
56
69
  required_rubygems_version: !ruby/object:Gem::Requirement
57
70
  requirements:
58
71
  - - ">="
59
72
  - !ruby/object:Gem::Version
60
73
  version: '0'
61
74
  requirements: []
62
- rubygems_version: 4.0.3
75
+ rubygems_version: 4.0.6
63
76
  specification_version: 4
64
- summary: clsx / classnames for Rails views
77
+ summary: Rails view helper integration for clsx-ruby
65
78
  test_files: []
data/CLAUDE.md DELETED
@@ -1,54 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Project Overview
6
-
7
- clsx-rails is a Ruby gem that provides a Rails view helper (`clsx`/`cn`) for constructing CSS class strings conditionally. It's a Ruby port of the JavaScript [clsx](https://github.com/lukeed/clsx) package, adapted for Rails conventions.
8
-
9
- ## Common Commands
10
-
11
- ```bash
12
- # Run all tests and linting (default rake task)
13
- bundle exec rake
14
-
15
- # Run tests only
16
- bundle exec rake test
17
-
18
- # Run a single test file
19
- bundle exec ruby -Itest test/clsx/helper_test.rb
20
-
21
- # Run a specific test method
22
- bundle exec ruby -Itest test/clsx/helper_test.rb -n test_with_strings
23
-
24
- # Run linter
25
- bundle exec rake rubocop
26
-
27
- # Run benchmark
28
- bundle exec ruby benchmark/run.rb
29
-
30
- # Install dependencies
31
- bin/setup
32
- ```
33
-
34
- ## Architecture
35
-
36
- The gem has a minimal structure:
37
-
38
- - `lib/clsx-rails.rb` - Entry point that auto-includes the helper into ActionView via `ActiveSupport.on_load`
39
- - `lib/clsx/helper.rb` - Core implementation with `clsx` method and `cn` alias
40
- - `lib/clsx/version.rb` - Version constant
41
-
42
- The helper uses an optimized algorithm with fast-paths for common cases (single string, string array, simple hash) and Hash-based deduplication for complex inputs.
43
-
44
- ## Key Behaviors
45
-
46
- - Returns `nil` (not empty string) when no classes apply - this prevents Rails from rendering empty `class=""` attributes
47
- - Eliminates duplicate classes automatically
48
- - Ruby falsy values are only `false` and `nil` (unlike JS, `0`, `''`, `[]`, `{}` are truthy)
49
- - Ignores `Proc`/lambda objects and boolean `true` values
50
- - Supports complex hash keys like `{ %w[foo bar] => true }` which resolve recursively
51
-
52
- ## Commit Convention
53
-
54
- Uses [Conventional Commits](https://www.conventionalcommits.org/): `feat`, `fix`, `perf`, `chore`, `docs`, `refactor`
data/lib/clsx/helper.rb DELETED
@@ -1,120 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # :nodoc:
4
- module Clsx
5
- # :nodoc:
6
- module Helper
7
- # The clsx function can take any number of arguments,
8
- # each of which can be Hash, Array, Boolean, String, or Symbol.
9
- #
10
- # **Important**
11
- # Any falsy values are discarded! Standalone Boolean values are discarded as well.
12
- #
13
- # @param [Mixed] args
14
- #
15
- # @return [String] the joined class names
16
- #
17
- # @example
18
- # clsx('foo', 'bar') # => 'foo bar'
19
- # clsx(true, { bar: true }) # => 'bar'
20
- # clsx('foo', { bar: false }) # => 'foo'
21
- # clsx({ bar: true }, 'baz', { bat: false }) # => 'bar baz'
22
- #
23
- # @example within a view
24
- # <div class="<%= clsx('foo', 'bar') %>">
25
- # <div class="<%= clsx('foo', active: @is_active, 'another-class' => @condition) %>">
26
- # <%= tag.div class: clsx(%w[foo bar], hidden: @condition) do ... end %>
27
- #
28
- # @note Implementation prioritizes performance over readability.
29
- # Direct class comparisons and explicit conditionals are used
30
- # instead of more idiomatic Ruby patterns for speed.
31
-
32
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
33
- def clsx(*args)
34
- return nil if args.empty?
35
-
36
- # Fast path: single argument (most common cases)
37
- if args.size == 1
38
- arg = args[0]
39
- klass = arg.class
40
-
41
- if klass == String
42
- return arg.empty? ? nil : arg
43
- elsif klass == Symbol
44
- return arg.name
45
- elsif klass == Array && arg.all?(String)
46
- seen = {}
47
- arg.each { |s| seen[s] = true unless s.empty? || seen.key?(s) }
48
- return seen.empty? ? nil : seen.keys.join(' ')
49
- elsif klass == Hash
50
- return clsx_simple_hash(arg)
51
- end
52
- end
53
-
54
- seen = {}
55
- clsx_process(args, seen)
56
- seen.empty? ? nil : seen.keys.join(' ')
57
- end
58
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
59
-
60
- alias cn clsx
61
-
62
- private
63
-
64
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
65
- def clsx_simple_hash(hash)
66
- return nil if hash.empty?
67
-
68
- seen = {}
69
- hash.each do |key, value|
70
- next unless value
71
-
72
- klass = key.class
73
-
74
- if klass == Symbol
75
- seen[key.name] = true
76
- elsif klass == String
77
- seen[key] = true unless key.empty?
78
- else
79
- # Complex key - fall back to full processing
80
- seen = {}
81
- clsx_process([hash], seen)
82
- return seen.empty? ? nil : seen.keys.join(' ')
83
- end
84
- end
85
-
86
- seen.empty? ? nil : seen.keys.join(' ')
87
- end
88
-
89
- # rubocop:disable Style/MultipleComparison
90
- def clsx_process(args, seen)
91
- deferred = nil
92
-
93
- args.each do |arg|
94
- klass = arg.class
95
-
96
- if klass == String
97
- seen[arg] = true unless arg.empty? || seen.key?(arg)
98
- elsif klass == Symbol
99
- str = arg.name
100
- seen[str] = true unless seen.key?(str)
101
- elsif klass == Array
102
- clsx_process(arg, seen)
103
- elsif klass == Hash
104
- arg.each { |key, value| (deferred ||= []) << key if value }
105
- elsif klass == Integer || klass == Float
106
- str = arg.to_s
107
- seen[str] = true unless seen.key?(str)
108
- elsif klass == NilClass || klass == FalseClass || klass == TrueClass || klass == Proc
109
- next
110
- else
111
- str = arg.to_s
112
- seen[str] = true unless str.empty? || seen.key?(str)
113
- end
114
- end
115
-
116
- clsx_process(deferred, seen) if deferred
117
- end
118
- # rubocop:enable Style/MultipleComparison, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
119
- end
120
- end