view_component-contrib 0.2.2 → 0.2.4
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 +11 -0
- data/README.md +72 -0
- data/lib/.rbnext/3.0/view_component_contrib/style_variants.rb +242 -0
- data/lib/view_component_contrib/style_variants.rb +34 -3
- data/lib/view_component_contrib/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66619dee8f7cb3f26eddce9c5809ce7c879f8f464f2fa8ed140cd5a0ba08bfbd
|
4
|
+
data.tar.gz: e0f4013b8bebd93cab59f17ed52f1a6bd92c401b672919f49b386019296b0b1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d35a906a4c66e3097266b2b3f8df662fac38dd5f0ce4580e35d5e0a99cd3c90ee2ce6c5db36c3fc8ec16a9e9359fc600e61b205075d55ac36d853b77e6afb00
|
7
|
+
data.tar.gz: d66d56152567959e2f9cbe57ee0b9becaaeeed366179c88bfaf17b9bc1dd92b50295c996a03e169bfd397f9d2444d4ba8e3c9e7e7c48fe91cca49c5f658d00ca
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.2.4 (2025-01-03)
|
6
|
+
|
7
|
+
- Add inheritance strategies to style variants ([@omarluq][])
|
8
|
+
|
9
|
+
- Add special `class:` variant to `style` helper. For appending classes.
|
10
|
+
Inspired by https://cva.style/docs/getting-started/extending-components
|
11
|
+
|
12
|
+
## 0.2.3 (2024-07-31)
|
13
|
+
|
14
|
+
- Fix publishing transpiled files (and bring Ruby 2.7 support back) ([@palkan][])
|
15
|
+
|
5
16
|
## 0.2.2 (2023-11-29)
|
6
17
|
|
7
18
|
- Add `compound` styles support. ([@palkan][])
|
data/README.md
CHANGED
@@ -277,6 +277,15 @@ And in the template:
|
|
277
277
|
</div>
|
278
278
|
```
|
279
279
|
|
280
|
+
You can also add additional classes through thr `style` method using the special `class:` variant, like so:
|
281
|
+
|
282
|
+
```erb
|
283
|
+
<div>
|
284
|
+
<button class="<%= style(size:, theme:, class: 'extra-class') %>">Click me</button>
|
285
|
+
<img src="..." class="<%= style(:image, orient: :portrait) %>">
|
286
|
+
</div>
|
287
|
+
```
|
288
|
+
|
280
289
|
Finally, you can inject into the class list compilation process to add your own logic:
|
281
290
|
|
282
291
|
```ruby
|
@@ -292,6 +301,69 @@ class ButtonComponent < ViewComponent::Base
|
|
292
301
|
end
|
293
302
|
```
|
294
303
|
|
304
|
+
### Style variants inheritance
|
305
|
+
|
306
|
+
Style variants support three inheritance strategies when extending components:
|
307
|
+
|
308
|
+
1. `override` (default behavior): Completely replaces parent variants.
|
309
|
+
2. `merge` (deep merge): Preserves all variant keys unless explicitly overwritten.
|
310
|
+
3. `extend` (shallow merge): Preserves variants unless explicitly overwritten.
|
311
|
+
|
312
|
+
Consider an example:
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
class Parent::Component < ViewComponent::Base
|
316
|
+
include ViewComponentContrib::StyleVariants
|
317
|
+
|
318
|
+
style do
|
319
|
+
variants do
|
320
|
+
size {
|
321
|
+
md { "text-md" }
|
322
|
+
lg { "text-lg" }
|
323
|
+
}
|
324
|
+
disabled {
|
325
|
+
yes { "opacity-50" }
|
326
|
+
}
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# Using override strategy (default)
|
332
|
+
class Child::Component < Parent::Component
|
333
|
+
style do
|
334
|
+
variants do
|
335
|
+
size {
|
336
|
+
lg { "text-larger" }
|
337
|
+
}
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Using merge strategy
|
343
|
+
class Child::Component < Parent::Component
|
344
|
+
style do
|
345
|
+
variants(strategy: :merge) do
|
346
|
+
size {
|
347
|
+
lg { "text-larger" }
|
348
|
+
}
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Using extend strategy
|
354
|
+
class Child::Component < Parent::Component
|
355
|
+
style do
|
356
|
+
variants(strategy: :extend) do
|
357
|
+
size {
|
358
|
+
lg { "text-larger" }
|
359
|
+
}
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
```
|
364
|
+
|
365
|
+
In this example, the `override` strategy will only keep the `size.lg` variant, dropping all others. The `merge` strategy preserves all variants and their keys, only replacing the `size.lg` value. The `extend` strategy keeps all variants but replaces all keys of the overwritten `size` variant.
|
366
|
+
|
295
367
|
### Dependent (or compound) styles
|
296
368
|
|
297
369
|
Sometimes it might be necessary to define complex styling rules, e.g., when a combination of variants requires adding additional styles. That's where usage of Ruby blocks for configuration becomes useful. For example:
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponentContrib
|
4
|
+
# Organize style in variants that can be combined.
|
5
|
+
# Inspired by https://www.tailwind-variants.org and https://cva.style/docs/getting-started/variants
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# class ButtonComponent < ViewComponent::Base
|
10
|
+
# include ViewComponentContrib::StyleVariants
|
11
|
+
#
|
12
|
+
# erb_template <<~ERB
|
13
|
+
# <button class="<%= style(size: 'sm', color: 'secondary') %>">Click me</button>
|
14
|
+
# ERB
|
15
|
+
#
|
16
|
+
# style do
|
17
|
+
# base {
|
18
|
+
# %w(
|
19
|
+
# font-medium bg-blue-500 text-white rounded-full
|
20
|
+
# )
|
21
|
+
# }
|
22
|
+
# variants {
|
23
|
+
# color {
|
24
|
+
# primary { %w(bg-blue-500 text-white) }
|
25
|
+
# secondary { %w(bg-purple-500 text-white) }
|
26
|
+
# }
|
27
|
+
# size {
|
28
|
+
# sm { "text-sm" }
|
29
|
+
# md { "text-base" }
|
30
|
+
# lg { "px-4 py-3 text-lg" }
|
31
|
+
# }
|
32
|
+
# }
|
33
|
+
# defaults { {size: :md, color: :primary} }
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# attr_reader :size, :color
|
37
|
+
#
|
38
|
+
# def initialize(size: :md, color: :primary)
|
39
|
+
# @size = size
|
40
|
+
# @color = color
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
module StyleVariants
|
45
|
+
class VariantBuilder
|
46
|
+
attr_reader :unwrap_blocks
|
47
|
+
|
48
|
+
def initialize(unwrap_blocks = true)
|
49
|
+
@unwrap_blocks = unwrap_blocks
|
50
|
+
@variants = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def build(&block)
|
54
|
+
instance_eval(&block)
|
55
|
+
@variants
|
56
|
+
end
|
57
|
+
|
58
|
+
def respond_to_missing?(name, include_private = false)
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(name, &block)
|
63
|
+
return super unless block_given?
|
64
|
+
|
65
|
+
@variants[name] = if unwrap_blocks
|
66
|
+
VariantBuilder.new(false).build(&block)
|
67
|
+
else
|
68
|
+
block
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class StyleSet
|
74
|
+
def initialize(&init_block)
|
75
|
+
@base_block = nil
|
76
|
+
@defaults = {}
|
77
|
+
@variants = {}
|
78
|
+
@compounds = {}
|
79
|
+
|
80
|
+
return unless init_block
|
81
|
+
|
82
|
+
@init_block = init_block
|
83
|
+
instance_eval(&init_block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def base(&block)
|
87
|
+
@base_block = block
|
88
|
+
end
|
89
|
+
|
90
|
+
def defaults(&block)
|
91
|
+
@defaults = block.call.freeze
|
92
|
+
end
|
93
|
+
|
94
|
+
def variants(strategy: :override, &block)
|
95
|
+
variants = build_variants(&block)
|
96
|
+
@variants = handle_variants(variants, strategy)
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_variants(&block)
|
100
|
+
VariantBuilder.new(true).build(&block)
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_variants(variants, strategy)
|
104
|
+
return variants if strategy == :override
|
105
|
+
|
106
|
+
parent_variants = find_parent_variants
|
107
|
+
return variants unless parent_variants
|
108
|
+
|
109
|
+
return parent_variants.deep_merge(variants) if strategy == :merge
|
110
|
+
|
111
|
+
parent_variants.merge(variants) if strategy == :extend
|
112
|
+
end
|
113
|
+
|
114
|
+
def find_parent_variants
|
115
|
+
parent_component = @init_block.binding.receiver.superclass
|
116
|
+
return unless parent_component.respond_to?(:style_config)
|
117
|
+
|
118
|
+
parent_config = parent_component.style_config
|
119
|
+
default_parent_style = parent_component.default_style_name
|
120
|
+
parent_style_set = parent_config.instance_variable_get(:@styles)[default_parent_style.to_sym]
|
121
|
+
parent_style_set.instance_variable_get(:@variants).deep_dup
|
122
|
+
end
|
123
|
+
|
124
|
+
def compound(**variants, &block)
|
125
|
+
@compounds[variants] = block
|
126
|
+
end
|
127
|
+
|
128
|
+
def compile(**variants)
|
129
|
+
acc = Array(@base_block&.call || [])
|
130
|
+
|
131
|
+
config = @defaults.merge(variants.compact)
|
132
|
+
|
133
|
+
config.each do |variant, value|
|
134
|
+
value = cast_value(value)
|
135
|
+
variant = @variants.dig(variant, value) || next
|
136
|
+
styles = variant.is_a?(::Proc) ? variant.call(**config) : variant
|
137
|
+
acc.concat(Array(styles))
|
138
|
+
end
|
139
|
+
|
140
|
+
@compounds.each do |compound, value|
|
141
|
+
next unless compound.all? { |k, v| config[k] == v }
|
142
|
+
|
143
|
+
styles = value.is_a?(::Proc) ? value.call(**config) : value
|
144
|
+
acc.concat(Array(styles))
|
145
|
+
end
|
146
|
+
|
147
|
+
acc.concat(Array(config[:class]))
|
148
|
+
acc.concat(Array(config[:class_name]))
|
149
|
+
acc
|
150
|
+
end
|
151
|
+
|
152
|
+
def dup
|
153
|
+
copy = super
|
154
|
+
copy.instance_variable_set(:@defaults, @defaults.dup)
|
155
|
+
copy.instance_variable_set(:@variants, @variants.dup)
|
156
|
+
copy.instance_variable_set(:@compounds, @compounds.dup)
|
157
|
+
copy
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def cast_value(val)
|
163
|
+
case val
|
164
|
+
when true then :yes
|
165
|
+
when false then :no
|
166
|
+
else
|
167
|
+
val
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class StyleConfig # :nodoc:
|
173
|
+
DEFAULT_POST_PROCESSOR = ->(compiled) { compiled.join(" ") }
|
174
|
+
|
175
|
+
attr_reader :postprocessor
|
176
|
+
|
177
|
+
def initialize
|
178
|
+
@styles = {}
|
179
|
+
@postprocessor = DEFAULT_POST_PROCESSOR
|
180
|
+
end
|
181
|
+
|
182
|
+
def define(name, &block)
|
183
|
+
styles[name] = StyleSet.new(&block)
|
184
|
+
end
|
185
|
+
|
186
|
+
def compile(name, **variants)
|
187
|
+
styles[name]&.compile(**variants).then do |compiled|
|
188
|
+
next unless compiled
|
189
|
+
|
190
|
+
postprocess(compiled)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Allow defining a custom postprocessor
|
195
|
+
def postprocess_with(callable = nil, &block)
|
196
|
+
@postprocessor = callable || block
|
197
|
+
end
|
198
|
+
|
199
|
+
def dup
|
200
|
+
copy = super
|
201
|
+
copy.instance_variable_set(:@styles, @styles.dup)
|
202
|
+
copy
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
attr_reader :styles
|
208
|
+
|
209
|
+
def postprocess(compiled) ; postprocessor.call(compiled); end
|
210
|
+
end
|
211
|
+
|
212
|
+
def self.included(base)
|
213
|
+
base.extend ClassMethods
|
214
|
+
end
|
215
|
+
|
216
|
+
module ClassMethods
|
217
|
+
# Returns the name of the default style set based on the class name:
|
218
|
+
# MyComponent::Component => my_component
|
219
|
+
# Namespaced::MyComponent => my_component
|
220
|
+
def default_style_name
|
221
|
+
@default_style_name ||= name.demodulize.sub(/(::Component|Component)$/, "").underscore.presence || "component"
|
222
|
+
end
|
223
|
+
|
224
|
+
def style(name = default_style_name, &block)
|
225
|
+
style_config.define(name.to_sym, &block)
|
226
|
+
end
|
227
|
+
|
228
|
+
def style_config
|
229
|
+
@style_config ||=
|
230
|
+
if superclass.respond_to?(:style_config)
|
231
|
+
superclass.style_config.dup
|
232
|
+
else
|
233
|
+
StyleConfig.new
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def style(name = self.class.default_style_name, **variants)
|
239
|
+
self.class.style_config.compile(name.to_sym, **variants)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -77,7 +77,10 @@ module ViewComponentContrib
|
|
77
77
|
@variants = {}
|
78
78
|
@compounds = {}
|
79
79
|
|
80
|
-
|
80
|
+
return unless init_block
|
81
|
+
|
82
|
+
@init_block = init_block
|
83
|
+
instance_eval(&init_block)
|
81
84
|
end
|
82
85
|
|
83
86
|
def base(&block)
|
@@ -88,8 +91,34 @@ module ViewComponentContrib
|
|
88
91
|
@defaults = block.call.freeze
|
89
92
|
end
|
90
93
|
|
91
|
-
def variants(&block)
|
92
|
-
|
94
|
+
def variants(strategy: :override, &block)
|
95
|
+
variants = build_variants(&block)
|
96
|
+
@variants = handle_variants(variants, strategy)
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_variants(&block)
|
100
|
+
VariantBuilder.new(true).build(&block)
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_variants(variants, strategy)
|
104
|
+
return variants if strategy == :override
|
105
|
+
|
106
|
+
parent_variants = find_parent_variants
|
107
|
+
return variants unless parent_variants
|
108
|
+
|
109
|
+
return parent_variants.deep_merge(variants) if strategy == :merge
|
110
|
+
|
111
|
+
parent_variants.merge(variants) if strategy == :extend
|
112
|
+
end
|
113
|
+
|
114
|
+
def find_parent_variants
|
115
|
+
parent_component = @init_block.binding.receiver.superclass
|
116
|
+
return unless parent_component.respond_to?(:style_config)
|
117
|
+
|
118
|
+
parent_config = parent_component.style_config
|
119
|
+
default_parent_style = parent_component.default_style_name
|
120
|
+
parent_style_set = parent_config.instance_variable_get(:@styles)[default_parent_style.to_sym]
|
121
|
+
parent_style_set.instance_variable_get(:@variants).deep_dup
|
93
122
|
end
|
94
123
|
|
95
124
|
def compound(**variants, &block)
|
@@ -115,6 +144,8 @@ module ViewComponentContrib
|
|
115
144
|
acc.concat(Array(styles))
|
116
145
|
end
|
117
146
|
|
147
|
+
acc.concat(Array(config[:class]))
|
148
|
+
acc.concat(Array(config[:class_name]))
|
118
149
|
acc
|
119
150
|
end
|
120
151
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component-contrib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: view_component
|
@@ -147,6 +147,7 @@ files:
|
|
147
147
|
- LICENSE.txt
|
148
148
|
- README.md
|
149
149
|
- app/views/view_component_contrib/preview.html.erb
|
150
|
+
- lib/.rbnext/3.0/view_component_contrib/style_variants.rb
|
150
151
|
- lib/view_component-contrib.rb
|
151
152
|
- lib/view_component_contrib.rb
|
152
153
|
- lib/view_component_contrib/base.rb
|
@@ -170,7 +171,7 @@ metadata:
|
|
170
171
|
documentation_uri: http://github.com/palkan/view_component-contrib
|
171
172
|
homepage_uri: http://github.com/palkan/view_component-contrib
|
172
173
|
source_code_uri: http://github.com/palkan/view_component-contrib
|
173
|
-
post_install_message:
|
174
|
+
post_install_message:
|
174
175
|
rdoc_options: []
|
175
176
|
require_paths:
|
176
177
|
- lib
|
@@ -185,8 +186,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
186
|
- !ruby/object:Gem::Version
|
186
187
|
version: '0'
|
187
188
|
requirements: []
|
188
|
-
rubygems_version: 3.4.
|
189
|
-
signing_key:
|
189
|
+
rubygems_version: 3.4.19
|
190
|
+
signing_key:
|
190
191
|
specification_version: 4
|
191
192
|
summary: A collection of extensions and developer tools for ViewComponent
|
192
193
|
test_files: []
|