vident-phlex 0.13.1 → 1.0.0.alpha1
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/CHANGELOG.md +24 -0
- data/README.md +521 -665
- data/lib/vident/phlex/html.rb +49 -6
- data/lib/vident/phlex.rb +0 -2
- metadata +6 -8
- data/lib/vident/phlex/core.rb +0 -29
- data/lib/vident/phlex/root_component.rb +0 -55
data/README.md
CHANGED
@@ -1,851 +1,707 @@
|
|
1
1
|
# Vident
|
2
2
|
|
3
|
-
|
3
|
+
A powerful Ruby gem for building interactive, type-safe components in Rails applications with seamless [Stimulus.js](https://stimulus.hotwired.dev/) integration.
|
4
4
|
|
5
|
-
|
5
|
+
Vident supports both [ViewComponent](https://viewcomponent.org/) and [Phlex](https://www.phlex.fun/) rendering engines while providing a consistent API for creating
|
6
|
+
reusable UI components powered by [Stimulus.js](https://stimulus.hotwired.dev/).
|
6
7
|
|
7
|
-
|
8
|
+
## Table of Contents
|
8
9
|
|
9
|
-
-
|
10
|
-
-
|
11
|
-
-
|
10
|
+
- [Introduction](#introduction)
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Quick Start](#quick-start)
|
13
|
+
- [Core Concepts](#core-concepts)
|
14
|
+
- [Component DSL](#component-dsl)
|
15
|
+
- [Stimulus Integration](#stimulus-integration)
|
16
|
+
- [Advanced Features](#advanced-features)
|
17
|
+
- [Testing](#testing)
|
18
|
+
- [Contributing](#contributing)
|
12
19
|
|
13
|
-
|
20
|
+
## Introduction
|
14
21
|
|
15
|
-
|
22
|
+
Vident is a collection of gems that enhance Rails view components with:
|
16
23
|
|
17
|
-
-
|
18
|
-
-
|
19
|
-
-
|
20
|
-
-
|
21
|
-
-
|
22
|
-
-
|
24
|
+
- **Type-safe properties** using the Literal gem
|
25
|
+
- **First-class [Stimulus.js](https://stimulus.hotwired.dev/) integration** for interactive behaviors
|
26
|
+
- **Support for both [ViewComponent](https://viewcomponent.org/) and [Phlex](https://www.phlex.fun/)** rendering engines
|
27
|
+
- **Intelligent CSS class management** with built-in Tailwind CSS merging
|
28
|
+
- **Component caching** for improved performance
|
29
|
+
- **Declarative DSL** for clean, maintainable component code
|
23
30
|
|
24
|
-
|
31
|
+
### Why Vident?
|
25
32
|
|
26
|
-
|
33
|
+
Stimulus.js is a powerful framework for adding interactivity to HTML, but managing the data attributes can be cumbersome,
|
34
|
+
and refactoring can be error-prone (as say controller names are repeated in many places).
|
27
35
|
|
28
|
-
|
29
|
-
|
30
|
-
├── lib/ # All gem code
|
31
|
-
│ ├── vident.rb # Core entry point
|
32
|
-
│ ├── vident-phlex.rb # Gem entry points
|
33
|
-
│ ├── vident-better_html.rb
|
34
|
-
│ ├── vident/ # Shared code
|
35
|
-
│ ├── base.rb
|
36
|
-
│ ├── phlex/ # Phlex integration
|
37
|
-
│ ├── better_html/ # Better HTML integration
|
38
|
-
│ └── ...
|
39
|
-
├── test/ # All tests
|
40
|
-
│ ├── vident/ # Core tests
|
41
|
-
│ ├── vident-phlex/ # Tests for each gem
|
42
|
-
│ └── ...
|
43
|
-
├── docs/ # Documentation
|
44
|
-
├── examples/ # Examples
|
45
|
-
├── vident.gemspec # Gemspec for core gem
|
46
|
-
├── vident-phlex.gemspec # Gemspecs for each gem
|
47
|
-
└── ...
|
48
|
-
```
|
49
|
-
|
50
|
-
## Development
|
51
|
-
|
52
|
-
### Setting Up Development Environment
|
53
|
-
|
54
|
-
```bash
|
55
|
-
# Clone the repository
|
56
|
-
git clone https://github.com/stevegeek/vident.git
|
57
|
-
cd vident
|
58
|
-
|
59
|
-
# Install dependencies
|
60
|
-
bundle install
|
61
|
-
```
|
62
|
-
|
63
|
-
### Running Tests
|
64
|
-
|
65
|
-
To run tests for all gems:
|
66
|
-
|
67
|
-
```bash
|
68
|
-
rake test
|
69
|
-
```
|
70
|
-
|
71
|
-
To run tests for a specific gem:
|
72
|
-
|
73
|
-
```bash
|
74
|
-
rake test:vident-phlex
|
75
|
-
```
|
76
|
-
|
77
|
-
### Building and Installing Gems
|
78
|
-
|
79
|
-
To build all gems:
|
80
|
-
|
81
|
-
```bash
|
82
|
-
rake build
|
83
|
-
```
|
84
|
-
|
85
|
-
To install all gems locally:
|
86
|
-
|
87
|
-
```bash
|
88
|
-
rake install
|
89
|
-
```
|
90
|
-
|
91
|
-
## Contributing
|
92
|
-
|
93
|
-
1. Fork the repository
|
94
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
95
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
96
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
97
|
-
5. Create a new Pull Request
|
98
|
-
|
99
|
-
## License
|
36
|
+
Vident simplifies this by providing a declarative DSL for defining Stimulus controllers, actions, targets, and values
|
37
|
+
directly within your component classes so you don't need to manually craft data attributes in your templates.
|
100
38
|
|
101
|
-
|
39
|
+
Vident also ensures that your components are flexible: for example you can easily add to, or override configuration,
|
40
|
+
classes etc at the point of rendering.
|
102
41
|
|
103
|
-
|
42
|
+
Vident's goal is to make building UI components more maintainable, and remove some of the boilerplate code of Stimulus
|
43
|
+
without being over-bearing or including too much magic.
|
104
44
|
|
105
|
-
|
45
|
+
## Installation
|
106
46
|
|
107
|
-
|
108
|
-
|
109
|
-
## gem: vident-typed-view_component
|
110
|
-
|
111
|
-
# Vident::Typed::ViewComponent
|
112
|
-
|
113
|
-
Adds typed attributes to Vident ViewComponent components.
|
47
|
+
Add the core gem and your preferred rendering engine integration to your Gemfile:
|
114
48
|
|
115
49
|
```ruby
|
116
|
-
|
117
|
-
|
118
|
-
```
|
50
|
+
# Core gem (required)
|
51
|
+
gem "vident"
|
119
52
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
Before we dive into a specific example note that there are some components implemented in `test/dummy/app/components`.
|
53
|
+
# Choose your rendering engine (at least one required)
|
54
|
+
gem "vident-view_component" # For ViewComponent support
|
55
|
+
gem "vident-phlex" # For Phlex support
|
56
|
+
```
|
125
57
|
|
126
|
-
|
58
|
+
Then run:
|
127
59
|
|
128
60
|
```bash
|
129
|
-
cd test/dummy
|
130
61
|
bundle install
|
131
|
-
rails assets:precompile
|
132
|
-
rails s
|
133
62
|
```
|
134
63
|
|
135
|
-
|
64
|
+
## Quick Start
|
136
65
|
|
137
|
-
|
138
|
-
### A Vident component example (without Stimulus)
|
139
|
-
|
140
|
-
First is an example component that uses `Vident::Typed::ViewComponent::Base` but no Stimulus features.
|
141
|
-
|
142
|
-
It is an avatar component that can either be displayed as an image or as initials.
|
143
|
-
|
144
|
-
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.
|
66
|
+
Here's a simple example of a Vident component using ViewComponent:
|
145
67
|
|
146
68
|
```ruby
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
else
|
169
|
-
{ class: "inline-flex items-center justify-center bg-gray-500" }
|
170
|
-
end
|
69
|
+
# app/components/button_component.rb
|
70
|
+
class ButtonComponent < Vident::ViewComponent::Base
|
71
|
+
# Define typed properties
|
72
|
+
prop :text, String, default: "Click me"
|
73
|
+
prop :url, _Nilable(String)
|
74
|
+
prop :style, Symbol, in: [:primary, :secondary], default: :primary
|
75
|
+
prop :clicked_count, Integer, default: 0
|
76
|
+
|
77
|
+
# Configure Stimulus integration
|
78
|
+
stimulus do
|
79
|
+
actions [:click, :handle_click]
|
80
|
+
# Static values
|
81
|
+
values loading_duration: 1000
|
82
|
+
# Map the clicked_count prop as a Stimulus value
|
83
|
+
values_from_props :clicked_count
|
84
|
+
# Dynamic values using procs (evaluated in component context)
|
85
|
+
values item_count: -> { @items.count }
|
86
|
+
values api_url: -> { Rails.application.routes.url_helpers.api_items_path }
|
87
|
+
# Static and dynamic classes
|
88
|
+
classes loading: "opacity-50 cursor-wait"
|
89
|
+
classes size: -> { @items.count > 10 ? "large" : "small" }
|
171
90
|
end
|
172
91
|
|
173
|
-
def
|
174
|
-
|
92
|
+
def call
|
93
|
+
root_element do
|
94
|
+
@text
|
95
|
+
end
|
175
96
|
end
|
176
97
|
|
177
|
-
|
178
|
-
|
179
|
-
def shape_class
|
180
|
-
(shape == :circle) ? "rounded-full" : "rounded-md"
|
181
|
-
end
|
98
|
+
private
|
182
99
|
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
"w-8 h-8"
|
189
|
-
when :medium
|
190
|
-
"w-12 h-12"
|
191
|
-
when :large
|
192
|
-
"w-14 h-14"
|
193
|
-
when :x_large
|
194
|
-
"sm:w-24 sm:h-24 w-16 h-16"
|
195
|
-
when :xx_large
|
196
|
-
"sm:w-32 sm:h-32 w-24 h-24"
|
197
|
-
else
|
198
|
-
"w-10 h-10"
|
199
|
-
end
|
100
|
+
def root_element_attributes
|
101
|
+
{
|
102
|
+
element_tag: @url ? :a : :button,
|
103
|
+
html_options: { href: @url }.compact
|
104
|
+
}
|
200
105
|
end
|
201
106
|
|
202
|
-
def
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
"text-lg"
|
210
|
-
when :large
|
211
|
-
"sm:text-xl text-lg"
|
212
|
-
when :extra_large
|
213
|
-
"sm:text-2xl text-xl"
|
214
|
-
else
|
215
|
-
"text-medium"
|
107
|
+
def element_classes
|
108
|
+
base_classes = "btn"
|
109
|
+
case @style
|
110
|
+
when :primary
|
111
|
+
"#{base_classes} btn-primary"
|
112
|
+
when :secondary
|
113
|
+
"#{base_classes} btn-secondary"
|
216
114
|
end
|
217
115
|
end
|
218
116
|
end
|
219
117
|
```
|
220
118
|
|
221
|
-
```erb
|
222
|
-
<%= render root(
|
223
|
-
element_tag: image_avatar? ? :img : :div,
|
224
|
-
html_options: default_html_options
|
225
|
-
) do %>
|
226
|
-
<% unless image_avatar? %>
|
227
|
-
<span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
|
228
|
-
<% end %>
|
229
|
-
<% end %>
|
230
119
|
|
231
|
-
|
120
|
+
Add the corresponding Stimulus controller would be:
|
232
121
|
|
233
|
-
|
122
|
+
```javascript
|
123
|
+
// app/javascript/controllers/button_component_controller.js
|
124
|
+
// Can also be "side-car" in the same directory as the component, see the documentation for details
|
125
|
+
import { Controller } from "@hotwired/stimulus"
|
234
126
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
127
|
+
export default class extends Controller {
|
128
|
+
static values = {
|
129
|
+
clickedCount: Number,
|
130
|
+
loadingDuration: Number
|
131
|
+
}
|
132
|
+
static classes = ["loading"]
|
133
|
+
|
134
|
+
handleClick(event) {
|
135
|
+
// Increment counter
|
136
|
+
this.clickedCountValue++
|
137
|
+
|
138
|
+
// Add loading state
|
139
|
+
this.element.classList.add(this.loadingClass)
|
140
|
+
this.element.disabled = true
|
141
|
+
|
142
|
+
// Use the loading duration from the component
|
143
|
+
setTimeout(() => {
|
144
|
+
this.element.classList.remove(this.loadingClass)
|
145
|
+
this.element.disabled = false
|
146
|
+
|
147
|
+
// Update text to show count
|
148
|
+
this.element.textContent = `${this.element.textContent} (${this.clickedCountValue})`
|
149
|
+
}, this.loadingDurationValue)
|
150
|
+
}
|
151
|
+
}
|
249
152
|
```
|
250
153
|
|
154
|
+
Use the component in your views:
|
251
155
|
|
252
|
-
|
156
|
+
```erb
|
157
|
+
<!-- Default clicked count of 0 -->
|
158
|
+
<%= render ButtonComponent.new(text: "Save", style: :primary) %>
|
253
159
|
|
254
|
-
|
255
|
-
|
256
|
-
<span class="text-xs font-medium leading-none text-white">SG</span>
|
257
|
-
</div>
|
258
|
-
```
|
160
|
+
<!-- Pre-set clicked count -->
|
161
|
+
<%= render ButtonComponent.new(text: "Submit", style: :primary, clicked_count: 5) %>
|
259
162
|
|
260
|
-
|
163
|
+
<!-- Link variant -->
|
164
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home", style: :secondary) %>
|
261
165
|
|
262
|
-
|
263
|
-
|
166
|
+
<!-- Override things -->
|
167
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home" classes: "bg-red-900", html_options: {role: "button"}) %>
|
264
168
|
```
|
265
169
|
|
266
|
-
|
170
|
+
The rendered HTML includes all Stimulus data attributes:
|
267
171
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
172
|
+
```html
|
173
|
+
<!-- First button with default count -->
|
174
|
+
<button class="bg-blue-500 hover:bg-blue-700 text-white"
|
175
|
+
data-controller="button-component"
|
176
|
+
data-action="click->button-component#handleClick"
|
177
|
+
data-button-component-clicked-count-value="0"
|
178
|
+
data-button-component-loading-duration-value="1000"
|
179
|
+
data-button-component-loading-class="opacity-50 cursor-wait"
|
180
|
+
id="button-component-123">
|
181
|
+
Save
|
182
|
+
</button>
|
183
|
+
|
184
|
+
<!-- Second button with pre-set count -->
|
185
|
+
<button class="bg-blue-500 hover:bg-blue-700 text-white"
|
186
|
+
data-controller="button-component"
|
187
|
+
data-action="click->button-component#handleClick"
|
188
|
+
data-button-component-clicked-count-value="5"
|
189
|
+
data-button-component-loading-duration-value="1000"
|
190
|
+
data-button-component-loading-class="opacity-50 cursor-wait"
|
191
|
+
id="button-component-456">
|
192
|
+
Submit
|
193
|
+
</button>
|
194
|
+
```
|
195
|
+
|
196
|
+
## Core Concepts
|
197
|
+
|
198
|
+
### Component Properties
|
199
|
+
|
200
|
+
Vident uses the Literal gem to provide type-safe component properties:
|
277
201
|
|
278
|
-
|
202
|
+
```ruby
|
203
|
+
class CardComponent < Vident::ViewComponent::Base
|
204
|
+
# Basic property with type
|
205
|
+
prop :title, String
|
206
|
+
|
207
|
+
# Property with default value
|
208
|
+
prop :subtitle, String, default: ""
|
209
|
+
|
210
|
+
# Nullable property
|
211
|
+
prop :image_url, _Nilable(String)
|
212
|
+
|
213
|
+
# Property with validation
|
214
|
+
prop :size, _Union(:small, :medium, :large), default: :medium
|
215
|
+
|
216
|
+
# Boolean property (creates predicate method)
|
217
|
+
prop :featured, _Boolean, default: false
|
218
|
+
end
|
219
|
+
```
|
279
220
|
|
280
|
-
|
281
|
-
<%# app/views/home/index.html.erb %>
|
282
|
-
|
283
|
-
<!-- ... -->
|
284
|
-
|
285
|
-
<!-- render the Greeter ViewComponent (that uses Vident) -->
|
286
|
-
<%= render ::GreeterComponent.new(cta: "Hey!", html_options: {class: "my-4"}) do |greeter| %>
|
287
|
-
<%# this component has a slot called `trigger` that renders a `ButtonComponent` (which also uses Vident) %>
|
288
|
-
<% greeter.with_trigger(
|
289
|
-
|
290
|
-
# The button component has attributes that are typed
|
291
|
-
before_clicked: "Greet",
|
292
|
-
after_clicked: "Greeted! Reset?",
|
293
|
-
|
294
|
-
# A stimulus action is added to the button that triggers the `greet` action on the greeter stimulus controller.
|
295
|
-
# This action will be added to any defined on the button component itself
|
296
|
-
actions: [
|
297
|
-
greeter.action(:click, :greet),
|
298
|
-
],
|
299
|
-
|
300
|
-
# We can also override the default button classes of our component, or set other HTML attributes
|
301
|
-
html_options: {
|
302
|
-
class: "bg-red-500 hover:bg-red-700"
|
303
|
-
}
|
304
|
-
) %>
|
305
|
-
<% end %>
|
221
|
+
### Built-in Properties
|
306
222
|
|
307
|
-
|
308
|
-
```
|
223
|
+
Every Vident component includes these properties:
|
309
224
|
|
310
|
-
The
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
data-controller="greeter-component"
|
315
|
-
data-greeter-component-pre-click-class="text-md text-gray-500"
|
316
|
-
data-greeter-component-post-click-class="text-xl text-blue-700"
|
317
|
-
id="greeter-component-1599855-6">
|
318
|
-
<input type="text"
|
319
|
-
data-greeter-component-target="name"
|
320
|
-
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
321
|
-
<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"
|
322
|
-
data-controller="button-component"
|
323
|
-
data-action="click->greeter-component#greet button-component#changeMessage"
|
324
|
-
data-button-component-after-clicked-message="Greeted! Reset?"
|
325
|
-
data-button-component-before-clicked-message="Greet"
|
326
|
-
id="button-component-7799479-7">Hey!</button>
|
327
|
-
<!-- you can also use the `target_tag` helper to render targets -->
|
328
|
-
<span class="ml-4 text-md text-gray-500"
|
329
|
-
data-greeter-component-target="output">
|
330
|
-
...
|
331
|
-
</span>
|
332
|
-
</div>
|
333
|
-
```
|
225
|
+
- `element_tag` - The HTML tag for the root element (default: `:div`)
|
226
|
+
- `id` - The component's DOM ID (auto-generated if not provided)
|
227
|
+
- `classes` - Additional CSS classes
|
228
|
+
- `html_options` - Hash of HTML attributes
|
334
229
|
|
335
|
-
|
230
|
+
### Root Element Rendering
|
336
231
|
|
337
|
-
The
|
232
|
+
The `root_element` helper method renders your component's root element with all configured attributes:
|
338
233
|
|
339
234
|
```ruby
|
340
|
-
#
|
235
|
+
# In your component class
|
236
|
+
def element_classes
|
237
|
+
["card", featured? ? "card-featured" : nil]
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
341
241
|
|
342
|
-
|
343
|
-
|
242
|
+
def root_element_attributes
|
243
|
+
{
|
244
|
+
html_options: { role: "article", "aria-label": title }
|
245
|
+
}
|
344
246
|
end
|
345
247
|
```
|
346
248
|
|
347
249
|
```erb
|
348
|
-
<%#
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
<%= render root named_classes: {
|
354
|
-
pre_click: "text-md text-gray-500", # named classes are exposed to Stimulus as `data-<controller>-<n>-class` attributes
|
355
|
-
post_click: "text-xl text-blue-700",
|
356
|
-
html_options: {class: "py-2"}
|
357
|
-
} do |greeter| %>
|
358
|
-
|
359
|
-
<%# `greeter` is the root element and exposes methods to generate stimulus targets and actions %>
|
360
|
-
<input type="text"
|
361
|
-
<%= greeter.as_target(:name) %>
|
362
|
-
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
363
|
-
|
364
|
-
<%# Render the slot %>
|
365
|
-
<%= trigger %>
|
366
|
-
|
367
|
-
<%# you can also use the `target_tag` helper to render targets %>
|
368
|
-
<%= greeter.target_tag(
|
369
|
-
:span,
|
370
|
-
:output,
|
371
|
-
# Stimulus named classes can be referenced to set class attributes at render time
|
372
|
-
class: "ml-4 #{greeter.named_classes(:pre_click)}"
|
373
|
-
) do %>
|
374
|
-
...
|
375
|
-
<% end %>
|
250
|
+
<%# In your template %>
|
251
|
+
<%= root_element do %>
|
252
|
+
<h2><%= title %></h2>
|
253
|
+
<p><%= subtitle %></p>
|
376
254
|
<% end %>
|
377
|
-
|
378
255
|
```
|
379
256
|
|
380
|
-
|
381
|
-
// app/components/greeter_component_controller.js
|
257
|
+
## Component DSL
|
382
258
|
|
383
|
-
|
259
|
+
### ViewComponent Integration
|
384
260
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
export default class extends Controller {
|
390
|
-
static targets = [ "name", "output" ]
|
391
|
-
static classes = [ "preClick", "postClick" ]
|
392
|
-
|
393
|
-
greet() {
|
394
|
-
this.clicked = !this.clicked;
|
395
|
-
this.outputTarget.classList.toggle(this.preClickClasses, !this.clicked);
|
396
|
-
this.outputTarget.classList.toggle(this.postClickClasses, this.clicked);
|
397
|
-
|
398
|
-
if (this.clicked)
|
399
|
-
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
|
400
|
-
else
|
401
|
-
this.clear();
|
402
|
-
}
|
261
|
+
```ruby
|
262
|
+
class MyComponent < Vident::ViewComponent::Base
|
263
|
+
# Component code
|
264
|
+
end
|
403
265
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
266
|
+
# Or with an application base class
|
267
|
+
class ApplicationComponent < Vident::ViewComponent::Base
|
268
|
+
# Shared configuration
|
269
|
+
end
|
270
|
+
|
271
|
+
class MyComponent < ApplicationComponent
|
272
|
+
# Component code
|
273
|
+
end
|
409
274
|
```
|
410
275
|
|
411
|
-
|
276
|
+
### Phlex Integration
|
412
277
|
|
413
278
|
```ruby
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
attribute :after_clicked, String, default: "Greeted!"
|
419
|
-
attribute :before_clicked, String, allow_nil: false
|
420
|
-
|
421
|
-
# This example is a templateless ViewComponent.
|
422
|
-
def call
|
423
|
-
# The button is rendered as a <button> tag with an click action on its own controller.
|
424
|
-
render root(
|
425
|
-
element_tag: :button,
|
426
|
-
|
427
|
-
# We can define actions as arrays of Symbols, or pass manually manually crafted strings.
|
428
|
-
# Here we specify the action name only, implying an action on the current components controller
|
429
|
-
# and the default event type of `click`.
|
430
|
-
actions: [:change_message],
|
431
|
-
# Alternatively: [:click, :change_message] or ["click", "changeMessage"] or even "click->button-component#changeMessage"
|
432
|
-
|
433
|
-
# A couple of data values are also set which will be available to the controller
|
434
|
-
data_maps: [{after_clicked_message: after_clicked, before_clicked_message: before_clicked}],
|
435
|
-
|
436
|
-
# The <button> tag has a default styling set directly on it. Note that
|
437
|
-
# if not using utility classes, you can style the component using its
|
438
|
-
# canonical class name (which is equal to the component's stimulus identifier),
|
439
|
-
# in this case `button-component`.
|
440
|
-
html_options: {class: "ml-4 whitespace-no-wrap bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"}
|
441
|
-
) do
|
442
|
-
@before_clicked
|
279
|
+
class MyComponent < Vident::Phlex::HTML
|
280
|
+
def view_template
|
281
|
+
root do
|
282
|
+
h1 { "Hello from Phlex!" }
|
443
283
|
end
|
444
284
|
end
|
445
285
|
end
|
446
286
|
```
|
447
287
|
|
448
|
-
|
449
|
-
// app/components/button_component_controller.js
|
450
|
-
|
451
|
-
import { Controller } from "@hotwired/stimulus"
|
452
|
-
|
453
|
-
export default class extends Controller {
|
454
|
-
// The action is in camelCase.
|
455
|
-
changeMessage() {
|
456
|
-
this.clicked = !this.clicked;
|
457
|
-
// The data attributes have their naming convention converted to camelCase.
|
458
|
-
this.element.textContent = this.clicked ? this.data.get("afterClickedMessage") : this.data.get("beforeClickedMessage");
|
459
|
-
}
|
460
|
-
}
|
288
|
+
## Stimulus Integration
|
461
289
|
|
462
|
-
|
290
|
+
Vident provides comprehensive Stimulus.js integration to add interactivity to your components.
|
463
291
|
|
464
|
-
###
|
465
|
-
How to use my plugin.
|
292
|
+
### Declarative Stimulus DSL
|
466
293
|
|
467
|
-
|
468
|
-
Add this line to your application's Gemfile:
|
294
|
+
Use the `stimulus` block for clean, declarative configuration:
|
469
295
|
|
470
296
|
```ruby
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
297
|
+
class ToggleComponent < Vident::ViewComponent::Base
|
298
|
+
prop :expanded, _Boolean, default: false
|
299
|
+
|
300
|
+
stimulus do
|
301
|
+
# Define actions the controller responds to
|
302
|
+
actions :toggle, :expand, :collapse
|
303
|
+
|
304
|
+
# Define targets for DOM element references
|
305
|
+
targets :button, :content
|
306
|
+
|
307
|
+
# Define static values
|
308
|
+
values animation_duration: 300
|
309
|
+
|
310
|
+
# Define dynamic values using procs (evaluated in component context)
|
311
|
+
values item_count: -> { @items.count }
|
312
|
+
values current_state: proc { expanded? ? "open" : "closed" }
|
313
|
+
|
314
|
+
# Map values from component props
|
315
|
+
values_from_props :expanded
|
316
|
+
|
317
|
+
# Define CSS classes for different states
|
318
|
+
classes expanded: "block",
|
319
|
+
collapsed: "hidden",
|
320
|
+
transitioning: "opacity-50"
|
321
|
+
end
|
322
|
+
end
|
482
323
|
```
|
483
324
|
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
## gem: vident-view_component
|
488
|
-
|
489
|
-
# Vident::ViewComponent
|
325
|
+
### Dynamic Values and Classes with Procs
|
490
326
|
|
491
|
-
|
327
|
+
The Stimulus DSL supports dynamic values and classes using procs or lambdas that are evaluated in the component instance context:
|
492
328
|
|
493
329
|
```ruby
|
494
|
-
class
|
330
|
+
class DynamicComponent < Vident::ViewComponent::Base
|
331
|
+
prop :items, _Array(Hash), default: -> { [] }
|
332
|
+
prop :loading, _Boolean, default: false
|
333
|
+
prop :user, _Nilable(User)
|
334
|
+
|
335
|
+
stimulus do
|
336
|
+
# Mix static and dynamic values in a single call
|
337
|
+
values(
|
338
|
+
static_config: "always_same",
|
339
|
+
item_count: -> { @items.count },
|
340
|
+
loading_state: proc { @loading ? "loading" : "idle" },
|
341
|
+
user_role: -> { @user&.role || "guest" },
|
342
|
+
api_endpoint: -> { Rails.application.routes.url_helpers.api_items_path }
|
343
|
+
)
|
344
|
+
|
345
|
+
# Mix static and dynamic classes
|
346
|
+
classes(
|
347
|
+
base: "component-container",
|
348
|
+
loading: -> { @loading ? "opacity-50 cursor-wait" : "" },
|
349
|
+
size: proc { @items.count > 10 ? "large" : "small" },
|
350
|
+
theme: -> { current_user&.dark_mode? ? "dark" : "light" }
|
351
|
+
)
|
352
|
+
|
353
|
+
# Dynamic actions and targets
|
354
|
+
actions -> { @loading ? [] : [:click, :submit] }
|
355
|
+
targets -> { @expanded ? [:content, :toggle] : [:toggle] }
|
356
|
+
end
|
357
|
+
|
358
|
+
private
|
359
|
+
|
360
|
+
def current_user
|
361
|
+
@current_user ||= User.current
|
362
|
+
end
|
495
363
|
end
|
496
364
|
```
|
497
365
|
|
498
|
-
|
499
|
-
|
500
|
-
### Examples
|
366
|
+
Procs have access to instance variables, component methods, and Rails helpers.
|
501
367
|
|
502
|
-
|
368
|
+
**Important**: Each proc returns a single value for its corresponding stimulus attribute. If a proc returns an array, that entire array is treated as a single value, not multiple separate values. To provide multiple values for an attribute, use multiple procs or mix procs with static values:
|
503
369
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
370
|
+
```ruby
|
371
|
+
stimulus do
|
372
|
+
# Single proc returns a single value (even if it's an array)
|
373
|
+
actions -> { @expanded ? [:click, :submit] : :click }
|
374
|
+
|
375
|
+
# Multiple procs provide multiple values
|
376
|
+
actions -> { @can_edit ? :edit : nil },
|
377
|
+
-> { @can_delete ? :delete : nil },
|
378
|
+
:cancel # static value
|
379
|
+
|
380
|
+
# This results in: [:edit, :delete, :cancel] (assuming both conditions are true)
|
381
|
+
end
|
511
382
|
```
|
512
383
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
### A Vident component example (without Stimulus)
|
517
|
-
|
518
|
-
First is an example component that uses `Vident::ViewComponent::Base` but no Stimulus features.
|
384
|
+
### Scoped Custom Events
|
519
385
|
|
520
|
-
|
386
|
+
Vident provides helper methods to generate scoped event names for dispatching custom events that are unique to your component:
|
521
387
|
|
522
388
|
```ruby
|
523
|
-
class
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
no_stimulus_controller
|
528
|
-
with_cache_key :attributes
|
529
|
-
|
530
|
-
attribute :url, allow_nil: true
|
531
|
-
attribute :initials, allow_nil: false
|
532
|
-
|
533
|
-
attribute :shape, default: :circle
|
534
|
-
|
535
|
-
attribute :border, default: false
|
536
|
-
|
537
|
-
attribute :size, default: :normal
|
538
|
-
|
539
|
-
private
|
540
|
-
|
541
|
-
def default_html_options
|
542
|
-
if image_avatar?
|
543
|
-
{ class: "inline-block object-contain", src: url, alt: t(".image") }
|
544
|
-
else
|
545
|
-
{ class: "inline-flex items-center justify-center bg-gray-500" }
|
546
|
-
end
|
389
|
+
class MyComponent < Vident::ViewComponent::Base
|
390
|
+
stimulus do
|
391
|
+
# Define an action that responds to a scoped event
|
392
|
+
actions [stimulus_scoped_event_on_window(:data_loaded), :handle_data_loaded]
|
547
393
|
end
|
548
|
-
|
549
|
-
def
|
550
|
-
|
394
|
+
|
395
|
+
def handle_click
|
396
|
+
# Dispatch a scoped event from JavaScript
|
397
|
+
# This would generate: "my-component:dataLoaded"
|
398
|
+
puts stimulus_scoped_event(:data_loaded)
|
399
|
+
|
400
|
+
# For window events, this generates: "my-component:dataLoaded@window"
|
401
|
+
puts stimulus_scoped_event_on_window(:data_loaded)
|
551
402
|
end
|
403
|
+
end
|
552
404
|
|
553
|
-
|
405
|
+
# Available as both class and instance methods:
|
406
|
+
MyComponent.stimulus_scoped_event(:data_loaded) # => "my-component:dataLoaded"
|
407
|
+
MyComponent.new.stimulus_scoped_event(:data_loaded) # => "my-component:dataLoaded"
|
408
|
+
```
|
554
409
|
|
555
|
-
|
556
|
-
|
557
|
-
|
410
|
+
This is useful for:
|
411
|
+
- Dispatching events from Stimulus controllers to communicate between components
|
412
|
+
- Creating unique event names that won't conflict with other components
|
413
|
+
- Setting up window-level event listeners with scoped names
|
558
414
|
|
559
|
-
|
560
|
-
case size
|
561
|
-
when :tiny
|
562
|
-
"w-6 h-6"
|
563
|
-
when :small
|
564
|
-
"w-8 h-8"
|
565
|
-
when :medium
|
566
|
-
"w-12 h-12"
|
567
|
-
when :large
|
568
|
-
"w-14 h-14"
|
569
|
-
when :x_large
|
570
|
-
"sm:w-24 sm:h-24 w-16 h-16"
|
571
|
-
when :xx_large
|
572
|
-
"sm:w-32 sm:h-32 w-24 h-24"
|
573
|
-
else
|
574
|
-
"w-10 h-10"
|
575
|
-
end
|
576
|
-
end
|
415
|
+
### Manual Stimulus Configuration
|
577
416
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
417
|
+
For more control, configure Stimulus attributes manually:
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
class CustomComponent < Vident::ViewComponent::Base
|
421
|
+
private
|
422
|
+
|
423
|
+
def root_element_attributes
|
424
|
+
{
|
425
|
+
element_tag: :article,
|
426
|
+
stimulus_controllers: ["custom", "analytics"],
|
427
|
+
stimulus_actions: [
|
428
|
+
[:click, :handleClick],
|
429
|
+
[:custom_event, :handleCustom]
|
430
|
+
],
|
431
|
+
stimulus_values: {
|
432
|
+
endpoint: "/api/data",
|
433
|
+
refresh_interval: 5000
|
434
|
+
},
|
435
|
+
stimulus_targets: {
|
436
|
+
container: true
|
437
|
+
}
|
438
|
+
}
|
593
439
|
end
|
594
440
|
end
|
595
441
|
```
|
596
442
|
|
443
|
+
or you can use tag helpers to generate HTML with Stimulus attributes:
|
444
|
+
|
597
445
|
```erb
|
598
|
-
<%=
|
599
|
-
|
600
|
-
|
601
|
-
) do %>
|
602
|
-
<% unless image_avatar? %>
|
603
|
-
<span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
|
446
|
+
<%= content_tag(:input, type: "text", class: "...", data: {**greeter.stimulus_target(:name)}) %>
|
447
|
+
<%= content_tag(:button, @cta, class: "...", data: {**greeter.stimulus_action([:click, :greet])}) do %>
|
448
|
+
<%= @cta %>
|
604
449
|
<% end %>
|
605
|
-
|
606
|
-
```
|
450
|
+
<%= content_tag(:span, class: "...", data: {**greeter.stimulus_target(:output)}) %>
|
607
451
|
|
608
|
-
|
452
|
+
<%# OR use the vident tag helper %>
|
609
453
|
|
610
|
-
|
611
|
-
<%=
|
612
|
-
<%=
|
613
|
-
|
614
|
-
<%=
|
454
|
+
<%= greeter.tag(:input, stimulus_target: :name, type: "text", class: "...") %>
|
455
|
+
<%= greeter.tag(:button, stimulus_action: [:click, :greet], class: "...") do %>
|
456
|
+
<%= @cta %>
|
457
|
+
<% end %>
|
458
|
+
<%= greeter.tag(:span, stimulus_target: :output, class: "...") %>
|
615
459
|
```
|
616
460
|
|
617
|
-
|
461
|
+
or in your Phlex templates:
|
618
462
|
|
619
|
-
```
|
620
|
-
|
621
|
-
|
622
|
-
|
463
|
+
```ruby
|
464
|
+
root_element do |greeter|
|
465
|
+
input(type: "text", data: {**greeter.stimulus_target(:name)}, class: %(...))
|
466
|
+
trigger_or_default(greeter)
|
467
|
+
greeter.tag(:span, stimulus_target: :output, class: "ml-4 #{greeter.class_list_for_stimulus_classes(:pre_click)}") do
|
468
|
+
plain %( ... )
|
469
|
+
end
|
470
|
+
end
|
623
471
|
```
|
624
472
|
|
625
|
-
|
473
|
+
or directly in the ViewComponent template (eg with ERB) using the `as_stimulus_*` helpers
|
626
474
|
|
627
|
-
```
|
628
|
-
|
475
|
+
```erb
|
476
|
+
<%# HTML embellishment approach, most familiar to working with HTML in ERB, but is injecting directly into open HTML tags... %>
|
477
|
+
<input type="text"
|
478
|
+
<%= greeter.as_stimulus_targets(:name) %>
|
479
|
+
class="...">
|
480
|
+
<button <%= greeter.as_stimulus_actions([:click, :greet]) %>
|
481
|
+
class="...">
|
482
|
+
<%= @cta %>
|
483
|
+
</button>
|
484
|
+
<span <%= greeter.as_stimulus_targets(:output) %> class="..."></span>
|
629
485
|
```
|
630
486
|
|
631
|
-
----
|
632
487
|
|
633
|
-
|
488
|
+
### Stimulus Helpers in Templates
|
634
489
|
|
635
|
-
|
636
|
-
How to use my plugin.
|
490
|
+
Vident provides helper methods for generating Stimulus attributes:
|
637
491
|
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
492
|
+
```erb
|
493
|
+
<%= render root do |component| %>
|
494
|
+
<!-- Create a target -->
|
495
|
+
<div <%= component.as_target(:content) %>>
|
496
|
+
Content here
|
497
|
+
</div>
|
498
|
+
|
499
|
+
<!-- Create an action -->
|
500
|
+
<button <%= component.as_action(:click, :toggle) %>>
|
501
|
+
Toggle
|
502
|
+
</button>
|
503
|
+
|
504
|
+
<!-- Use the tag helper -->
|
505
|
+
<%= component.tag :div, stimulus_target: :output, class: "mt-4" do %>
|
506
|
+
Output here
|
507
|
+
<% end %>
|
508
|
+
|
509
|
+
<!-- Multiple targets/actions -->
|
510
|
+
<input <%= component.as_targets(:input, :field) %>
|
511
|
+
<%= component.as_actions([:input, :validate], [:change, :save]) %>>
|
512
|
+
<% end %>
|
643
513
|
```
|
644
514
|
|
645
|
-
|
646
|
-
```bash
|
647
|
-
$ bundle
|
648
|
-
```
|
515
|
+
### Stimulus Outlets
|
649
516
|
|
650
|
-
|
651
|
-
```bash
|
652
|
-
$ gem install vident-view_component
|
653
|
-
```
|
517
|
+
Connect components via Stimulus outlets:
|
654
518
|
|
655
|
-
---
|
656
519
|
|
657
|
-
## gem: vident-better_html
|
658
520
|
|
659
|
-
# Vident::BetterHtml
|
660
|
-
Short description and motivation.
|
661
521
|
|
662
|
-
###
|
663
|
-
How to use my plugin.
|
522
|
+
### Stimulus Controller Naming
|
664
523
|
|
665
|
-
|
666
|
-
BetterHtml.config = BetterHtml::Config.new(YAML.load(File.read(".better-html.yml")))
|
524
|
+
Vident automatically generates Stimulus controller names based on your component class:
|
667
525
|
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
# ViewComponent needs to do this hack to work in certain cases
|
672
|
-
# see https://github.com/Shopify/better-html/pull/98
|
673
|
-
class BetterHtml::HtmlAttributes
|
674
|
-
alias_method :to_s_without_html_safe, :to_s
|
526
|
+
- `ButtonComponent` → `button-component`
|
527
|
+
- `Admin::UserCardComponent` → `admin--user-card-component`
|
528
|
+
- `MyApp::WidgetComponent` → `my-app--widget-component`
|
675
529
|
|
676
|
-
|
677
|
-
to_s_without_html_safe.html_safe
|
678
|
-
end
|
679
|
-
end
|
680
|
-
```
|
530
|
+
### Working with Child Components
|
681
531
|
|
682
|
-
|
683
|
-
Add this line to your application's Gemfile:
|
532
|
+
Setting Stimulus configuration between parent and child components:
|
684
533
|
|
685
534
|
```ruby
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
535
|
+
class ParentComponent < Vident::ViewComponent::Base
|
536
|
+
renders_one :a_nested_component, ButtonComponent
|
537
|
+
|
538
|
+
stimulus do
|
539
|
+
actions :handleTrigger
|
540
|
+
end
|
541
|
+
end
|
692
542
|
```
|
693
543
|
|
694
|
-
|
695
|
-
|
696
|
-
|
544
|
+
```erb
|
545
|
+
<%= root_element do |parent| %>
|
546
|
+
<% parent.with_a_nested_component(
|
547
|
+
text: "Click me",
|
548
|
+
stimulus_actions: [
|
549
|
+
parent.stimulus_action(:click, :handleTrigger)
|
550
|
+
]
|
551
|
+
) %>
|
552
|
+
<% end %>
|
697
553
|
```
|
698
554
|
|
699
|
-
|
555
|
+
This creates a nested component that once clicked triggers the parent components `handleTrigger` action.
|
700
556
|
|
701
|
-
##
|
557
|
+
## Other Features
|
702
558
|
|
703
|
-
|
559
|
+
### Custom Element Tags
|
704
560
|
|
705
|
-
|
561
|
+
Change the root element tag dynamically:
|
706
562
|
|
707
563
|
```ruby
|
708
|
-
class
|
564
|
+
class LinkOrButtonComponent < Vident::ViewComponent::Base
|
565
|
+
prop :url, _Nilable(String)
|
566
|
+
|
567
|
+
private
|
568
|
+
|
569
|
+
def root_element_attributes
|
570
|
+
{
|
571
|
+
element_tag: url? ? :a : :button,
|
572
|
+
html_options: {
|
573
|
+
href: url,
|
574
|
+
type: url? ? nil : "button"
|
575
|
+
}.compact
|
576
|
+
}
|
577
|
+
end
|
709
578
|
end
|
710
579
|
```
|
711
580
|
|
712
|
-
|
713
|
-
|
714
|
-
### Usage
|
715
|
-
How to use my plugin.
|
581
|
+
### Intelligent Class Management
|
716
582
|
|
717
|
-
|
718
|
-
Add this line to your application's Gemfile:
|
583
|
+
Vident intelligently merges CSS classes from multiple sources:
|
719
584
|
|
720
585
|
```ruby
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
586
|
+
class StyledComponent < Vident::ViewComponent::Base
|
587
|
+
prop :variant, Symbol, default: :default
|
588
|
+
|
589
|
+
private
|
590
|
+
|
591
|
+
# Classes on the root element
|
592
|
+
def element_classes
|
593
|
+
["base-class", variant_class]
|
594
|
+
end
|
595
|
+
|
596
|
+
def variant_class
|
597
|
+
case @variant
|
598
|
+
when :primary then "text-blue-600 bg-blue-100"
|
599
|
+
when :danger then "text-red-600 bg-red-100"
|
600
|
+
else "text-gray-600 bg-gray-100"
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
727
604
|
```
|
728
605
|
|
729
|
-
|
730
|
-
```
|
731
|
-
|
606
|
+
Usage:
|
607
|
+
```erb
|
608
|
+
<!-- All classes are intelligently merged -->
|
609
|
+
<%= render StyledComponent.new(
|
610
|
+
variant: :primary,
|
611
|
+
classes: "rounded-lg shadow"
|
612
|
+
) %>
|
613
|
+
<!-- Result: class="base-class text-blue-600 bg-blue-100 rounded-lg shadow" -->
|
732
614
|
```
|
733
615
|
|
734
|
-
|
735
|
-
|
736
|
-
## gem: vident-tailwind
|
737
|
-
|
738
|
-
# Vident::Tailwind
|
739
|
-
Short description and motivation.
|
740
|
-
|
741
|
-
### Usage
|
742
|
-
How to use my plugin.
|
616
|
+
### Tailwind CSS Integration
|
743
617
|
|
744
|
-
|
745
|
-
Add this line to your application's Gemfile:
|
618
|
+
Vident includes built-in support for Tailwind CSS class merging when the `tailwind_merge` gem is available:
|
746
619
|
|
747
620
|
```ruby
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
621
|
+
class TailwindComponent < Vident::ViewComponent::Base
|
622
|
+
prop :size, Symbol, default: :medium
|
623
|
+
|
624
|
+
private
|
625
|
+
|
626
|
+
def element_classes
|
627
|
+
# Conflicts with size_class will be resolved automatically
|
628
|
+
"p-2 text-sm #{size_class}"
|
629
|
+
end
|
630
|
+
|
631
|
+
def size_class
|
632
|
+
case @size
|
633
|
+
when :small then "p-1 text-xs"
|
634
|
+
when :large then "p-4 text-lg"
|
635
|
+
else "p-2 text-base"
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
759
639
|
```
|
760
640
|
|
761
|
-
|
641
|
+
### Component Caching
|
762
642
|
|
763
|
-
|
764
|
-
|
765
|
-
# Vident::Typed::Minitest
|
766
|
-
Short description and motivation.
|
767
|
-
|
768
|
-
### Usage
|
769
|
-
How to use my plugin.
|
770
|
-
|
771
|
-
### Installation
|
772
|
-
Add this line to your application's Gemfile:
|
643
|
+
Enable fragment caching for expensive components:
|
773
644
|
|
774
645
|
```ruby
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
646
|
+
class ExpensiveComponent < Vident::ViewComponent::Base
|
647
|
+
include Vident::Caching
|
648
|
+
|
649
|
+
with_cache_key :to_h # Cache based on all attributes
|
650
|
+
# or
|
651
|
+
with_cache_key :id, :updated_at # Cache based on specific attributes
|
652
|
+
end
|
781
653
|
```
|
782
654
|
|
783
|
-
|
784
|
-
|
785
|
-
|
655
|
+
```erb
|
656
|
+
<% cache component.cache_key do %>
|
657
|
+
<%= render component %>
|
658
|
+
<% end %>
|
786
659
|
```
|
787
660
|
|
788
|
-
---
|
789
|
-
|
790
|
-
## gem: vident-typed-phlex
|
791
661
|
|
792
|
-
|
662
|
+
## Testing
|
793
663
|
|
794
|
-
|
664
|
+
Vident components work seamlessly with testing frameworks that support ViewComponent or Phlex.
|
795
665
|
|
796
|
-
|
797
|
-
class ApplicationComponent < ::Vident::Typed::Phlex::HTML
|
798
|
-
end
|
799
|
-
```
|
800
|
-
|
801
|
-
For more details see [vident](https://github.com/stevegeek/vident).
|
802
|
-
|
803
|
-
### Usage
|
804
|
-
How to use my plugin.
|
805
|
-
|
806
|
-
### Installation
|
807
|
-
Add this line to your application's Gemfile:
|
666
|
+
## Development
|
808
667
|
|
809
|
-
|
810
|
-
gem "vident-typed-phlex"
|
811
|
-
```
|
668
|
+
### Running Tests
|
812
669
|
|
813
|
-
And then execute:
|
814
670
|
```bash
|
815
|
-
|
671
|
+
# Run all tests
|
672
|
+
bin/rails test
|
816
673
|
```
|
817
674
|
|
818
|
-
|
819
|
-
```bash
|
820
|
-
$ gem install vident-typed-phlex
|
821
|
-
```
|
675
|
+
### Local Development
|
822
676
|
|
823
|
-
|
677
|
+
```bash
|
678
|
+
# Clone the repository
|
679
|
+
git clone https://github.com/stevegeek/vident.git
|
680
|
+
cd vident
|
824
681
|
|
682
|
+
# Install dependencies
|
683
|
+
bundle install
|
825
684
|
|
826
|
-
|
685
|
+
# Run the dummy app
|
686
|
+
cd test/dummy
|
687
|
+
rails s
|
688
|
+
```
|
827
689
|
|
828
|
-
|
829
|
-
Short description and motivation.
|
690
|
+
## Contributing
|
830
691
|
|
831
|
-
|
832
|
-
|
692
|
+
1. Fork the repository
|
693
|
+
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
694
|
+
3. Write tests for your changes
|
695
|
+
4. Commit your changes (`git commit -am 'Add new feature'`)
|
696
|
+
5. Push to the branch (`git push origin feature/my-new-feature`)
|
697
|
+
6. Create a Pull Request
|
833
698
|
|
834
|
-
|
835
|
-
Add this line to your application's Gemfile:
|
699
|
+
## License
|
836
700
|
|
837
|
-
|
838
|
-
gem "vident-typed"
|
839
|
-
```
|
701
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
840
702
|
|
841
|
-
|
842
|
-
```bash
|
843
|
-
$ bundle
|
844
|
-
```
|
703
|
+
## Credits
|
845
704
|
|
846
|
-
|
847
|
-
```bash
|
848
|
-
$ gem install vident-typed
|
849
|
-
```
|
705
|
+
Vident is maintained by [Stephen Ierodiaconou](https://github.com/stevegeek).
|
850
706
|
|
851
|
-
|
707
|
+
Special thanks to the ViewComponent and Phlex communities for their excellent component frameworks that Vident builds upon.
|