clsx-rails 3.0.1 → 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 +8 -0
- data/README.md +159 -51
- data/lib/clsx/rails/version.rb +1 -1
- metadata +4 -10
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,14 @@ 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
|
+
|
|
9
17
|
## v3.0.1 (2026-02-27)
|
|
10
18
|
|
|
11
19
|
### Changed
|
data/README.md
CHANGED
|
@@ -1,9 +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
|
-
> The fastest conditional CSS class builder for Rails — 2
|
|
3
|
+
> The fastest conditional CSS class builder for Rails — 2–4x faster drop-in replacement for `class_names`.
|
|
4
4
|
> Powered by [clsx-ruby](https://github.com/svyatov/clsx-ruby).
|
|
5
5
|
|
|
6
|
-
|
|
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)
|
|
7
27
|
|
|
8
28
|
## Quick Start
|
|
9
29
|
|
|
@@ -14,7 +34,7 @@ bundle add clsx-rails
|
|
|
14
34
|
Or add it manually to the Gemfile:
|
|
15
35
|
|
|
16
36
|
```ruby
|
|
17
|
-
gem 'clsx-rails', '~> 3.
|
|
37
|
+
gem 'clsx-rails', '~> 3.1'
|
|
18
38
|
```
|
|
19
39
|
|
|
20
40
|
That's it — `clsx` and `cn` are now available in all your views:
|
|
@@ -25,11 +45,11 @@ That's it — `clsx` and `cn` are now available in all your views:
|
|
|
25
45
|
<% end %>
|
|
26
46
|
```
|
|
27
47
|
|
|
28
|
-
## Why clsx-rails
|
|
48
|
+
## Why clsx-rails?
|
|
29
49
|
|
|
30
|
-
###
|
|
50
|
+
### Blazing fast
|
|
31
51
|
|
|
32
|
-
**2
|
|
52
|
+
**2–4x faster** than Rails `class_names` — never slower, on realistic markup:
|
|
33
53
|
|
|
34
54
|
| Scenario | clsx | Rails `class_names` | Speedup |
|
|
35
55
|
|---|---|---|---|
|
|
@@ -42,19 +62,24 @@ That's it — `clsx` and `cn` are now available in all your views:
|
|
|
42
62
|
|
|
43
63
|
<sup>Ruby 4.0.1, Apple M1 Pro. Reproduce: `bundle exec ruby benchmark/run.rb`</sup>
|
|
44
64
|
|
|
45
|
-
### More
|
|
65
|
+
### More feature-rich than `class_names`
|
|
46
66
|
|
|
47
67
|
| Feature | clsx-rails | Rails `class_names` |
|
|
48
68
|
|---|---|---|
|
|
49
|
-
| Conditional classes |
|
|
50
|
-
| Auto-deduplication |
|
|
51
|
-
| 2
|
|
52
|
-
| Returns `nil` when empty |
|
|
53
|
-
| Complex hash keys |
|
|
54
|
-
|
|
|
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 | ✅ | ❌ |
|
|
55
76
|
|
|
56
77
|
## Usage
|
|
57
78
|
|
|
79
|
+
`clsx` and its `cn` alias are available in every view — no `include`, no setup.
|
|
80
|
+
|
|
81
|
+
### Input types
|
|
82
|
+
|
|
58
83
|
```ruby
|
|
59
84
|
# Strings (variadic)
|
|
60
85
|
clsx('foo', true && 'bar', 'baz')
|
|
@@ -76,14 +101,31 @@ cn(['foo', nil, false, 'bar'])
|
|
|
76
101
|
clsx(['foo'], ['', nil, false, 'bar'], [['baz', [['hello'], 'there']]])
|
|
77
102
|
# => 'foo bar baz hello there'
|
|
78
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
|
+
|
|
79
120
|
# Kitchen sink (with nesting)
|
|
80
121
|
cn('foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya')
|
|
81
122
|
# => 'foo bar hello world cya'
|
|
82
123
|
```
|
|
83
124
|
|
|
84
|
-
###
|
|
125
|
+
### Template engines
|
|
85
126
|
|
|
86
127
|
```erb
|
|
128
|
+
<%# ERB %>
|
|
87
129
|
<%= tag.div class: clsx('foo', 'baz', 'is-active': @active) do %>
|
|
88
130
|
Hello, world!
|
|
89
131
|
<% end %>
|
|
@@ -93,68 +135,137 @@ cn('foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya')
|
|
|
93
135
|
</div>
|
|
94
136
|
```
|
|
95
137
|
|
|
96
|
-
### HAML
|
|
97
|
-
|
|
98
138
|
```haml
|
|
139
|
+
-# HAML
|
|
99
140
|
%div{class: clsx('foo', 'baz', 'is-active': @active)}
|
|
100
141
|
Hello, world!
|
|
101
142
|
```
|
|
102
143
|
|
|
103
|
-
### Slim
|
|
104
|
-
|
|
105
144
|
```slim
|
|
145
|
+
/ Slim
|
|
106
146
|
div class=clsx('foo', 'baz', 'is-active': @active)
|
|
107
147
|
| Hello, world!
|
|
108
148
|
```
|
|
109
149
|
|
|
110
|
-
## Framework
|
|
150
|
+
## Framework Integration
|
|
111
151
|
|
|
112
|
-
|
|
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:
|
|
113
158
|
|
|
114
159
|
```ruby
|
|
115
|
-
class
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
160
|
+
class NavLink < ViewComponent::Base
|
|
161
|
+
include Clsx::Helper
|
|
162
|
+
|
|
163
|
+
def initialize(active: false)
|
|
164
|
+
@active = active
|
|
119
165
|
end
|
|
120
166
|
|
|
121
167
|
def classes
|
|
122
|
-
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
|
+
)
|
|
123
173
|
end
|
|
124
174
|
end
|
|
125
175
|
```
|
|
126
176
|
|
|
127
|
-
|
|
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:
|
|
128
210
|
|
|
129
211
|
```ruby
|
|
130
|
-
|
|
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
|
|
131
225
|
include Clsx::Helper
|
|
226
|
+
end
|
|
227
|
+
```
|
|
132
228
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
238
|
end
|
|
137
239
|
|
|
138
|
-
def
|
|
139
|
-
|
|
240
|
+
def classes
|
|
241
|
+
clsx("alert", "alert-#{@variant}", @html_class, dismissible: @dismissible)
|
|
140
242
|
end
|
|
141
243
|
end
|
|
142
244
|
```
|
|
143
245
|
|
|
144
|
-
|
|
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:
|
|
145
254
|
|
|
146
255
|
```ruby
|
|
147
|
-
class
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
150
265
|
end
|
|
151
266
|
|
|
152
|
-
def
|
|
153
|
-
clsx(
|
|
154
|
-
'px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
|
155
|
-
'text-white bg-indigo-600': @active,
|
|
156
|
-
'text-gray-300 hover:text-white hover:bg-gray-700': !@active
|
|
157
|
-
)
|
|
267
|
+
def view_template
|
|
268
|
+
span(class: clsx("badge", "badge-#{@color}", @attributes[:class], pill: @pill)) { yield }
|
|
158
269
|
end
|
|
159
270
|
end
|
|
160
271
|
```
|
|
@@ -166,30 +277,27 @@ end
|
|
|
166
277
|
clsx(nil, false) # => nil
|
|
167
278
|
```
|
|
168
279
|
|
|
169
|
-
2. **Deduplication** —
|
|
280
|
+
2. **Deduplication** — Duplicate classes are automatically removed, even across multi-token strings:
|
|
170
281
|
```ruby
|
|
171
|
-
clsx('foo', 'foo')
|
|
282
|
+
clsx('foo', 'foo') # => 'foo'
|
|
283
|
+
clsx('foo bar', 'foo') # => 'foo bar'
|
|
172
284
|
```
|
|
173
285
|
|
|
174
|
-
3. **Falsy values** —
|
|
286
|
+
3. **Falsy values** — In Ruby only `false` and `nil` are falsy, so `0`, `''`, `[]`, `{}` are all truthy:
|
|
175
287
|
```ruby
|
|
176
288
|
clsx('foo' => 0, bar: []) # => 'foo bar'
|
|
177
289
|
```
|
|
178
290
|
|
|
179
|
-
4. **Complex hash keys** —
|
|
291
|
+
4. **Complex hash keys** — Any valid `clsx` input works as a hash key:
|
|
180
292
|
```ruby
|
|
181
293
|
clsx([{ foo: true }, 'bar'] => true) # => 'foo bar'
|
|
182
294
|
```
|
|
183
295
|
|
|
184
|
-
5. **Ignored values** —
|
|
296
|
+
5. **Ignored values** — Boolean `true` and `Proc`/lambda objects are silently ignored:
|
|
185
297
|
```ruby
|
|
186
298
|
clsx('', proc {}, -> {}, nil, false, true) # => nil
|
|
187
299
|
```
|
|
188
300
|
|
|
189
|
-
## Looking for a framework-agnostic version?
|
|
190
|
-
|
|
191
|
-
See [clsx-ruby](https://github.com/svyatov/clsx-ruby) — works with Rails, Sinatra, Hanami, or plain Ruby.
|
|
192
|
-
|
|
193
301
|
## Supported Versions
|
|
194
302
|
|
|
195
303
|
Ruby 3.2+ and Rails 7.2+.
|
data/lib/clsx/rails/version.rb
CHANGED
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: 3.0
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leonid Svyatov
|
|
@@ -29,20 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '1.
|
|
33
|
-
- - ">="
|
|
34
|
-
- !ruby/object:Gem::Version
|
|
35
|
-
version: 1.1.3
|
|
32
|
+
version: '1.2'
|
|
36
33
|
type: :runtime
|
|
37
34
|
prerelease: false
|
|
38
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
39
36
|
requirements:
|
|
40
37
|
- - "~>"
|
|
41
38
|
- !ruby/object:Gem::Version
|
|
42
|
-
version: '1.
|
|
43
|
-
- - ">="
|
|
44
|
-
- !ruby/object:Gem::Version
|
|
45
|
-
version: 1.1.3
|
|
39
|
+
version: '1.2'
|
|
46
40
|
description: Build CSS class strings from conditional expressions, hashes, arrays,
|
|
47
41
|
or nested structures. 2-4x faster drop-in replacement for Rails class_names. Supports
|
|
48
42
|
ViewComponent, Phlex, and Tailwind CSS.
|
|
@@ -78,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
72
|
- !ruby/object:Gem::Version
|
|
79
73
|
version: '0'
|
|
80
74
|
requirements: []
|
|
81
|
-
rubygems_version: 4.0.
|
|
75
|
+
rubygems_version: 4.0.12
|
|
82
76
|
specification_version: 4
|
|
83
77
|
summary: The fastest conditional CSS class builder for Rails
|
|
84
78
|
test_files: []
|