view_component-scoped_styles 0.1.0 → 0.3.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/README.md +50 -2
- data/lib/generators/view_component/scoped_styles/install_generator.rb +4 -0
- data/lib/generators/view_component/scoped_styles/templates/view_component_scoped_styles.rb.tt +3 -0
- data/lib/view_component/scoped_styles/concern.rb +79 -16
- data/lib/view_component/scoped_styles/configuration.rb +6 -0
- data/lib/view_component/scoped_styles/railtie.rb +7 -2
- data/lib/view_component/scoped_styles/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4518c4426e35b342ba212fb90ab4d5e4692d7427fdf5fdbe5ced923a71cc1e42
|
|
4
|
+
data.tar.gz: 601433c85c4aad6602831f8c53e662f73080d8bb40f9104f28fc8842411e0569
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e9a1ab80bf24d000347be17e22d19e684a1ec1f70a08cf95952618c20891b84b8c50d34ab3f08e595c622659d77f5aa423eb5f2f11ebe0213aefa9af75cd9dc
|
|
7
|
+
data.tar.gz: 3846bc856b99e9be1f416e6616e33e4f0fa9388ddd7a253c714bc56198218543a68e17d5872a3aa1dbe92993691adf4257068f4f6e512aede4015d6c6296bdf1
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ViewComponent::ScopedStyles
|
|
1
|
+
# ViewComponent::ScopedStyles [](https://badge.fury.io/rb/view_component-scoped_styles)
|
|
2
2
|
|
|
3
3
|
Scoped, colocated CSS for [ViewComponent](https://viewcomponent.org/).
|
|
4
4
|
|
|
@@ -8,13 +8,14 @@ E.g. `.button` becomes `.c-a1b2c3d4`
|
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
10
10
|
|
|
11
|
-
- [ViewComponent::ScopedStyles](#viewcomponentscopedstyles)
|
|
11
|
+
- [ViewComponent::ScopedStyles ](#viewcomponentscopedstyles-)
|
|
12
12
|
- [Table of Contents](#table-of-contents)
|
|
13
13
|
- [Installation](#installation)
|
|
14
14
|
- [Usage](#usage)
|
|
15
15
|
- [1. Using a sidecar stylesheet](#1-using-a-sidecar-stylesheet)
|
|
16
16
|
- [2. Using a styles block in the component](#2-using-a-styles-block-in-the-component)
|
|
17
17
|
- [Referencing classes](#referencing-classes)
|
|
18
|
+
- [Ignoring classes](#ignoring-classes)
|
|
18
19
|
- [Using the scoped CSS](#using-the-scoped-css)
|
|
19
20
|
- [Configuration](#configuration)
|
|
20
21
|
- [Related projects](#related-projects)
|
|
@@ -160,6 +161,49 @@ end
|
|
|
160
161
|
</div>
|
|
161
162
|
```
|
|
162
163
|
|
|
164
|
+
Scoped class names are prefixed by default (e.g. `c-a1b2c3d4`). Set a global prefix in configuration, or override per component with `css_class_prefix`:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
class ExampleComponent < ViewComponent::Base
|
|
168
|
+
include ViewComponent::ScopedStyles
|
|
169
|
+
|
|
170
|
+
css_class_prefix "vc-"
|
|
171
|
+
|
|
172
|
+
styles do
|
|
173
|
+
<<~CSS
|
|
174
|
+
.component { ... } # becomes .vc-a1b2c3d4 in components.scoped.css
|
|
175
|
+
CSS
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Ignoring classes
|
|
181
|
+
|
|
182
|
+
Ignored classes are left unchanged in generated CSS:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
class ExampleComponent < ViewComponent::Base
|
|
186
|
+
include ViewComponent::ScopedStyles
|
|
187
|
+
|
|
188
|
+
ignored_css_classes "is-open", "active"
|
|
189
|
+
|
|
190
|
+
styles do
|
|
191
|
+
<<~CSS
|
|
192
|
+
.component { ... }
|
|
193
|
+
.is-open { ... } # stays .is-open in components.scoped.css
|
|
194
|
+
CSS
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
In your view, you can either reference the class directly:
|
|
199
|
+
```erb
|
|
200
|
+
<div class="<%= component_class %> is-open">
|
|
201
|
+
```
|
|
202
|
+
or via the `component_class` helper:
|
|
203
|
+
```erb
|
|
204
|
+
<div class="<%= component_class %> <%= component_class("is-open") %>">
|
|
205
|
+
```
|
|
206
|
+
|
|
163
207
|
### Using the scoped CSS
|
|
164
208
|
|
|
165
209
|
All scoped CSS will be compiled into `app/assets/stylesheets/components.scoped.css`.
|
|
@@ -191,6 +235,9 @@ ViewComponent::ScopedStyles.configure do |config|
|
|
|
191
235
|
|
|
192
236
|
# Optional @layer name for components.scoped.css (e.g. "components"). Default: nil.
|
|
193
237
|
config.components_layer = nil
|
|
238
|
+
|
|
239
|
+
# Prefix for scoped class names (e.g. "c-" produces "c-a1b2c3d4"). Default: "c-"
|
|
240
|
+
config.css_class_prefix = "c-"
|
|
194
241
|
end
|
|
195
242
|
```
|
|
196
243
|
|
|
@@ -198,6 +245,7 @@ end
|
|
|
198
245
|
| --- | --- | --- |
|
|
199
246
|
| `components_path` | `"app/components"` | Where ViewComponent classes live, relative to `Rails.root`. |
|
|
200
247
|
| `components_layer` | `nil` | When set, wraps generated CSS in `@layer <name> { ... }` for cascade control. |
|
|
248
|
+
| `css_class_prefix` | `"c-"` | Prefix prepended to scoped class names (e.g. `"vc-"` → `"vc-a1b2c3d4"`). |
|
|
201
249
|
|
|
202
250
|
## Related projects
|
|
203
251
|
|
data/lib/generators/view_component/scoped_styles/templates/view_component_scoped_styles.rb.tt
CHANGED
|
@@ -6,4 +6,7 @@ ViewComponent::ScopedStyles.configure do |config|
|
|
|
6
6
|
|
|
7
7
|
# Optional @layer name for components.scoped.css (e.g. "components"). Default: nil.
|
|
8
8
|
config.components_layer = <%= components_layer_value %>
|
|
9
|
+
|
|
10
|
+
# Prefix for scoped class names (e.g. "c-" produces "c-a1b2c3d4"). Default: "c-"
|
|
11
|
+
config.css_class_prefix = <%= css_class_prefix_value %>
|
|
9
12
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
3
5
|
module ViewComponent
|
|
4
6
|
module ScopedStyles
|
|
5
7
|
extend ActiveSupport::Concern
|
|
@@ -9,11 +11,12 @@ module ViewComponent
|
|
|
9
11
|
|
|
10
12
|
# Default root class for +component_class+ when it matches a selector in the CSS.
|
|
11
13
|
COMPONENT_CSS_CLASS = "component".freeze
|
|
14
|
+
IGNORED_CSS_CLASSES = [].freeze
|
|
12
15
|
|
|
13
16
|
class_methods do
|
|
14
17
|
# Sets which CSS class is the root for +component_class+ (no argument).
|
|
15
|
-
# Also triggers style registration when Rails is loaded
|
|
16
|
-
# runs via the Railtie for all styled components).
|
|
18
|
+
# Also triggers style registration in development when Rails is loaded
|
|
19
|
+
# (registration also runs via the Railtie for all styled components).
|
|
17
20
|
#
|
|
18
21
|
# All class selectors are rewritten to scoped names in +components.scoped.css+.
|
|
19
22
|
# The primary class uses an id from the full stylesheet (e.g. +.icon+ → +.c-99d08d5a+);
|
|
@@ -26,7 +29,39 @@ module ViewComponent
|
|
|
26
29
|
# @param name [String, nil] selector name without a leading dot (e.g. +"icon"+)
|
|
27
30
|
def component_css_class(name = nil)
|
|
28
31
|
if name
|
|
29
|
-
const_set(:COMPONENT_CSS_CLASS, name)
|
|
32
|
+
const_set(:COMPONENT_CSS_CLASS, name.to_s)
|
|
33
|
+
clear_component_style_cache
|
|
34
|
+
end
|
|
35
|
+
register_styles_if_rails_loaded
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Declares CSS class selectors that are not rewritten to scoped names.
|
|
39
|
+
#
|
|
40
|
+
# Ignored classes stay in +components.scoped.css+ as written (e.g. +.is-open+).
|
|
41
|
+
# +component_class("is-open")+ returns the original name.
|
|
42
|
+
#
|
|
43
|
+
# Clears cached generated styles when the list changes.
|
|
44
|
+
#
|
|
45
|
+
# @param classes [String, Symbol] selector names without a leading dot
|
|
46
|
+
def ignored_css_classes(*classes)
|
|
47
|
+
if classes.any?
|
|
48
|
+
names = classes.flatten.map { |css_class| css_class.to_s.delete_prefix(".") }
|
|
49
|
+
const_set(:IGNORED_CSS_CLASSES, names.freeze)
|
|
50
|
+
clear_component_style_cache
|
|
51
|
+
end
|
|
52
|
+
register_styles_if_rails_loaded
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Sets the prefix for scoped class names on this component (e.g. +"vc-"+ → +"vc-a1b2c3d4"+).
|
|
56
|
+
#
|
|
57
|
+
# Overrides {ViewComponent::ScopedStyles.configuration}.css_class_prefix for this component.
|
|
58
|
+
#
|
|
59
|
+
# Clears cached generated styles when +prefix+ changes.
|
|
60
|
+
#
|
|
61
|
+
# @param prefix [String, nil] prefix without a hash suffix (e.g. +"c-"+, +"vc-"+)
|
|
62
|
+
def css_class_prefix(prefix = nil)
|
|
63
|
+
if prefix
|
|
64
|
+
const_set(:CSS_CLASS_PREFIX, prefix.to_s)
|
|
30
65
|
clear_component_style_cache
|
|
31
66
|
end
|
|
32
67
|
register_styles_if_rails_loaded
|
|
@@ -40,8 +75,10 @@ module ViewComponent
|
|
|
40
75
|
generate_component_styles
|
|
41
76
|
end
|
|
42
77
|
|
|
43
|
-
# Writes this component's processed styles to
|
|
78
|
+
# Writes this component's processed styles to +components.scoped.css+.
|
|
79
|
+
# Only runs in development; production and test use the committed stylesheet.
|
|
44
80
|
def register_styles
|
|
81
|
+
return unless register_styles_to_stylesheet?
|
|
45
82
|
return unless @styles_block || has_stylesheet?
|
|
46
83
|
|
|
47
84
|
Stylist.register(self)
|
|
@@ -66,18 +103,22 @@ module ViewComponent
|
|
|
66
103
|
end
|
|
67
104
|
|
|
68
105
|
def register_styles_if_rails_loaded
|
|
69
|
-
return unless
|
|
106
|
+
return unless register_styles_to_stylesheet?
|
|
70
107
|
return unless defined?(Rails::Server) # only web server boot path
|
|
71
108
|
|
|
72
109
|
register_styles
|
|
73
110
|
end
|
|
74
111
|
|
|
112
|
+
def register_styles_to_stylesheet?
|
|
113
|
+
defined?(Rails::Application) && Rails.application && Rails.env.development?
|
|
114
|
+
end
|
|
115
|
+
|
|
75
116
|
def generate_component_styles
|
|
76
117
|
styles_content = generate_styles_content
|
|
77
118
|
css_classes = extract_css_classes(styles_content)
|
|
78
119
|
primary_class = primary_css_class(css_classes)
|
|
79
120
|
|
|
80
|
-
@component_id =
|
|
121
|
+
@component_id = component_id_for(primary_class, styles_content)
|
|
81
122
|
@component_class_map = build_component_class_map(styles_content, css_classes, primary_class)
|
|
82
123
|
@component_styles = replace_css_classes(styles_content, @component_class_map)
|
|
83
124
|
end
|
|
@@ -97,30 +138,52 @@ module ViewComponent
|
|
|
97
138
|
|
|
98
139
|
def build_component_class_map(styles_content, css_classes, primary_class)
|
|
99
140
|
css_classes.index_with do |css_class|
|
|
100
|
-
if css_class
|
|
101
|
-
|
|
141
|
+
if ignored_css_class?(css_class)
|
|
142
|
+
css_class
|
|
102
143
|
else
|
|
103
|
-
generate_scoped_class_id(styles_content, css_class)
|
|
144
|
+
generate_scoped_class_id(styles_content, css_class, primary_class)
|
|
104
145
|
end
|
|
105
146
|
end
|
|
106
147
|
end
|
|
107
148
|
|
|
108
149
|
def replace_css_classes(styles_content, class_map)
|
|
109
|
-
class_map.
|
|
150
|
+
scoped_map = class_map.reject do |css_class, scoped|
|
|
151
|
+
css_class == scoped
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
sorted_classes = scoped_map.keys.sort_by(&:length).reverse
|
|
155
|
+
|
|
156
|
+
sorted_classes.reduce(styles_content) do |content, css_class|
|
|
110
157
|
content.gsub(/\.#{Regexp.escape(css_class)}\b/, ".#{class_map[css_class]}")
|
|
111
158
|
end
|
|
112
159
|
end
|
|
113
160
|
|
|
114
|
-
def
|
|
115
|
-
|
|
161
|
+
def component_id_for(primary_class, styles_content)
|
|
162
|
+
if ignored_css_class?(primary_class)
|
|
163
|
+
primary_class
|
|
164
|
+
else
|
|
165
|
+
generate_scoped_class_id(styles_content, primary_class, primary_class)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
116
168
|
|
|
117
|
-
|
|
169
|
+
def ignored_css_class?(css_class)
|
|
170
|
+
self::IGNORED_CSS_CLASSES.include?(css_class)
|
|
118
171
|
end
|
|
119
172
|
|
|
120
|
-
def generate_scoped_class_id(styles_content, css_class)
|
|
121
|
-
|
|
173
|
+
def generate_scoped_class_id(styles_content, css_class, primary_class)
|
|
174
|
+
is_primary = css_class == primary_class
|
|
175
|
+
input = is_primary ? styles_content : "#{styles_content}:#{css_class}"
|
|
176
|
+
hash = ::Digest::MD5.hexdigest(input)[0..7]
|
|
122
177
|
|
|
123
|
-
"
|
|
178
|
+
"#{scoped_css_class_prefix}#{hash}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def scoped_css_class_prefix
|
|
182
|
+
if const_defined?(:CSS_CLASS_PREFIX, false)
|
|
183
|
+
self::CSS_CLASS_PREFIX
|
|
184
|
+
else
|
|
185
|
+
ViewComponent::ScopedStyles.configuration.css_class_prefix
|
|
186
|
+
end
|
|
124
187
|
end
|
|
125
188
|
|
|
126
189
|
def clear_component_style_cache
|
|
@@ -25,9 +25,15 @@ module ViewComponent
|
|
|
25
25
|
# @return [String, nil] default: +nil+ (no layer wrapper)
|
|
26
26
|
attr_accessor :components_layer
|
|
27
27
|
|
|
28
|
+
# Prefix prepended to scoped class names (e.g. +"c-"+ → +"c-a1b2c3d4"+).
|
|
29
|
+
#
|
|
30
|
+
# @return [String] default: +"c-"+
|
|
31
|
+
attr_accessor :css_class_prefix
|
|
32
|
+
|
|
28
33
|
def initialize
|
|
29
34
|
@components_path = File.join("app", "components")
|
|
30
35
|
@components_layer = nil
|
|
36
|
+
@css_class_prefix = "c-"
|
|
31
37
|
end
|
|
32
38
|
end
|
|
33
39
|
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/core_ext/module/delegation"
|
|
3
4
|
require "rails/railtie"
|
|
4
5
|
|
|
5
6
|
module ViewComponent
|
|
6
7
|
module ScopedStyles
|
|
7
8
|
class Railtie < Rails::Railtie
|
|
8
9
|
config.after_initialize do
|
|
10
|
+
next unless Rails.env.development?
|
|
11
|
+
|
|
9
12
|
ViewComponent::ScopedStyles::Railtie.load_and_register_components
|
|
10
13
|
end
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
initializer "view_component.scoped_styles.reload" do |app|
|
|
16
|
+
next unless Rails.env.development?
|
|
17
|
+
|
|
18
|
+
app.config.to_prepare do
|
|
14
19
|
ViewComponent::ScopedStyles::Railtie.register_components
|
|
15
20
|
end
|
|
16
21
|
end
|