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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43108a25040819bdeee257cd43228632e8de7822b24e96d675625208ff5f1544
4
- data.tar.gz: 1023d48e94469ea2db73b971eb0c09440bdfb18167aea8abb461cb45e5bc6ed5
3
+ metadata.gz: 4518c4426e35b342ba212fb90ab4d5e4692d7427fdf5fdbe5ced923a71cc1e42
4
+ data.tar.gz: 601433c85c4aad6602831f8c53e662f73080d8bb40f9104f28fc8842411e0569
5
5
  SHA512:
6
- metadata.gz: e40669a1884ae189bc18edcf04cad923e210875078095d6fd7238b3dd9ff061172253f8c6dd86632ee207cd3e1ae108bf04d1dac1866daedd948c078e0a95e50
7
- data.tar.gz: 6f67241201f8e8452e8eec7e316ff4a1259496347bc62f58dc6b2a7739557dc3df7f4e69ce75cc1041843dcd5d95d9a3cc40d590d7108c917f3850ae630e89e0
6
+ metadata.gz: 8e9a1ab80bf24d000347be17e22d19e684a1ec1f70a08cf95952618c20891b84b8c50d34ab3f08e595c622659d77f5aa423eb5f2f11ebe0213aefa9af75cd9dc
7
+ data.tar.gz: 3846bc856b99e9be1f416e6616e33e4f0fa9388ddd7a253c714bc56198218543a68e17d5872a3aa1dbe92993691adf4257068f4f6e512aede4015d6c6296bdf1
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ViewComponent::ScopedStyles
1
+ # ViewComponent::ScopedStyles [![Gem Version](https://badge.fury.io/rb/view_component-scoped_styles.svg)](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
 
@@ -34,6 +34,10 @@ module ViewComponent
34
34
  def components_layer_value
35
35
  configuration_defaults.components_layer.inspect
36
36
  end
37
+
38
+ def css_class_prefix_value
39
+ configuration_defaults.css_class_prefix.inspect
40
+ end
37
41
  end
38
42
  end
39
43
  end
@@ -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 (registration also
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 the bundled or host stylesheet.
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 defined?(Rails) && Rails.root
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 = generate_component_id(styles_content)
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 == primary_class
101
- @component_id
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.keys.sort_by(&:length).reverse.reduce(styles_content) do |content, css_class|
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 generate_component_id(styles_content)
115
- hash = Digest::MD5.hexdigest(styles_content)[0..7]
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
- "c-#{hash}"
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
- hash = Digest::MD5.hexdigest("#{styles_content}:#{css_class}")[0..7]
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
- "c-#{hash}"
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
- if Rails.env.development?
13
- config.to_prepare do
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ViewComponent
4
4
  module ScopedStyles
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component-scoped_styles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Edwards