vident 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6907ba81655f5cf18c500edcdfa43c8786d8b2463573a1dadd0dd51ba1637ea3
4
- data.tar.gz: 72964008e3a0c64172726f4edfe2ba030f66018a08713e8bad529eae4dda45d7
3
+ metadata.gz: 9144ee8a1b3361914b568d5dfbdcc22df506818813573e91ede2302e3a6775aa
4
+ data.tar.gz: 6476408b4f5acf6e4dd40d96539df7fb5588bb225200d3fbd05d35310a7c1c2d
5
5
  SHA512:
6
- metadata.gz: dea9a3d4e70478870b829fe050cfbbcd89dad15866a4a4334d8661d217f56e30a47b81285f279008bfaf762a406e7356ca33d56c607e98a34ddde6b2bf26df13
7
- data.tar.gz: ea963a8ba620e0c510c8e4fadf02eb0293159398c576dbd9554bffc01fd7bb930e7b9d31e2011710ab7a4c41a2929a2e8964cceee2bdfe35827a8dedcad148b4
6
+ metadata.gz: 3098fa53c35329dc0ab5bbae9c4d2ca368ce8d9486fa5cfad29fddf8b006bed162dd8eb89cc1b76d42be91daecb0f0b41f979c866734f5a05f99580936444728
7
+ data.tar.gz: 47dff0e200721e7b48498e13cb27cf58fd7b0da9be6b428be201bb4f73593ffc233a459dc2e0b16f2c925097c65956a274a41c3707c1bfb45be1eccc7b2edbc7
data/README.md CHANGED
@@ -11,7 +11,7 @@ Vident also provides a neat Ruby DSL to make wiring up **Stimulus easier & less
11
11
  # Motivation
12
12
 
13
13
  I love working with Stimulus, but I find manually crafting the data attributes for
14
- targets and actions error prone and tedious. Vident aims to make this process easier
14
+ targets and actions error-prone and tedious. Vident aims to make this process easier
15
15
  and keep me thinking in Ruby.
16
16
 
17
17
  I have been using Vident with `ViewComponent` in production apps for a while now (and recently `Phlex`!)
@@ -19,33 +19,64 @@ and it has been constantly evolving.
19
19
 
20
20
  This gem is a work in progress and I would love to get your feedback and contributions!
21
21
 
22
+ # Vident is a collection of gems
22
23
 
24
+ The core gems are:
23
25
 
