vident 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +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
|