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 +4 -4
- data/README.md +93 -431
- data/lib/vident/caching.rb +120 -0
- data/lib/vident/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9144ee8a1b3361914b568d5dfbdcc22df506818813573e91ede2302e3a6775aa
|
4
|
+
data.tar.gz: 6476408b4f5acf6e4dd40d96539df7fb5588bb225200d3fbd05d35310a7c1c2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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
|
-
-
|
31
|
-
root element component (
|
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
|
-
-
|
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
|
-
|
38
|
-
so you can easily override classes when rendering a component.
|
69
|
+
Such as...
|
39
70
|
|
40
|
-
-
|
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
|
-
|
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 (
|
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
|
-
##
|
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
|
-
|
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
|
-
|
218
|
-
|
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
|
-
|
105
|
+
First, add this line to your application's Gemfile:
|
224
106
|
|
225
|
-
```
|
226
|
-
|
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
|
-
|
113
|
+
#### Q1. Do you want to use [`ViewComponent`](https://viewcomponent.org/) or [`Phlex`](https://www.phlex.fun/) for your view components?
|
232
114
|
|
233
|
-
|
115
|
+
For ViewComponent use:
|
234
116
|
|
235
|
-
|
117
|
+
- [`vident-view_component`](https://github.com/stevegeek/vident-view_component)
|
236
118
|
|
237
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
-
|
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
|
-
|
136
|
+
If yes, then add `vident-typed` to your Gemfile:
|
300
137
|
|
301
138
|
```ruby
|
302
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
149
|
+
For example, for ViewComponent, you might end up with:
|
376
150
|
|
377
151
|
```ruby
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
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
|
-
|
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
|
-
|
486
|
-
|
487
|
-
|
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
|
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
|
-
|
499
|
-
to the `app/assets/config/manifest.js`:
|
170
|
+
### Q4. Do you use or want to use [TailwindCSS](https://tailwindcss.com/)?
|
500
171
|
|
501
|
-
|
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
|
-
|
510
|
-
|
175
|
+
...
|
176
|
+
gem 'vident-tailwind'
|
511
177
|
```
|
512
178
|
|
513
|
-
|
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
|
-
###
|
181
|
+
### Q5. Did none of the above gems suit your needs?
|
518
182
|
|
519
|
-
|
183
|
+
You can always just use base `vident` gems and then roll your own solutions:
|
520
184
|
|
521
|
-
|
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
|
-
##
|
189
|
+
## Documentation
|
526
190
|
|
527
|
-
|
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
|
data/lib/vident/version.rb
CHANGED
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.
|
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-
|
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.
|
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
|