24
- **The docs below need updating**
26
+ - [`vident`](https://github.com/stevegeek/vident) to get the base functionality
27
+ - [`vident-typed`](https://github.com/stevegeek/vident-typed) to optionally define typed attributes for your view components
25
28
 
29
+ Gems that provide support for `ViewComponent` and `Phlex`:
30
+
31
+ - [`vident-view_component`](https://github.com/stevegeek/vident-view_component) for using with `ViewComponent` and untyped attributes
32
+ - [`vident-typed-view_component`](https://github.com/stevegeek/vident-typed-view_component) for using with `ViewComponent` and typed attributes
33
+ - [`vident-phlex`](https://github.com/stevegeek/vident-phlex) for using with `Phlex` and untyped attributes
34
+ - [`vident-typed-phlex`](https://github.com/stevegeek/vident-typed-phlex) for using with `Phlex` and typed attributes
35
+
36
+ There is also:
37
+
38
+ - [`vident-typed-minitest`](https://github.com/stevegeek/vident-typed-minitest) to get some test helpers for typed attributes (auto generates inputs to test attributes)
39
+ - [`vident-better_html`](https://github.com/stevegeek/vident-better_html) to support `better_html` if you use it in your Rails app
40
+ - [`vident-tailwind`](https://github.com/stevegeek/vident-tailwind) to get all the benefits of the amazing [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge/).
26
41
 
27
42
 
43
+ # Things still to do...
44
+
45
+ This is a work in progress. Here's what's left to do for first release:
46
+
47
+ - Iterate on the interfaces and functionality
48
+ - Add tests
49
+ - Make the gem more configurable to fit more use cases
50
+ - Create an example library of a few components for some design system
51
+ - Create a demo app with `lookbook` and those components
52
+ - Add more documentation
53
+
54
+ # About Vident
55
+
28
56
  ## What does Vident provide?
29
57
 
30
- - `Vident::Component`: A mixin for your `ViewComponent` components or `Phlex` components that provides the a helper to create the
31
- root element component (in templated or template-less components).
58
+ - Base classes for your `ViewComponent` components or `Phlex` components that provides a helper to create the
59
+ all important 'root' element component (can be used with templated or template-less components).
60
+
61
+ - implementations of these root components for creating the 'root' element in your view components. Similar to `Primer::BaseComponent` but
62
+ exposes a simple API for configuring and adding Stimulus controllers, targets and actions. The root component also handles deduplication
63
+ of classes, creating a unique ID, setting the element tag type, handling possible overrides set at the render site, and determining stimulus controller identifiers etc
32
64
 
33
- - `Vident::TypedComponent`: like `Vident::Component` but uses `dry-types` to define typed attributes for your components.
65
+ - a way to define attributes for your components, either typed or untyped, with default values and optional validation.
34
66
 
35
67
  ### Various utilities
36
68
 
37
- - `Vident::Tailwind`: a mixin for your vident component which uses [tailwind_merge](https://github.com/gjtorikian/tailwind_merge) to merge TailwindCSS classes
38
- so you can easily override classes when rendering a component.
69
+ Such as...
39
70
 
40
- - `Vident::Caching::CacheKey`: a mixin for your vident component which provides a `cache_key` method that can be used to generate a cache key for
71
+ - for Taiwind users, a mixin for your vident component which uses [tailwind_merge](https://github.com/gjtorikian/tailwind_merge) to merge TailwindCSS classes
72
+ so you can easily override classes when rendering a component.
73
+ - a mixin for your Vident Components which provides a `#cache_key` method that can be used to generate a cache key for
41
74
  fragment caching or etag generation.
75
+ - a test helper for your typed Vident ViewComponents which can be used to generate good and bad attribute/params/inputs
42
76
 
43
- - `Vident::RootComponent::*` which are components for creating the 'root' element in your view components. Similar to `Primer::BaseComponent` but
44
- exposes a simple API for configuring and adding Stimulus controllers, targets and actions. Normally you create these
45
- using the `root` helper method on `Vident::Component`/`Vident::TypedComponent`.
46
-
47
- # Features
77
+ ## All the Features...
48
78
 
79
+ - use Vident with `ViewComponent` or `Phlex` or your own view component system
49
80
  - A helper to create the root HTML element for your component, which then handles creation of attributes.
50
81
  - Component arguments are defined using the `attribute` method which allows you to define default values, (optionally) types and
51
82
  if blank or nil values should be allowed.
@@ -57,474 +88,107 @@ This gem is a work in progress and I would love to get your feedback and contrib
57
88
  having to update the data attributes in your views.
58
89
  - Components are rendered with useful class names and IDs to make debugging easier (autogenerated IDs are 'random' but deterministic so they
59
90
  are the same each time a given view is rendered to avoid content changing/Etag changing).
60
- - (experimental) Support for fragment caching of components (only with ViewComponent and with caveats)
91
+ - (experimental) Support for fragment caching of components (`Vident::Caching` and `Vident::<ViewComponent | Phlex>::Caching`... implementation has caveats)
61
92
  - (experimental) A test helper to make testing components easier by utilising type information from the component arguments to render
62
93
  automatically configured good and bad examples of the component.
63
94
  - (experimental) support for `better_html`
64
95
 
65
96
 
66
- ## Things still to do...
67
-
68
- This is a work in progress. Here's what's left to do for first release:
69
-
70
- - Iterate on the interfaces and functionality
71
- - Add tests
72
- - Make the gem more configurable to fit more use cases
73
- - Create an example library of a few components for some design system
74
- - Create a demo app with `lookbook` and those components
75
- - Add more documentation
76
- - split `vident` into `vident` + `vident-rails` gems (and maybe `vident-rspec`) (Phlex can be used outside of Rails)
77
- - possibly also split into `vident-phlex` and `vident-view_component` gems ?
78
-
79
-
80
-
81
- # Examples
82
-
83
- Before we dive into a specific example note that there are some components implemented with
84
- both ViewComponent and Phlex (with and without Vident) in the `test/dummy`.
85
- - https://github.com/stevegeek/vident/tree/main/test/dummy/app/components
86
- - https://github.com/stevegeek/vident/tree/main/test/dummy/app/views
87
-
88
- Start Rails:
89
-
90
- ```bash
91
- cd test/dummy
92
- bundle install
93
- rails assets:precompile
94
- rails s
95
- ```
96
-
97
- and visit http://localhost:3000
98
-
99
-
100
- ## A Vident component example (without Stimulus)
101
-
102
- First is an example component that uses `Vident::TypedComponent` but no Stimulus features.
103
-
104
- It is an avatar component that can either be displayed as an image or as initials.
105
-
106
- It supports numerous sizes and shapes and can optionally have a border. It also generates a cache key for use in fragment caching or etag generation.
107
-
108
- ```ruby
109
- class AvatarComponent < ViewComponent::Base
110
- include ::Vident::TypedComponent
111
- include ::Vident::Tailwind
112
- include ::Vident::Caching::CacheKey
113
-
114
- no_stimulus_controller
115
- with_cache_key :attributes
116
-
117
- attribute :url, String, allow_nil: true, allow_blank: false
118
- attribute :initials, String, allow_blank: false
119
-
120
- attribute :shape, Symbol, in: %i[circle square], default: :circle
121
-
122
- attribute :border, :boolean, default: false
123
-
124
- attribute :size, Symbol, in: %i[tiny small normal medium large x_large xx_large], default: :normal
125
-
126
- private
127
-
128
- def html_options
129
- if image_avatar?
130
- { class: "inline-block object-contain", src: url, alt: t(".image") }
131
- else
132
- { class: "inline-flex items-center justify-center bg-gray-500" }
133
- end
134
- end
135
-
136
- def element_classes
137
- [size_classes, shape_class, border? ? "border" : ""]
138
- end
139
-
140
- alias_method :image_avatar?, :url?
141
-
142
- def shape_class
143
- (shape == :circle) ? "rounded-full" : "rounded-md"
144
- end
145
-
146
- def size_classes
147
- case size
148
- when :tiny
149
- "w-6 h-6"
150
- when :small
151
- "w-8 h-8"
152
- when :medium
153
- "w-12 h-12"
154
- when :large
155
- "w-14 h-14"
156
- when :x_large
157
- "sm:w-24 sm:h-24 w-16 h-16"
158
- when :xx_large
159
- "sm:w-32 sm:h-32 w-24 h-24"
160
- else
161
- "w-10 h-10"
162
- end
163
- end
164
-
165
- def text_size_class
166
- case size
167
- when :tiny
168
- "text-xs"
169
- when :small
170
- "text-xs"
171
- when :medium
172
- "text-lg"
173
- when :large
174
- "sm:text-xl text-lg"
175
- when :extra_large
176
- "sm:text-2xl text-xl"
177
- else
178
- "text-medium"
179
- end
180
- end
181
- end
182
- ```
183
-
184
- ```erb
185
- <%= render root(
186
- element_tag: image_avatar? ? :img : :div,
187
- html_options: html_options
188
- ) do %>
189
- <% unless image_avatar? %>
190
- <span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
191
- <% end %>
192
- <% end %>
193
-
194
- ```
195
-
196
- Example usages:
197
-
198
- ```erb
199
- <!-- These will render -->
200
- <%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", initials: "AB" size: :large) %>
201
- <%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", html_options: {alt: "My alt text", class: "object-scale-down"}) %>
202
- <%= render AvatarComponent.new(initials: "SG", size: :small) %>
203
- <%= render AvatarComponent.new(initials: "SG", size: :large, html_options: {class: "border-2 border-red-600"}) %>
204
-
205
- <!-- These will raise an error -->
206
- <!-- missing initals -->
207
- <%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", size: :large) %>
208
- <!-- initials blank -->
209
- <%= render AvatarComponent.new(initials: "", size: :large) %>
210
- <!-- invalid size -->
211
- <%= render AvatarComponent.new(initials: "SG", size: :foo_bar) %>
212
- ```
213
-
97
+ ## Installation
214
98
 
215
- The following is rendered when used `render AvatarComponent.new(initials: "SG", size: :small, border: true)`:
99
+ This gem (`vident`) provides only base functionality but there are a number of gems that provide additional functionality
100
+ or an "out of the box" experience.
216
101
 
217
- ```html
218
- <div class="avatar-component w-8 h-8 rounded-full border inline-flex items-center justify-center bg-gray-500" id="avatar-component-9790427-12">
219
- <span class="text-xs font-medium leading-none text-white">SG</span>
220
- </div>
221
- ```
102
+ It's a "pick your own adventure" approach. You decide what frameworks and features you want to use
103
+ and add the gems as needed.
222
104
 
223
- The following is rendered when used `render AvatarComponent.new(url: "https://i.pravatar.cc/300", initials: "AB", html_options: {alt: "My alt text", class: "block"})`:
105
+ First, add this line to your application's Gemfile:
224
106
 
225
- ```html
226
- <img src="https://i.pravatar.cc/300" alt="My alt text" class="avatar-component w-10 h-10 rounded-full object-contain block" id="avatar-component-7083941-11">
107
+ ```ruby
108
+ gem 'vident'
227
109
  ```
228
110
 
229
- ----
111
+ Then go on to choose the gems you want to use:
230
112
 
231
- ![Example](examples/avatar.png)
113
+ #### Q1. Do you want to use [`ViewComponent`](https://viewcomponent.org/) or [`Phlex`](https://www.phlex.fun/) for your view components?
232
114
 
233
- ## Another ViewComponent + Vident example with Stimulus
115
+ For ViewComponent use:
234
116
 
235
- Consider the following ERB that might be part of an application's views. The app uses `ViewComponent`, `Stimulus` and `Vident`.
117
+ - [`vident-view_component`](https://github.com/stevegeek/vident-view_component)
236
118
 
237
- The Greeter is a component that displays a text input and a button. When the button is clicked, the text input's value is
238
- used to greet the user. At the same time the button changes to be a 'reset' button, which resets the greeting when clicked again.
119
+ For Phlex use:
239
120
 
240
- ![ex1.gif](examples%2Fex1.gif)
121
+ - [`vident-phlex`](https://github.com/stevegeek/vident-phlex)
241
122
 
242
- ```erb
243
- <%# app/views/home/index.html.erb %>
244
123
 
245
- <!-- ... -->
124
+ Note: you can also use both in the same app.
246
125
 
247
- <!-- render the Greeter ViewComponent (that uses Vident) -->
248
- <%= render ::GreeterComponent.new(cta: "Hey!", html_options: {class: "my-4"}) do |greeter| %>
249
- <%# this component has a slot called `trigger` that renders a `ButtonComponent` (which also uses Vident) %>
250
- <% greeter.trigger(
251
-
252
- # The button component has attributes that are typed
253
- before_clicked: "Greet",
254
- after_clicked: "Greeted! Reset?",
255
-
256
- # A stimulus action is added to the button that triggers the `greet` action on the greeter stimulus controller.
257
- # This action will be added to any defined on the button component itself
258
- actions: [
259
- greeter.action(:click, :greet),
260
- ],
261
-
262
- # We can also override the default button classes of our component, or set other HTML attributes
263
- html_options: {
264
- class: "bg-red-500 hover:bg-red-700"
265
- }
266
- ) %>
267
- <% end %>
268
-
269
- <!-- ... -->
270
- ```
126
+ For example, if you want to use ViewComponent and Phlex in the same app, you might end up with:
271
127
 
272
- The output HTML of the above, using Vident, is:
273
-
274
- ```html
275
- <div class="greeter-component py-2 my-4"
276
- data-controller="greeter-component"
277
- data-greeter-component-pre-click-class="text-md text-gray-500"
278
- data-greeter-component-post-click-class="text-xl text-blue-700"
279
- id="greeter-component-1599855-6">
280
- <input type="text"
281
- data-greeter-component-target="name"
282
- class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
283
- <button class="button-component ml-4 whitespace-no-wrap bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded bg-red-500 hover:bg-red-700"
284
- data-controller="button-component"
285
- data-action="click->greeter-component#greet button-component#changeMessage"
286
- data-button-component-after-clicked-message="Greeted! Reset?"
287
- data-button-component-before-clicked-message="Greet"
288
- id="button-component-7799479-7">Hey!</button>
289
- <!-- you can also use the `target_tag` helper to render targets -->
290
- <span class="ml-4 text-md text-gray-500"
291
- data-greeter-component-target="output">
292
- ...
293
- </span>
294
- </div>
128
+ ```ruby
129
+ gem 'vident'
130
+ gem 'vident-view_component'
131
+ gem 'vident-phlex'
295
132
  ```
296
133
 
297
- Let's look at the components in more detail.
134
+ #### Q2. Do you want to build components where the attributes have runtime type checking (powered by [`dry-types`](https://github.com/dry-rb/dry-types))?
298
135
 
299
- The main component is the `GreeterComponent`:
136
+ If yes, then add `vident-typed` to your Gemfile:
300
137
 
301
138
  ```ruby
302
- # app/components/greeter_component.rb
303
-
304
- class GreeterComponent < ViewComponent::Base
305
- include Vident::Component
306
-
307
- renders_one :trigger, ButtonComponent
308
- end
139
+ gem 'vident-typed'
309
140
  ```
310
141
 
311
- ```erb
312
- <%# app/components/greeter_component.html.erb %>
313
-
314
- <%# Rendering the `root` element creates a tag which has stimulus `data-*`s, a unique id & other attributes set. %>
315
- <%# The stimulus controller name (identifier) is derived from the component name, and then used to generate the relavent data attribute names. %>
316
-
317
- <%= render root named_classes: {
318
- pre_click: "text-md text-gray-500", # named classes are exposed to Stimulus as `data-<controller>-<name>-class` attributes
319
- post_click: "text-xl text-blue-700",
320
- html_options: {class: "py-2"}
321
- } do |greeter| %>
322
-
323
- <%# `greeter` is the root element and exposes methods to generate stimulus targets and actions %>
324
- <input type="text"
325
- <%= greeter.as_target(:name) %>
326
- class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
327
-
328
- <%# Render the slot %>
329
- <%= trigger %>
330
-
331
- <%# you can also use the `target_tag` helper to render targets %>
332
- <%= greeter.target_tag(
333
- :span,
334
- :output,
335
- # Stimulus named classes can be referenced to set class attributes at render time
336
- class: "ml-4 #{greeter.named_classes(:pre_click)}"
337
- ) do %>
338
- ...
339
- <% end %>
340
- <% end %>
142
+ and then use the relavent `*-typed-*` gems for your chosen view component system:
341
143
 
342
- ```
144
+ - use [`vident-typed-view_component`](https://github.com/stevegeek/vident-typed-view_component)
145
+ - and/or [`vident-typed-phlex`](https://github.com/stevegeek/vident-typed-phlex)
343
146
 
344
- ```js
345
- // app/components/greeter_component_controller.js
346
-
347
- import { Controller } from "@hotwired/stimulus"
348
-
349
- // This is a Stimulus controller that is automatically registered for the `GreeterComponent`
350
- // and is 'sidecar' to the component. You can see that while in the ERB we use Ruby naming conventions
351
- // with snake_case Symbols, here they are converted to camelCase names. We can also just use camelCase
352
- // in the ERB if we want.
353
- export default class extends Controller {
354
- static targets = [ "name", "output" ]
355
- static classes = [ "preClick", "postClick" ]
356
-
357
- greet() {
358
- this.clicked = !this.clicked;
359
- this.outputTarget.classList.toggle(this.preClickClasses, !this.clicked);
360
- this.outputTarget.classList.toggle(this.postClickClasses, this.clicked);
361
-
362
- if (this.clicked)
363
- this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
364
- else
365
- this.clear();
366
- }
367
-
368
- clear() {
369
- this.outputTarget.textContent = '...';
370
- this.nameTarget.value = '';
371
- }
372
- }
373
- ```
147
+ Note you must also include the gem for the view component system you are using.
374
148
 
375
- The slot renders a `ButtonComponent` component:
149
+ For example, for ViewComponent, you might end up with:
376
150
 
377
151
  ```ruby
378
- # app/components/button_component.rb
379
-
380
- class ButtonComponent < ViewComponent::Base
381
- # This component uses Vident::TypedComponent which uses dry-types to define typed attributes.
382
- include Vident::TypedComponent
383
-
384
- # The attributes can specify an expected type, a default value and if nil is allowed.
385
- attribute :after_clicked, String, default: "Greeted!"
386
- attribute :before_clicked, String, allow_nil: false
387
-
388
- # This example is a templateless ViewComponent.
389
- def call
390
- # The button is rendered as a <button> tag with an click action on its own controller.
391
- render root(
392
- element_tag: :button,
393
-
394
- # We can define actions as arrays of Symbols, or pass manually manually crafted strings.
395
- # Here we specify the action name only, implying an action on the current components controller
396
- # and the default event type of `click`.
397
- actions: [:change_message],
398
- # Alternatively: [:click, :change_message] or ["click", "changeMessage"] or even "click->button-component#changeMessage"
399
-
400
- # A couple of data values are also set which will be available to the controller
401
- data_maps: [{after_clicked_message: after_clicked, before_clicked_message: before_clicked}],
402
-
403
- # The <button> tag has a default styling set directly on it. Note that
404
- # if not using utility classes, you can style the component using its
405
- # canonical class name (which is equal to the component's stimulus identifier),
406
- # in this case `button-component`.
407
- html_options: {class: "ml-4 whitespace-no-wrap bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"}
408
- ) do
409
- @before_clicked
410
- end
411
- end
412
- end
152
+ gem 'vident'
153
+ gem 'vident-view_component'
154
+ gem 'vident-typed'
155
+ gem 'vident-typed-view_component'
413
156
  ```
414
157
 
415
- ```js
416
- // app/components/button_component_controller.js
417
-
418
- import { Controller } from "@hotwired/stimulus"
419
-
420
- export default class extends Controller {
421
- // The action is in camelCase.
422
- changeMessage() {
423
- this.clicked = !this.clicked;
424
- // The data attributes have their naming convention converted to camelCase.
425
- this.element.textContent = this.clicked ? this.data.get("afterClickedMessage") : this.data.get("beforeClickedMessage");
426
- }
427
- }
428
-
429
- ```
430
-
431
- ## Installation
432
-
433
- This gem (`vident`) provides only base functionality but there are a number of gems that provide additional functionality
434
- or a "out of the box" experience.
435
-
436
- It's a "pick your own adventure" approach. You decide what frameworks and features you want to use
437
- and add the gems as needed.
438
-
439
- Start by asking yorself these questions:
440
-
441
- _Q1. Do you want to use [`ViewComponent`](https://viewcomponent.org/) or [`Phlex`](https://www.phlex.fun/) for your view components?_
442
-
443
- _Q2. Do you want to use attributes with runtime type checking (powered by [`dry-types`](https://github.com/dry-rb/dry-types)), or not?_
444
-
445
- Depending on your answer to Q1, you can choose from either the `*-view_component` or `*-phlex` gems,
446
- then depending on your answer to Q2, you can choose from either the untyped or `*-typed-*` gems.
447
-
448
- Note you can also use some or all of them in the same app.
449
-
450
- - [`vident-view_component`](https://github.com/stevegeek/vident-view_component) for using with `ViewComponent` and untyped attributes
451
- - [`vident-typed-view_component`](https://github.com/stevegeek/vident-typed-view_component) for using with `ViewComponent` and typed attributes
452
- - [`vident-phlex`](https://github.com/stevegeek/vident-phlex) for using with `Phlex` and untyped attributes
453
- - [`vident-typed-phlex`](https://github.com/stevegeek/vident-typed-phlex) for using with `Phlex` and typed attributes
454
-
455
- _Q3. Do you use or want to use [BetterHTML](https://github.com/Shopify/better-html) in your Rails project?_
158
+ #### Q3. Do you use or want to use [BetterHTML](https://github.com/Shopify/better-html) in your Rails project?
456
159
 
457
160
  If yes, then include [`vident-better_html`](https://github.com/stevegeek/vident-better_html) in your Gemfile alongside `better_html` and your vident gems of choice.
458
- Note that `vident-better_html` automatically enables `better_html` support in Vident root components.
459
-
460
- _Q4. Do you use or want to use [TailwindCSS](https://tailwindcss.com/)?_
461
-
462
- If yes, then consider adding [`vident-tailwind`](https://github.com/stevegeek/vident-tailwind) to your Gemfile alongside your vident gems of choice. When creating
463
- your components you can then include `Vident::Tailwind` to get all the benefits of the amazing [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge/).
464
-
465
- Finally if none of the above gems suit your needs, you can always just use base `vident` gems to roll your own
466
- solution:
467
-
468
- - [`vident`](https://github.com/stevegeek/vident) to get the base functionality to mix with your own view component system
469
- - [`vident-typed`](https://github.com/stevegeek/vident-typed) to define typed attributes for your own view component system
470
-
471
-
472
- And then execute:
473
-
474
- $ bundle install
475
-
476
- ## Making 'sidecar' Stimulus Controllers work
477
-
478
- ### When using `stimulus-rails`, `sprockets-rails` & `importmap-rails`
479
-
480
- Pin any JS modules from under `app/views` and `app/components` which are sidecar with their respective components.
481
-
482
- Add to `config/importmap.rb`:
483
161
 
484
162
  ```ruby
485
- components_directories = [Rails.root.join("app/components"), Rails.root.join("app/views")]
486
- components_directories.each do |components_path|
487
- prefix = components_path.basename.to_s
488
- components_path.glob("**/*_controller.js").each do |controller|
489
- name = controller.relative_path_from(components_path).to_s.remove(/\.js$/)
490
- pin "#{prefix}/#{name}", to: name
491
- end
492
- end
163
+ ...
164
+ gem 'better_html'
165
+ gem 'vident-better_html'
493
166
  ```
494
167
 
495
- Note we don't use `pin_all_from` as it is meant to work with a subdirectory in `assets.paths`
496
- See this for more: https://stackoverflow.com/a/73228193/268602
168
+ Note that `vident-better_html` automatically enables `better_html` support in Vident root components.
497
169
 
498
- Then we need to ensure that sprockets picks up those files in build, so add
499
- to the `app/assets/config/manifest.js`:
170
+ ### Q4. Do you use or want to use [TailwindCSS](https://tailwindcss.com/)?
500
171
 
501
- ```js
502
- //= link_tree ../../components .js
503
- //= link_tree ../../views .js
504
- ```
505
-
506
- We also need to add to `assets.paths`. Add to your to `config/application.rb`
172
+ If yes, then consider adding [`vident-tailwind`](https://github.com/stevegeek/vident-tailwind) to your Gemfile alongside your vident gems of choice.
507
173
 
508
174
  ```ruby
509
- config.importmap.cache_sweepers.append(Rails.root.join("app/components"), Rails.root.join("app/views"))
510
- config.assets.paths.append("app/components", "app/views")
175
+ ...
176
+ gem 'vident-tailwind'
511
177
  ```
512
178
 
513
- ### When using `webpacker`
514
-
515
- TODO
179
+ When creating your components you can then include `Vident::Tailwind` to get all the benefits of the amazing [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge/).
516
180
 
517
- ### When using `propshaft`
181
+ ### Q5. Did none of the above gems suit your needs?
518
182
 
519
- TODO
183
+ You can always just use base `vident` gems and then roll your own solutions:
520
184
 
521
- ## Using TypeScript for Stimulus Controllers
185
+ - [`vident`](https://github.com/stevegeek/vident) to get the base functionality to mix with your own view component system
186
+ - [`vident-typed`](https://github.com/stevegeek/vident-typed) to define typed attributes for your own view component system
522
187
 
523
- TODO
524
188
 
525
- ## Usage
189
+ ## Documentation
526
190
 
527
- TODO: Write usage instructions here
191
+ See the [docs](docs/) directory and visit the individual gem pages for more information.
528
192
 
529
193
  ## Development
530
194
 
@@ -532,8 +196,6 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
532
196
 
533
197
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
534
198
 
535
-
536
-
537
199
  ## Contributing
538
200
 
539
201
  Bug reports and pull requests are welcome on GitHub at https://github.com/stevegeek/vident. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/vident/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vident
4
+ # Rails fragment caching works by either expecting the cached key object to respond to `cache_key` or for that object
5
+ # to be an array or hash.
6
+ module Caching
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def inherited(subclass)
11
+ subclass.instance_variable_set(:@named_cache_key_attributes, @named_cache_key_attributes.clone)
12
+ super
13
+ end
14
+
15
+ def with_cache_key(*attrs, name: :_collection)
16
+ # Add view file to cache key
17
+ attrs << :component_modified_time
18
+ attrs << :attributes
19
+ named_cache_key_includes(name, *attrs.uniq)
20
+ end
21
+
22
+ attr_reader :named_cache_key_attributes
23
+
24
+ # Components can be used with fragment caching, but you need to be careful! Read on...
25
+ #
26
+ # <% cache component do %>
27
+ # <%= render component %>
28
+ # <% end %>
29
+ #
30
+ # The most important point is that Rails cannot track dependencies on the component itself, so you need to
31
+ # be careful to be explicit on the attributes, and manually specify any sub Viewcomponent dependencies that the
32
+ # component has. The assumption is that the subcomponent takes any attributes from the parent, so the cache key
33
+ # depends on the parent component attributes. Otherwise changes to the parent or sub component views/Ruby class
34
+ # will result in different cache keys too. Of course if you invalidate all cache keys with a modifier on deploy
35
+ # then no need to worry about changing the cache key on component changes, only on attribute/data changes.
36
+ #
37
+ # A big caveat is that the cache key cannot depend on anything related to the view_context of the component (such
38
+ # as `helpers` as the key is created before the rending pipline is invoked (which is when the view_context is set).
39
+ def depends_on(*klasses)
40
+ @component_dependencies ||= []
41
+ @component_dependencies += klasses
42
+ end
43
+
44
+ attr_reader :component_dependencies
45
+
46
+ def component_modified_time
47
+ return @component_modified_time if Rails.env.production? && @component_modified_time
48
+
49
+ raise StandardError, "Must implement current_component_modified_time" unless respond_to?(:current_component_modified_time)
50
+
51
+ # FIXME: This could stack overflow if there are circular dependencies
52
+ deps = component_dependencies&.map(&:component_modified_time)&.join("-") || ""
53
+ @component_modified_time = deps + current_component_modified_time
54
+ end
55
+
56
+ private
57
+
58
+ def named_cache_key_includes(name, *attrs)
59
+ define_cache_key_method unless @named_cache_key_attributes
60
+ @named_cache_key_attributes ||= {}
61
+ @named_cache_key_attributes[name] = attrs
62
+ end
63
+
64
+ def define_cache_key_method
65
+ # If the presenter defines cache key setup then define the method. Otherwise Rails assumes this
66
+ # will return a valid key if the class will respond to this
67
+ define_method :cache_key do |n = :_collection|
68
+ if defined?(@cache_key)
69
+ return @cache_key[n] if @cache_key.key?(n)
70
+ else
71
+ @cache_key ||= {}
72
+ end
73
+ generate_cache_key(n)
74
+ @cache_key[n]
75
+ end
76
+ end
77
+ end
78
+
79
+ # Component modified time which is combined with other cache key attributes to generate cache key for an instance
80
+ def component_modified_time
81
+ self.class.component_modified_time
82
+ end
83
+
84
+ def cacheable?
85
+ respond_to? :cache_key
86
+ end
87
+
88
+ def cache_key_modifier
89
+ ENV["RAILS_CACHE_ID"]
90
+ end
91
+
92
+ def cache_keys_for_sources(key_attributes)
93
+ sources = key_attributes.flat_map { |n| n.is_a?(Proc) ? instance_eval(&n) : send(n) }
94
+ sources.compact.map do |item|
95
+ next if item == self
96
+ generate_item_cache_key_from(item)
97
+ end
98
+ end
99
+
100
+ def generate_item_cache_key_from(item)
101
+ if item.respond_to? :cache_key_with_version
102
+ item.cache_key_with_version
103
+ elsif item.respond_to? :cache_key
104
+ item.cache_key
105
+ elsif item.is_a?(String)
106
+ Digest::SHA1.hexdigest(item)
107
+ else
108
+ Digest::SHA1.hexdigest(Marshal.dump(item))
109
+ end
110
+ end
111
+
112
+ def generate_cache_key(index)
113
+ key_attributes = self.class.named_cache_key_attributes[index]
114
+ return nil unless key_attributes
115
+ key = "#{self.class.name}/#{cache_keys_for_sources(key_attributes).join("/")}"
116
+ raise StandardError, "Cache key for key #{key} is blank!" if key.blank?
117
+ @cache_key[index] = cache_key_modifier.present? ? "#{key}/#{cache_key_modifier}" : key
118
+ end
119
+ end
120
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vident
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vident
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ierodiaconou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-31 00:00:00.000000000 Z
11
+ date: 2023-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -46,6 +46,7 @@ files:
46
46
  - lib/vident.rb
47
47
  - lib/vident/attributes/not_typed.rb
48
48
  - lib/vident/base.rb
49
+ - lib/vident/caching.rb
49
50
  - lib/vident/component.rb
50
51
  - lib/vident/engine.rb
51
52
  - lib/vident/root_component.rb
@@ -73,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
74
  - !ruby/object:Gem::Version
74
75
  version: '0'
75
76
  requirements: []
76
- rubygems_version: 3.4.6
77
+ rubygems_version: 3.4.10
77
78
  signing_key:
78
79
  specification_version: 4
79
80
  summary: Vident is the base of your design system implementation, which provides helpers