view_component-scoped_styles 0.1.0 → 0.2.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: 65712096bb4c02068da2dca36b66bad2d4dffc773234d42910dcadc5ef354a21
4
+ data.tar.gz: 12d6dc8afdc1b530a749810a3fb32b13545c6a3421dd8e21c7c8f85c76b1539d
5
5
  SHA512:
6
- metadata.gz: e40669a1884ae189bc18edcf04cad923e210875078095d6fd7238b3dd9ff061172253f8c6dd86632ee207cd3e1ae108bf04d1dac1866daedd948c078e0a95e50
7
- data.tar.gz: 6f67241201f8e8452e8eec7e316ff4a1259496347bc62f58dc6b2a7739557dc3df7f4e69ce75cc1041843dcd5d95d9a3cc40d590d7108c917f3850ae630e89e0
6
+ metadata.gz: 6c7b9ede8f02445070c51dbe34e935ff43e9cbc60c3594d270915454cb6e583b02748b21b1531209fdef17b99d5654815333997fb6d3bd9fe5f46336957899ba
7
+ data.tar.gz: 239fe37c5445b1aac40e589da187643a37c66d9b8cdf0f9ffbaae51049d8778bea1a86cab5fa38307a3022070140df2e39d393b76f266a97434cb2ecfbe6b106
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,33 @@ end
160
161
  </div>
161
162
  ```
162
163
 
164
+ ### Ignoring classes
165
+
166
+ Ignored classes are left unchanged in generated CSS:
167
+
168
+ ```ruby
169
+ class ExampleComponent < ViewComponent::Base
170
+ include ViewComponent::ScopedStyles
171
+
172
+ ignored_css_classes "is-open", "active"
173
+
174
+ styles do
175
+ <<~CSS
176
+ .component { ... }
177
+ .is-open { ... } # stays .is-open in components.scoped.css
178
+ CSS
179
+ end
180
+ end
181
+ ```
182
+ In your view, you can either reference the class directly:
183
+ ```erb
184
+ <div class="<%= component_class %> is-open">
185
+ ```
186
+ or via the `component_class` helper:
187
+ ```erb
188
+ <div class="<%= component_class %> <%= component_class("is-open") %>">
189
+ ```
190
+
163
191
  ### Using the scoped CSS
164
192
 
165
193
  All scoped CSS will be compiled into `app/assets/stylesheets/components.scoped.css`.
@@ -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,24 @@ 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)
30
50
  clear_component_style_cache
31
51
  end
32
52
  register_styles_if_rails_loaded
@@ -40,8 +60,10 @@ module ViewComponent
40
60
  generate_component_styles
41
61
  end
42
62
 
43
- # Writes this component's processed styles to the bundled or host stylesheet.
63
+ # Writes this component's processed styles to +components.scoped.css+.
64
+ # Only runs in development; production and test use the committed stylesheet.
44
65
  def register_styles
66
+ return unless register_styles_to_stylesheet?
45
67
  return unless @styles_block || has_stylesheet?
46
68
 
47
69
  Stylist.register(self)
@@ -66,18 +88,22 @@ module ViewComponent
66
88
  end
67
89
 
68
90
  def register_styles_if_rails_loaded
69
- return unless defined?(Rails) && Rails.root
91
+ return unless register_styles_to_stylesheet?
70
92
  return unless defined?(Rails::Server) # only web server boot path
71
93
 
72
94
  register_styles
73
95
  end
74
96
 
97
+ def register_styles_to_stylesheet?
98
+ defined?(Rails::Application) && Rails.application && Rails.env.development?
99
+ end
100
+
75
101
  def generate_component_styles
76
102
  styles_content = generate_styles_content
77
103
  css_classes = extract_css_classes(styles_content)
78
104
  primary_class = primary_css_class(css_classes)
79
105
 
80
- @component_id = generate_component_id(styles_content)
106
+ @component_id = component_id_for(primary_class, styles_content)
81
107
  @component_class_map = build_component_class_map(styles_content, css_classes, primary_class)
82
108
  @component_styles = replace_css_classes(styles_content, @component_class_map)
83
109
  end
@@ -97,28 +123,42 @@ module ViewComponent
97
123
 
98
124
  def build_component_class_map(styles_content, css_classes, primary_class)
99
125
  css_classes.index_with do |css_class|
100
- if css_class == primary_class
101
- @component_id
126
+ if ignored_css_class?(css_class)
127
+ css_class
102
128
  else
103
- generate_scoped_class_id(styles_content, css_class)
129
+ generate_scoped_class_id(styles_content, css_class, primary_class)
104
130
  end
105
131
  end
106
132
  end
107
133
 
108
134
  def replace_css_classes(styles_content, class_map)
109
- class_map.keys.sort_by(&:length).reverse.reduce(styles_content) do |content, css_class|
135
+ scoped_map = class_map.reject do |css_class, scoped|
136
+ css_class == scoped
137
+ end
138
+
139
+ sorted_classes = scoped_map.keys.sort_by(&:length).reverse
140
+
141
+ sorted_classes.reduce(styles_content) do |content, css_class|
110
142
  content.gsub(/\.#{Regexp.escape(css_class)}\b/, ".#{class_map[css_class]}")
111
143
  end
112
144
  end
113
145
 
114
- def generate_component_id(styles_content)
115
- hash = Digest::MD5.hexdigest(styles_content)[0..7]
146
+ def component_id_for(primary_class, styles_content)
147
+ if ignored_css_class?(primary_class)
148
+ primary_class
149
+ else
150
+ generate_scoped_class_id(styles_content, primary_class, primary_class)
151
+ end
152
+ end
116
153
 
117
- "c-#{hash}"
154
+ def ignored_css_class?(css_class)
155
+ self::IGNORED_CSS_CLASSES.include?(css_class)
118
156
  end
119
157
 
120
- def generate_scoped_class_id(styles_content, css_class)
121
- hash = Digest::MD5.hexdigest("#{styles_content}:#{css_class}")[0..7]
158
+ def generate_scoped_class_id(styles_content, css_class, primary_class)
159
+ is_primary = css_class == primary_class
160
+ input = is_primary ? styles_content : "#{styles_content}:#{css_class}"
161
+ hash = ::Digest::MD5.hexdigest(input)[0..7]
122
162
 
123
163
  "c-#{hash}"
124
164
  end
@@ -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.2.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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Edwards