clsx-rails 3.0.0 → 3.1.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/CHANGELOG.md +14 -0
- data/README.md +170 -61
- data/lib/clsx/rails/version.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bef11730f6fbfe63e305c35697e86117393c6114915d7b68853b3b2e12ee6737
|
|
4
|
+
data.tar.gz: d8244bf7d2fe9eaf563948b26eafbed1a4bd76aa17328aacd97ba2d513186a41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d99571d5e502310350a03aa3d2fc0da8f815f1bb8f33fa9bfd0e15da17439a26ab078d185c0c9fc7943ede6190dece4067d5d1996da71420fa9c1ee08438891
|
|
7
|
+
data.tar.gz: 38797c2478f8fe3da811851d79c00a3045aa629edaa4dd2b7708fd944793fc34b0a8e84ff342247ef1cba10befd1029777b42874673f4eeaf85ea11c2d95c0e7
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v3.1.0 (2026-07-01)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `twm` Tailwind class-merge helper, available in all views via clsx-ruby 1.2.0's optional `tailwind_merge` integration (opt-in: `require 'clsx/tailwind_merge'` + `Clsx.merger =`)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Require clsx-ruby `~> 1.2` (was `~> 1.1, >= 1.1.3`); picks up faster single-string and hash paths
|
|
16
|
+
|
|
17
|
+
## v3.0.1 (2026-02-27)
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Require clsx-ruby >= 1.1.3 for correct deduplication behavior
|
|
21
|
+
- Updated benchmark numbers to reflect current performance (2-4x faster)
|
|
22
|
+
|
|
9
23
|
## v3.0.0 (2026-02-13)
|
|
10
24
|
|
|
11
25
|
### Added
|
data/README.md
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
# clsx-rails [](https://rubygems.org/gems/clsx-rails) [](https://app.codecov.io/gh/svyatov/clsx-rails) [](https://github.com/svyatov/clsx-rails/actions?query=workflow%3ACI) [](LICENSE.txt)
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
> The fastest conditional CSS class builder for Rails — 2–4x faster drop-in replacement for `class_names`.
|
|
4
|
+
> Powered by [clsx-ruby](https://github.com/svyatov/clsx-ruby).
|
|
5
|
+
|
|
6
|
+
Auto-loads `clsx` and `cn` helpers into every Rails view. Zero configuration.
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Why clsx-rails?](#why-clsx-rails)
|
|
12
|
+
- [Blazing fast](#blazing-fast)
|
|
13
|
+
- [More feature-rich than `class_names`](#more-feature-rich-than-class_names)
|
|
14
|
+
- [Usage](#usage)
|
|
15
|
+
- [Input types](#input-types)
|
|
16
|
+
- [Template engines](#template-engines)
|
|
17
|
+
- [Framework Integration](#framework-integration)
|
|
18
|
+
- [Tailwind CSS](#tailwind-css)
|
|
19
|
+
- [Merging conflicting utilities](#merging-conflicting-utilities)
|
|
20
|
+
- [ViewComponent](#viewcomponent)
|
|
21
|
+
- [Phlex](#phlex)
|
|
22
|
+
- [Differences from JavaScript clsx](#differences-from-javascript-clsx)
|
|
23
|
+
- [Supported Versions](#supported-versions)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Contributing](#contributing)
|
|
26
|
+
- [License](#license)
|
|
6
27
|
|
|
7
28
|
## Quick Start
|
|
8
29
|
|
|
@@ -13,7 +34,7 @@ bundle add clsx-rails
|
|
|
13
34
|
Or add it manually to the Gemfile:
|
|
14
35
|
|
|
15
36
|
```ruby
|
|
16
|
-
gem 'clsx-rails', '~> 3.
|
|
37
|
+
gem 'clsx-rails', '~> 3.1'
|
|
17
38
|
```
|
|
18
39
|
|
|
19
40
|
That's it — `clsx` and `cn` are now available in all your views:
|
|
@@ -24,36 +45,41 @@ That's it — `clsx` and `cn` are now available in all your views:
|
|
|
24
45
|
<% end %>
|
|
25
46
|
```
|
|
26
47
|
|
|
27
|
-
## Why clsx-rails
|
|
48
|
+
## Why clsx-rails?
|
|
28
49
|
|
|
29
|
-
###
|
|
50
|
+
### Blazing fast
|
|
30
51
|
|
|
31
|
-
**
|
|
52
|
+
**2–4x faster** than Rails `class_names` — never slower, on realistic markup:
|
|
32
53
|
|
|
33
54
|
| Scenario | clsx | Rails `class_names` | Speedup |
|
|
34
55
|
|---|---|---|---|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
| Hash |
|
|
40
|
-
|
|
|
56
|
+
| String array | 1.2M i/s | 317K i/s | **3.9x** |
|
|
57
|
+
| Multiple strings | 1.3M i/s | 346K i/s | **3.8x** |
|
|
58
|
+
| Single string | 2.3M i/s | 812K i/s | **2.9x** |
|
|
59
|
+
| Mixed types | 901K i/s | 331K i/s | **2.7x** |
|
|
60
|
+
| Hash | 1.7M i/s | 684K i/s | **2.4x** |
|
|
61
|
+
| String + hash | 1.2M i/s | 550K i/s | **2.1x** |
|
|
41
62
|
|
|
42
63
|
<sup>Ruby 4.0.1, Apple M1 Pro. Reproduce: `bundle exec ruby benchmark/run.rb`</sup>
|
|
43
64
|
|
|
44
|
-
### More
|
|
65
|
+
### More feature-rich than `class_names`
|
|
45
66
|
|
|
46
67
|
| Feature | clsx-rails | Rails `class_names` |
|
|
47
68
|
|---|---|---|
|
|
48
|
-
| Conditional classes |
|
|
49
|
-
| Auto-deduplication |
|
|
50
|
-
|
|
|
51
|
-
| Returns `nil` when empty |
|
|
52
|
-
| Complex hash keys |
|
|
53
|
-
|
|
|
69
|
+
| Conditional classes | ✅ | ✅ |
|
|
70
|
+
| Auto-deduplication | ✅ | ✅ |
|
|
71
|
+
| 2–4× faster | ✅ | ❌ |
|
|
72
|
+
| Returns `nil` when empty | ✅ | ❌ (returns `""`) |
|
|
73
|
+
| Complex hash keys | ✅ | ❌ |
|
|
74
|
+
| Tailwind conflict merge (`twm`) | ✅ | ❌ |
|
|
75
|
+
| Short `cn` alias | ✅ | ❌ |
|
|
54
76
|
|
|
55
77
|
## Usage
|
|
56
78
|
|
|
79
|
+
`clsx` and its `cn` alias are available in every view — no `include`, no setup.
|
|
80
|
+
|
|
81
|
+
### Input types
|
|
82
|
+
|
|
57
83
|
```ruby
|
|
58
84
|
# Strings (variadic)
|
|
59
85
|
clsx('foo', true && 'bar', 'baz')
|
|
@@ -75,14 +101,31 @@ cn(['foo', nil, false, 'bar'])
|
|
|
75
101
|
clsx(['foo'], ['', nil, false, 'bar'], [['baz', [['hello'], 'there']]])
|
|
76
102
|
# => 'foo bar baz hello there'
|
|
77
103
|
|
|
104
|
+
# Symbols
|
|
105
|
+
clsx(:foo, :'bar-baz')
|
|
106
|
+
# => 'foo bar-baz'
|
|
107
|
+
|
|
108
|
+
# Numbers
|
|
109
|
+
clsx(1, 2, 3)
|
|
110
|
+
# => '1 2 3'
|
|
111
|
+
|
|
112
|
+
# Multi-token strings (deduplicated)
|
|
113
|
+
clsx('a b', 'b c')
|
|
114
|
+
# => 'a b c'
|
|
115
|
+
|
|
116
|
+
# Whitespace is normalized; blank/whitespace-only => nil
|
|
117
|
+
clsx(" a\tb\n\nc ")
|
|
118
|
+
# => 'a b c'
|
|
119
|
+
|
|
78
120
|
# Kitchen sink (with nesting)
|
|
79
121
|
cn('foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya')
|
|
80
122
|
# => 'foo bar hello world cya'
|
|
81
123
|
```
|
|
82
124
|
|
|
83
|
-
###
|
|
125
|
+
### Template engines
|
|
84
126
|
|
|
85
127
|
```erb
|
|
128
|
+
<%# ERB %>
|
|
86
129
|
<%= tag.div class: clsx('foo', 'baz', 'is-active': @active) do %>
|
|
87
130
|
Hello, world!
|
|
88
131
|
<% end %>
|
|
@@ -92,68 +135,137 @@ cn('foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya')
|
|
|
92
135
|
</div>
|
|
93
136
|
```
|
|
94
137
|
|
|
95
|
-
### HAML
|
|
96
|
-
|
|
97
138
|
```haml
|
|
139
|
+
-# HAML
|
|
98
140
|
%div{class: clsx('foo', 'baz', 'is-active': @active)}
|
|
99
141
|
Hello, world!
|
|
100
142
|
```
|
|
101
143
|
|
|
102
|
-
### Slim
|
|
103
|
-
|
|
104
144
|
```slim
|
|
145
|
+
/ Slim
|
|
105
146
|
div class=clsx('foo', 'baz', 'is-active': @active)
|
|
106
147
|
| Hello, world!
|
|
107
148
|
```
|
|
108
149
|
|
|
109
|
-
## Framework
|
|
150
|
+
## Framework Integration
|
|
110
151
|
|
|
111
|
-
|
|
152
|
+
Plain Rails views (ERB, HAML, Slim) get `clsx`/`cn` automatically. ViewComponent and Phlex
|
|
153
|
+
render in their own object hierarchies, so include `Clsx::Helper` once in your base class.
|
|
154
|
+
|
|
155
|
+
### Tailwind CSS
|
|
156
|
+
|
|
157
|
+
Compose conditional utilities without string juggling:
|
|
112
158
|
|
|
113
159
|
```ruby
|
|
114
|
-
class
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
160
|
+
class NavLink < ViewComponent::Base
|
|
161
|
+
include Clsx::Helper
|
|
162
|
+
|
|
163
|
+
def initialize(active: false)
|
|
164
|
+
@active = active
|
|
118
165
|
end
|
|
119
166
|
|
|
120
167
|
def classes
|
|
121
|
-
clsx(
|
|
168
|
+
clsx(
|
|
169
|
+
'px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
|
170
|
+
'text-white bg-indigo-600': @active,
|
|
171
|
+
'text-gray-300 hover:text-white hover:bg-gray-700': !@active
|
|
172
|
+
)
|
|
122
173
|
end
|
|
123
174
|
end
|
|
124
175
|
```
|
|
125
176
|
|
|
126
|
-
|
|
177
|
+
#### Merging conflicting utilities
|
|
178
|
+
|
|
179
|
+
`clsx`/`cn` keep every class, so conflicting Tailwind utilities both survive:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
clsx('px-2 px-4') # => 'px-2 px-4'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
For conflict resolution, opt into the [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge)
|
|
186
|
+
gem. Add it to your `Gemfile` (clsx-rails itself pulls in only clsx-ruby), then require the
|
|
187
|
+
integration once at boot:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
# Gemfile
|
|
191
|
+
gem 'tailwind_merge'
|
|
192
|
+
|
|
193
|
+
# config/initializers/clsx.rb
|
|
194
|
+
require 'clsx/tailwind_merge'
|
|
195
|
+
|
|
196
|
+
# Optional: configure the merger (prefix, cache size, custom theme, …)
|
|
197
|
+
Clsx.merger = TailwindMerge::Merger.new
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
This adds a merged variant — `twm` — available in every view alongside `clsx`/`cn`.
|
|
201
|
+
`clsx`/`cn` stay pure; only `twm` merges, and the last conflicting utility wins:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
twm('px-2 px-4') # => 'px-4'
|
|
205
|
+
twm('p-4', 'p-2', 'bg-red', 'bg-blue') # => 'p-2 bg-blue'
|
|
206
|
+
clsx('px-2 px-4') # => 'px-2 px-4' (unchanged)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
It accepts the same arguments as `clsx` (strings, hashes, arrays, nesting), then merges:
|
|
127
210
|
|
|
128
211
|
```ruby
|
|
129
|
-
|
|
212
|
+
twm(['px-2', 'rounded'], 'px-4', 'px-6': active) # => 'rounded px-6' (when active)
|
|
213
|
+
|
|
214
|
+
# Also available as a bracket API and a module method:
|
|
215
|
+
Twm['px-2 px-4'] # => 'px-4'
|
|
216
|
+
Clsx.twm('px-2 px-4') # => 'px-4'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### ViewComponent
|
|
220
|
+
|
|
221
|
+
Include the mixin once in your base component instead of per component:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
class ApplicationComponent < ViewComponent::Base
|
|
130
225
|
include Clsx::Helper
|
|
226
|
+
end
|
|
227
|
+
```
|
|
131
228
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
229
|
+
Accept a caller-supplied `class:` and merge it — clsx dedupes across every argument, so
|
|
230
|
+
callers can extend or repeat classes safely:
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
class AlertComponent < ApplicationComponent
|
|
234
|
+
def initialize(variant: :info, dismissible: false, class: nil)
|
|
235
|
+
@variant = variant
|
|
236
|
+
@dismissible = dismissible
|
|
237
|
+
@html_class = binding.local_variable_get(:class) # `class` is a Ruby keyword
|
|
135
238
|
end
|
|
136
239
|
|
|
137
|
-
def
|
|
138
|
-
|
|
240
|
+
def classes
|
|
241
|
+
clsx("alert", "alert-#{@variant}", @html_class, dismissible: @dismissible)
|
|
139
242
|
end
|
|
140
243
|
end
|
|
141
244
|
```
|
|
142
245
|
|
|
143
|
-
|
|
246
|
+
```erb
|
|
247
|
+
<div class="<%= classes %>">...</div>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Phlex
|
|
251
|
+
|
|
252
|
+
Include the mixin once in your base component, then merge caller-supplied attributes —
|
|
253
|
+
clsx dedupes across every argument:
|
|
144
254
|
|
|
145
255
|
```ruby
|
|
146
|
-
class
|
|
147
|
-
|
|
148
|
-
|
|
256
|
+
class ApplicationComponent < Phlex::HTML
|
|
257
|
+
include Clsx::Helper
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
class Badge < ApplicationComponent
|
|
261
|
+
def initialize(color: :blue, pill: false, **attributes)
|
|
262
|
+
@color = color
|
|
263
|
+
@pill = pill
|
|
264
|
+
@attributes = attributes
|
|
149
265
|
end
|
|
150
266
|
|
|
151
|
-
def
|
|
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
|
-
)
|
|
267
|
+
def view_template
|
|
268
|
+
span(class: clsx("badge", "badge-#{@color}", @attributes[:class], pill: @pill)) { yield }
|
|
157
269
|
end
|
|
158
270
|
end
|
|
159
271
|
```
|
|
@@ -165,30 +277,27 @@ end
|
|
|
165
277
|
clsx(nil, false) # => nil
|
|
166
278
|
```
|
|
167
279
|
|
|
168
|
-
2. **Deduplication** —
|
|
280
|
+
2. **Deduplication** — Duplicate classes are automatically removed, even across multi-token strings:
|
|
169
281
|
```ruby
|
|
170
|
-
clsx('foo', 'foo')
|
|
282
|
+
clsx('foo', 'foo') # => 'foo'
|
|
283
|
+
clsx('foo bar', 'foo') # => 'foo bar'
|
|
171
284
|
```
|
|
172
285
|
|
|
173
|
-
3. **Falsy values** —
|
|
286
|
+
3. **Falsy values** — In Ruby only `false` and `nil` are falsy, so `0`, `''`, `[]`, `{}` are all truthy:
|
|
174
287
|
```ruby
|
|
175
288
|
clsx('foo' => 0, bar: []) # => 'foo bar'
|
|
176
289
|
```
|
|
177
290
|
|
|
178
|
-
4. **Complex hash keys** —
|
|
291
|
+
4. **Complex hash keys** — Any valid `clsx` input works as a hash key:
|
|
179
292
|
```ruby
|
|
180
293
|
clsx([{ foo: true }, 'bar'] => true) # => 'foo bar'
|
|
181
294
|
```
|
|
182
295
|
|
|
183
|
-
5. **Ignored values** —
|
|
296
|
+
5. **Ignored values** — Boolean `true` and `Proc`/lambda objects are silently ignored:
|
|
184
297
|
```ruby
|
|
185
298
|
clsx('', proc {}, -> {}, nil, false, true) # => nil
|
|
186
299
|
```
|
|
187
300
|
|
|
188
|
-
## Looking for a framework-agnostic version?
|
|
189
|
-
|
|
190
|
-
See [clsx-ruby](https://github.com/svyatov/clsx-ruby) — works with Rails, Sinatra, Hanami, or plain Ruby.
|
|
191
|
-
|
|
192
301
|
## Supported Versions
|
|
193
302
|
|
|
194
303
|
Ruby 3.2+ and Rails 7.2+.
|
|
@@ -196,9 +305,9 @@ Ruby 3.2+ and Rails 7.2+.
|
|
|
196
305
|
## Development
|
|
197
306
|
|
|
198
307
|
```bash
|
|
199
|
-
bin/setup
|
|
200
|
-
bundle exec rake test
|
|
201
|
-
bundle exec ruby benchmark/run.rb
|
|
308
|
+
bin/setup # install dependencies
|
|
309
|
+
bundle exec rake test # run tests
|
|
310
|
+
bundle exec ruby benchmark/run.rb # run benchmarks
|
|
202
311
|
```
|
|
203
312
|
|
|
204
313
|
## Contributing
|
data/lib/clsx/rails/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clsx-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leonid Svyatov
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
@@ -29,16 +29,17 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '1.
|
|
32
|
+
version: '1.2'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '1.
|
|
40
|
-
description:
|
|
41
|
-
|
|
39
|
+
version: '1.2'
|
|
40
|
+
description: Build CSS class strings from conditional expressions, hashes, arrays,
|
|
41
|
+
or nested structures. 2-4x faster drop-in replacement for Rails class_names. Supports
|
|
42
|
+
ViewComponent, Phlex, and Tailwind CSS.
|
|
42
43
|
email:
|
|
43
44
|
- leonid@svyatov.com
|
|
44
45
|
executables: []
|
|
@@ -54,7 +55,6 @@ homepage: https://github.com/svyatov/clsx-rails
|
|
|
54
55
|
licenses:
|
|
55
56
|
- MIT
|
|
56
57
|
metadata:
|
|
57
|
-
homepage_uri: https://github.com/svyatov/clsx-rails
|
|
58
58
|
source_code_uri: https://github.com/svyatov/clsx-rails
|
|
59
59
|
changelog_uri: https://github.com/svyatov/clsx-rails/blob/main/CHANGELOG.md
|
|
60
60
|
rubygems_mfa_required: 'true'
|
|
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
73
|
version: '0'
|
|
74
74
|
requirements: []
|
|
75
|
-
rubygems_version: 4.0.
|
|
75
|
+
rubygems_version: 4.0.12
|
|
76
76
|
specification_version: 4
|
|
77
|
-
summary:
|
|
77
|
+
summary: The fastest conditional CSS class builder for Rails
|
|
78
78
|
test_files: []
|