vident-phlex 0.14.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 +22 -3
- data/README.md +525 -616
- 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 -47
data/README.md
CHANGED
@@ -1,798 +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
|
-
-
|
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
|
22
30
|
|
23
|
-
|
31
|
+
### Why Vident?
|
24
32
|
|
25
|
-
|
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).
|
26
35
|
|
27
|
-
|
28
|
-
|
29
|
-
├── lib/ # All gem code
|
30
|
-
│ ├── vident.rb # Core entry point
|
31
|
-
│ ├── vident-phlex.rb # Gem entry points
|
32
|
-
├── test/ # All tests
|
33
|
-
│ ├── vident/ # Core tests
|
34
|
-
│ ├── vident-phlex/ # Tests for each gem
|
35
|
-
│ └── ...
|
36
|
-
├── docs/ # Documentation
|
37
|
-
├── examples/ # Examples
|
38
|
-
├── vident.gemspec # Gemspec for core gem
|
39
|
-
├── vident-phlex.gemspec # Gemspecs for each gem
|
40
|
-
└── ...
|
41
|
-
```
|
42
|
-
|
43
|
-
## Development
|
44
|
-
|
45
|
-
### Setting Up Development Environment
|
46
|
-
|
47
|
-
```bash
|
48
|
-
# Clone the repository
|
49
|
-
git clone https://github.com/stevegeek/vident.git
|
50
|
-
cd vident
|
51
|
-
|
52
|
-
# Install dependencies
|
53
|
-
bundle install
|
54
|
-
```
|
55
|
-
|
56
|
-
### Running Tests
|
57
|
-
|
58
|
-
To run tests for all gems:
|
59
|
-
|
60
|
-
```bash
|
61
|
-
rake test
|
62
|
-
```
|
63
|
-
|
64
|
-
To run tests for a specific gem:
|
65
|
-
|
66
|
-
```bash
|
67
|
-
rake test:vident-phlex
|
68
|
-
```
|
69
|
-
|
70
|
-
### Building and Installing Gems
|
71
|
-
|
72
|
-
To build all gems:
|
73
|
-
|
74
|
-
```bash
|
75
|
-
rake build
|
76
|
-
```
|
77
|
-
|
78
|
-
To install all gems locally:
|
79
|
-
|
80
|
-
```bash
|
81
|
-
rake install
|
82
|
-
```
|
83
|
-
|
84
|
-
## Contributing
|
85
|
-
|
86
|
-
1. Fork the repository
|
87
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
88
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
89
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
90
|
-
5. Create a new Pull Request
|
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.
|
91
38
|
|
92
|
-
|
93
|
-
|
94
|
-
The gems are available as open source under the terms of the [MIT License](LICENSE.txt).
|
95
|
-
|
96
|
-
---
|
97
|
-
|
98
|
-
# Component Documentation
|
99
|
-
|
100
|
-
---
|
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.
|
101
41
|
|
102
|
-
|
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.
|
103
44
|
|
104
|
-
|
45
|
+
## Installation
|
105
46
|
|
106
|
-
|
47
|
+
Add the core gem and your preferred rendering engine integration to your Gemfile:
|
107
48
|
|
108
49
|
```ruby
|
109
|
-
|
110
|
-
|
111
|
-
```
|
112
|
-
|
113
|
-
For more details see [vident](https://github.com/stevegeek/vident).
|
50
|
+
# Core gem (required)
|
51
|
+
gem "vident"
|
114
52
|
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
+
```
|
118
57
|
|
119
|
-
|
58
|
+
Then run:
|
120
59
|
|
121
60
|
```bash
|
122
|
-
cd test/dummy
|
123
61
|
bundle install
|
124
|
-
rails assets:precompile
|
125
|
-
rails s
|
126
62
|
```
|
127
63
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
### A Vident component example (without Stimulus)
|
132
|
-
|
133
|
-
First is an example component that uses `Vident::Typed::ViewComponent::Base` but no Stimulus features.
|
64
|
+
## Quick Start
|
134
65
|
|
135
|
-
|
136
|
-
|
137
|
-
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:
|
138
67
|
|
139
68
|
```ruby
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
else
|
162
|
-
{ class: "inline-flex items-center justify-center bg-gray-500" }
|
163
|
-
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" }
|
164
90
|
end
|
165
91
|
|
166
|
-
def
|
167
|
-
|
92
|
+
def call
|
93
|
+
root_element do
|
94
|
+
@text
|
95
|
+
end
|
168
96
|
end
|
169
97
|
|
170
|
-
|
98
|
+
private
|
171
99
|
|
172
|
-
def
|
173
|
-
|
100
|
+
def root_element_attributes
|
101
|
+
{
|
102
|
+
element_tag: @url ? :a : :button,
|
103
|
+
html_options: { href: @url }.compact
|
104
|
+
}
|
174
105
|
end
|
175
106
|
|
176
|
-
def
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
"w-12 h-12"
|
184
|
-
when :large
|
185
|
-
"w-14 h-14"
|
186
|
-
when :x_large
|
187
|
-
"sm:w-24 sm:h-24 w-16 h-16"
|
188
|
-
when :xx_large
|
189
|
-
"sm:w-32 sm:h-32 w-24 h-24"
|
190
|
-
else
|
191
|
-
"w-10 h-10"
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def text_size_class
|
196
|
-
case size
|
197
|
-
when :tiny
|
198
|
-
"text-xs"
|
199
|
-
when :small
|
200
|
-
"text-xs"
|
201
|
-
when :medium
|
202
|
-
"text-lg"
|
203
|
-
when :large
|
204
|
-
"sm:text-xl text-lg"
|
205
|
-
when :extra_large
|
206
|
-
"sm:text-2xl text-xl"
|
207
|
-
else
|
208
|
-
"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"
|
209
114
|
end
|
210
115
|
end
|
211
116
|
end
|
212
117
|
```
|
213
118
|
|
214
|
-
```erb
|
215
|
-
<%= render root(
|
216
|
-
element_tag: image_avatar? ? :img : :div,
|
217
|
-
html_options: default_html_options
|
218
|
-
) do %>
|
219
|
-
<% unless image_avatar? %>
|
220
|
-
<span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
|
221
|
-
<% end %>
|
222
|
-
<% end %>
|
223
119
|
|
224
|
-
|
120
|
+
Add the corresponding Stimulus controller would be:
|
225
121
|
|
226
|
-
|
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"
|
227
126
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
+
}
|
242
152
|
```
|
243
153
|
|
154
|
+
Use the component in your views:
|
244
155
|
|
245
|
-
|
156
|
+
```erb
|
157
|
+
<!-- Default clicked count of 0 -->
|
158
|
+
<%= render ButtonComponent.new(text: "Save", style: :primary) %>
|
246
159
|
|
247
|
-
|
248
|
-
|
249
|
-
<span class="text-xs font-medium leading-none text-white">SG</span>
|
250
|
-
</div>
|
251
|
-
```
|
160
|
+
<!-- Pre-set clicked count -->
|
161
|
+
<%= render ButtonComponent.new(text: "Submit", style: :primary, clicked_count: 5) %>
|
252
162
|
|
253
|
-
|
163
|
+
<!-- Link variant -->
|
164
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home", style: :secondary) %>
|
254
165
|
|
255
|
-
|
256
|
-
|
166
|
+
<!-- Override things -->
|
167
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home" classes: "bg-red-900", html_options: {role: "button"}) %>
|
257
168
|
```
|
258
169
|
|
259
|
-
|
260
|
-
|
261
|
-

|
170
|
+
The rendered HTML includes all Stimulus data attributes:
|
262
171
|
|
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:
|
263
201
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
+
```
|
272
220
|
|
273
|
-
|
274
|
-
<%# app/views/home/index.html.erb %>
|
275
|
-
|
276
|
-
<!-- ... -->
|
277
|
-
|
278
|
-
<!-- render the Greeter ViewComponent (that uses Vident) -->
|
279
|
-
<%= render ::GreeterComponent.new(cta: "Hey!", html_options: {class: "my-4"}) do |greeter| %>
|
280
|
-
<%# this component has a slot called `trigger` that renders a `ButtonComponent` (which also uses Vident) %>
|
281
|
-
<% greeter.with_trigger(
|
282
|
-
|
283
|
-
# The button component has attributes that are typed
|
284
|
-
before_clicked: "Greet",
|
285
|
-
after_clicked: "Greeted! Reset?",
|
286
|
-
|
287
|
-
# A stimulus action is added to the button that triggers the `greet` action on the greeter stimulus controller.
|
288
|
-
# This action will be added to any defined on the button component itself
|
289
|
-
actions: [
|
290
|
-
greeter.action(:click, :greet),
|
291
|
-
],
|
292
|
-
|
293
|
-
# We can also override the default button classes of our component, or set other HTML attributes
|
294
|
-
html_options: {
|
295
|
-
class: "bg-red-500 hover:bg-red-700"
|
296
|
-
}
|
297
|
-
) %>
|
298
|
-
<% end %>
|
221
|
+
### Built-in Properties
|
299
222
|
|
300
|
-
|
301
|
-
```
|
223
|
+
Every Vident component includes these properties:
|
302
224
|
|
303
|
-
The
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
data-controller="greeter-component"
|
308
|
-
data-greeter-component-pre-click-class="text-md text-gray-500"
|
309
|
-
data-greeter-component-post-click-class="text-xl text-blue-700"
|
310
|
-
id="greeter-component-1599855-6">
|
311
|
-
<input type="text"
|
312
|
-
data-greeter-component-target="name"
|
313
|
-
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
314
|
-
<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"
|
315
|
-
data-controller="button-component"
|
316
|
-
data-action="click->greeter-component#greet button-component#changeMessage"
|
317
|
-
data-button-component-after-clicked-message="Greeted! Reset?"
|
318
|
-
data-button-component-before-clicked-message="Greet"
|
319
|
-
id="button-component-7799479-7">Hey!</button>
|
320
|
-
<!-- you can also use the `target_tag` helper to render targets -->
|
321
|
-
<span class="ml-4 text-md text-gray-500"
|
322
|
-
data-greeter-component-target="output">
|
323
|
-
...
|
324
|
-
</span>
|
325
|
-
</div>
|
326
|
-
```
|
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
|
327
229
|
|
328
|
-
|
230
|
+
### Root Element Rendering
|
329
231
|
|
330
|
-
The
|
232
|
+
The `root_element` helper method renders your component's root element with all configured attributes:
|
331
233
|
|
332
234
|
```ruby
|
333
|
-
#
|
235
|
+
# In your component class
|
236
|
+
def element_classes
|
237
|
+
["card", featured? ? "card-featured" : nil]
|
238
|
+
end
|
334
239
|
|
335
|
-
|
336
|
-
|
240
|
+
private
|
241
|
+
|
242
|
+
def root_element_attributes
|
243
|
+
{
|
244
|
+
html_options: { role: "article", "aria-label": title }
|
245
|
+
}
|
337
246
|
end
|
338
247
|
```
|
339
248
|
|
340
249
|
```erb
|
341
|
-
<%#
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
<%= render root named_classes: {
|
347
|
-
pre_click: "text-md text-gray-500", # named classes are exposed to Stimulus as `data-<controller>-<n>-class` attributes
|
348
|
-
post_click: "text-xl text-blue-700",
|
349
|
-
html_options: {class: "py-2"}
|
350
|
-
} do |greeter| %>
|
351
|
-
|
352
|
-
<%# `greeter` is the root element and exposes methods to generate stimulus targets and actions %>
|
353
|
-
<input type="text"
|
354
|
-
<%= greeter.as_target(:name) %>
|
355
|
-
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
356
|
-
|
357
|
-
<%# Render the slot %>
|
358
|
-
<%= trigger %>
|
359
|
-
|
360
|
-
<%# you can also use the `target_tag` helper to render targets %>
|
361
|
-
<%= greeter.target_tag(
|
362
|
-
:span,
|
363
|
-
:output,
|
364
|
-
# Stimulus named classes can be referenced to set class attributes at render time
|
365
|
-
class: "ml-4 #{greeter.named_classes(:pre_click)}"
|
366
|
-
) do %>
|
367
|
-
...
|
368
|
-
<% end %>
|
250
|
+
<%# In your template %>
|
251
|
+
<%= root_element do %>
|
252
|
+
<h2><%= title %></h2>
|
253
|
+
<p><%= subtitle %></p>
|
369
254
|
<% end %>
|
370
|
-
|
371
255
|
```
|
372
256
|
|
373
|
-
|
374
|
-
// app/components/greeter_component_controller.js
|
257
|
+
## Component DSL
|
375
258
|
|
376
|
-
|
259
|
+
### ViewComponent Integration
|
377
260
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
export default class extends Controller {
|
383
|
-
static targets = [ "name", "output" ]
|
384
|
-
static classes = [ "preClick", "postClick" ]
|
385
|
-
|
386
|
-
greet() {
|
387
|
-
this.clicked = !this.clicked;
|
388
|
-
this.outputTarget.classList.toggle(this.preClickClasses, !this.clicked);
|
389
|
-
this.outputTarget.classList.toggle(this.postClickClasses, this.clicked);
|
390
|
-
|
391
|
-
if (this.clicked)
|
392
|
-
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
|
393
|
-
else
|
394
|
-
this.clear();
|
395
|
-
}
|
261
|
+
```ruby
|
262
|
+
class MyComponent < Vident::ViewComponent::Base
|
263
|
+
# Component code
|
264
|
+
end
|
396
265
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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
|
402
274
|
```
|
403
275
|
|
404
|
-
|
276
|
+
### Phlex Integration
|
405
277
|
|
406
278
|
```ruby
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
attribute :after_clicked, String, default: "Greeted!"
|
412
|
-
attribute :before_clicked, String, allow_nil: false
|
413
|
-
|
414
|
-
# This example is a templateless ViewComponent.
|
415
|
-
def call
|
416
|
-
# The button is rendered as a <button> tag with an click action on its own controller.
|
417
|
-
render root(
|
418
|
-
element_tag: :button,
|
419
|
-
|
420
|
-
# We can define actions as arrays of Symbols, or pass manually manually crafted strings.
|
421
|
-
# Here we specify the action name only, implying an action on the current components controller
|
422
|
-
# and the default event type of `click`.
|
423
|
-
actions: [:change_message],
|
424
|
-
# Alternatively: [:click, :change_message] or ["click", "changeMessage"] or even "click->button-component#changeMessage"
|
425
|
-
|
426
|
-
# A couple of data values are also set which will be available to the controller
|
427
|
-
data_maps: [{after_clicked_message: after_clicked, before_clicked_message: before_clicked}],
|
428
|
-
|
429
|
-
# The <button> tag has a default styling set directly on it. Note that
|
430
|
-
# if not using utility classes, you can style the component using its
|
431
|
-
# canonical class name (which is equal to the component's stimulus identifier),
|
432
|
-
# in this case `button-component`.
|
433
|
-
html_options: {class: "ml-4 whitespace-no-wrap bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"}
|
434
|
-
) do
|
435
|
-
@before_clicked
|
279
|
+
class MyComponent < Vident::Phlex::HTML
|
280
|
+
def view_template
|
281
|
+
root do
|
282
|
+
h1 { "Hello from Phlex!" }
|
436
283
|
end
|
437
284
|
end
|
438
285
|
end
|
439
286
|
```
|
440
287
|
|
441
|
-
|
442
|
-
// app/components/button_component_controller.js
|
443
|
-
|
444
|
-
import { Controller } from "@hotwired/stimulus"
|
445
|
-
|
446
|
-
export default class extends Controller {
|
447
|
-
// The action is in camelCase.
|
448
|
-
changeMessage() {
|
449
|
-
this.clicked = !this.clicked;
|
450
|
-
// The data attributes have their naming convention converted to camelCase.
|
451
|
-
this.element.textContent = this.clicked ? this.data.get("afterClickedMessage") : this.data.get("beforeClickedMessage");
|
452
|
-
}
|
453
|
-
}
|
288
|
+
## Stimulus Integration
|
454
289
|
|
455
|
-
|
290
|
+
Vident provides comprehensive Stimulus.js integration to add interactivity to your components.
|
456
291
|
|
457
|
-
###
|
458
|
-
How to use my plugin.
|
292
|
+
### Declarative Stimulus DSL
|
459
293
|
|
460
|
-
|
461
|
-
Add this line to your application's Gemfile:
|
294
|
+
Use the `stimulus` block for clean, declarative configuration:
|
462
295
|
|
463
296
|
```ruby
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
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
|
475
323
|
```
|
476
324
|
|
477
|
-
|
325
|
+
### Dynamic Values and Classes with Procs
|
478
326
|
|
327
|
+
The Stimulus DSL supports dynamic values and classes using procs or lambdas that are evaluated in the component instance context:
|
479
328
|
|
480
|
-
|
329
|
+
```ruby
|
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
|
363
|
+
end
|
364
|
+
```
|
481
365
|
|
482
|
-
|
366
|
+
Procs have access to instance variables, component methods, and Rails helpers.
|
483
367
|
|
484
|
-
|
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:
|
485
369
|
|
486
370
|
```ruby
|
487
|
-
|
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)
|
488
381
|
end
|
489
382
|
```
|
490
383
|
|
491
|
-
|
492
|
-
|
493
|
-
### Examples
|
384
|
+
### Scoped Custom Events
|
494
385
|
|
495
|
-
|
386
|
+
Vident provides helper methods to generate scoped event names for dispatching custom events that are unique to your component:
|
496
387
|
|
497
|
-
|
388
|
+
```ruby
|
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]
|
393
|
+
end
|
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)
|
402
|
+
end
|
403
|
+
end
|
498
404
|
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
rails assets:precompile
|
503
|
-
rails s
|
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"
|
504
408
|
```
|
505
409
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
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
|
510
414
|
|
511
|
-
|
415
|
+
### Manual Stimulus Configuration
|
512
416
|
|
513
|
-
|
417
|
+
For more control, configure Stimulus attributes manually:
|
514
418
|
|
515
419
|
```ruby
|
516
|
-
class
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
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
|
+
}
|
439
|
+
end
|
440
|
+
end
|
441
|
+
```
|
522
442
|
|
523
|
-
|
524
|
-
attribute :initials, allow_nil: false
|
443
|
+
or you can use tag helpers to generate HTML with Stimulus attributes:
|
525
444
|
|
526
|
-
|
445
|
+
```erb
|
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 %>
|
449
|
+
<% end %>
|
450
|
+
<%= content_tag(:span, class: "...", data: {**greeter.stimulus_target(:output)}) %>
|
527
451
|
|
528
|
-
|
452
|
+
<%# OR use the vident tag helper %>
|
529
453
|
|
530
|
-
|
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: "...") %>
|
459
|
+
```
|
531
460
|
|
532
|
-
|
461
|
+
or in your Phlex templates:
|
533
462
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
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 %( ... )
|
540
469
|
end
|
470
|
+
end
|
471
|
+
```
|
541
472
|
|
542
|
-
|
543
|
-
[size_classes, shape_class, border? ? "border" : ""]
|
544
|
-
end
|
473
|
+
or directly in the ViewComponent template (eg with ERB) using the `as_stimulus_*` helpers
|
545
474
|
|
546
|
-
|
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>
|
485
|
+
```
|
547
486
|
|
548
|
-
def shape_class
|
549
|
-
(shape == :circle) ? "rounded-full" : "rounded-md"
|
550
|
-
end
|
551
487
|
|
552
|
-
|
553
|
-
case size
|
554
|
-
when :tiny
|
555
|
-
"w-6 h-6"
|
556
|
-
when :small
|
557
|
-
"w-8 h-8"
|
558
|
-
when :medium
|
559
|
-
"w-12 h-12"
|
560
|
-
when :large
|
561
|
-
"w-14 h-14"
|
562
|
-
when :x_large
|
563
|
-
"sm:w-24 sm:h-24 w-16 h-16"
|
564
|
-
when :xx_large
|
565
|
-
"sm:w-32 sm:h-32 w-24 h-24"
|
566
|
-
else
|
567
|
-
"w-10 h-10"
|
568
|
-
end
|
569
|
-
end
|
488
|
+
### Stimulus Helpers in Templates
|
570
489
|
|
571
|
-
|
572
|
-
case size
|
573
|
-
when :tiny
|
574
|
-
"text-xs"
|
575
|
-
when :small
|
576
|
-
"text-xs"
|
577
|
-
when :medium
|
578
|
-
"text-lg"
|
579
|
-
when :large
|
580
|
-
"sm:text-xl text-lg"
|
581
|
-
when :extra_large
|
582
|
-
"sm:text-2xl text-xl"
|
583
|
-
else
|
584
|
-
"text-medium"
|
585
|
-
end
|
586
|
-
end
|
587
|
-
end
|
588
|
-
```
|
490
|
+
Vident provides helper methods for generating Stimulus attributes:
|
589
491
|
|
590
492
|
```erb
|
591
|
-
<%= render root
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
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
|
597
507
|
<% end %>
|
508
|
+
|
509
|
+
<!-- Multiple targets/actions -->
|
510
|
+
<input <%= component.as_targets(:input, :field) %>
|
511
|
+
<%= component.as_actions([:input, :validate], [:change, :save]) %>>
|
598
512
|
<% end %>
|
599
513
|
```
|
600
514
|
|
601
|
-
|
515
|
+
### Stimulus Outlets
|
602
516
|
|
603
|
-
|
604
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", initials: "AB" size: :large) %>
|
605
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", html_options: {alt: "My alt text", class: "object-scale-down"}) %>
|
606
|
-
<%= render AvatarComponent.new(initials: "SG", size: :small) %>
|
607
|
-
<%= render AvatarComponent.new(initials: "SG", size: :large, html_options: {class: "border-2 border-red-600"}) %>
|
608
|
-
```
|
517
|
+
Connect components via Stimulus outlets:
|
609
518
|
|
610
|
-
The following is rendered when used `render AvatarComponent.new(initials: "SG", size: :small, border: true)`:
|
611
519
|
|
612
|
-
```html
|
613
|
-
<div class="avatar-component w-8 h-8 rounded-full border inline-flex items-center justify-center bg-gray-500" id="avatar-component-9790427-12">
|
614
|
-
<span class="text-xs font-medium leading-none text-white">SG</span>
|
615
|
-
</div>
|
616
|
-
```
|
617
520
|
|
618
|
-
The following is rendered when used `render AvatarComponent.new(url: "https://i.pravatar.cc/300", initials: "AB", html_options: {alt: "My alt text", class: "block"})`:
|
619
521
|
|
620
|
-
|
621
|
-
<img src="https://i.pravatar.cc/300" alt="My alt text" class="avatar-component w-10 h-10 rounded-full object-contain block" id="avatar-component-7083941-11">
|
622
|
-
```
|
522
|
+
### Stimulus Controller Naming
|
623
523
|
|
624
|
-
|
524
|
+
Vident automatically generates Stimulus controller names based on your component class:
|
625
525
|
|
626
|
-
|
526
|
+
- `ButtonComponent` → `button-component`
|
527
|
+
- `Admin::UserCardComponent` → `admin--user-card-component`
|
528
|
+
- `MyApp::WidgetComponent` → `my-app--widget-component`
|
627
529
|
|
628
|
-
###
|
629
|
-
How to use my plugin.
|
530
|
+
### Working with Child Components
|
630
531
|
|
631
|
-
|
632
|
-
Add this line to your application's Gemfile:
|
532
|
+
Setting Stimulus configuration between parent and child components:
|
633
533
|
|
634
534
|
```ruby
|
635
|
-
|
535
|
+
class ParentComponent < Vident::ViewComponent::Base
|
536
|
+
renders_one :a_nested_component, ButtonComponent
|
537
|
+
|
538
|
+
stimulus do
|
539
|
+
actions :handleTrigger
|
540
|
+
end
|
541
|
+
end
|
636
542
|
```
|
637
543
|
|
638
|
-
|
639
|
-
|
640
|
-
|
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 %>
|
641
553
|
```
|
642
554
|
|
643
|
-
|
644
|
-
```bash
|
645
|
-
$ gem install vident-view_component
|
646
|
-
```
|
555
|
+
This creates a nested component that once clicked triggers the parent components `handleTrigger` action.
|
647
556
|
|
648
|
-
##
|
557
|
+
## Other Features
|
649
558
|
|
650
|
-
|
559
|
+
### Custom Element Tags
|
651
560
|
|
652
|
-
|
561
|
+
Change the root element tag dynamically:
|
653
562
|
|
654
563
|
```ruby
|
655
|
-
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
|
656
578
|
end
|
657
579
|
```
|
658
580
|
|
659
|
-
|
581
|
+
### Intelligent Class Management
|
660
582
|
|
661
|
-
|
662
|
-
How to use my plugin.
|
663
|
-
|
664
|
-
### Installation
|
665
|
-
Add this line to your application's Gemfile:
|
583
|
+
Vident intelligently merges CSS classes from multiple sources:
|
666
584
|
|
667
585
|
```ruby
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
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
|
674
604
|
```
|
675
605
|
|
676
|
-
|
677
|
-
```
|
678
|
-
|
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" -->
|
679
614
|
```
|
680
615
|
|
681
|
-
|
682
|
-
|
683
|
-
## gem: vident-tailwind
|
684
|
-
|
685
|
-
# Vident::Tailwind
|
686
|
-
Short description and motivation.
|
616
|
+
### Tailwind CSS Integration
|
687
617
|
|
688
|
-
|
689
|
-
How to use my plugin.
|
690
|
-
|
691
|
-
### Installation
|
692
|
-
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:
|
693
619
|
|
694
620
|
```ruby
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
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
|
706
639
|
```
|
707
640
|
|
708
|
-
|
709
|
-
|
710
|
-
## gem: vident-typed-minitest
|
711
|
-
|
712
|
-
# Vident::Typed::Minitest
|
713
|
-
Short description and motivation.
|
641
|
+
### Component Caching
|
714
642
|
|
715
|
-
|
716
|
-
How to use my plugin.
|
717
|
-
|
718
|
-
### Installation
|
719
|
-
Add this line to your application's Gemfile:
|
643
|
+
Enable fragment caching for expensive components:
|
720
644
|
|
721
645
|
```ruby
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
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
|
728
653
|
```
|
729
654
|
|
730
|
-
|
731
|
-
|
732
|
-
|
655
|
+
```erb
|
656
|
+
<% cache component.cache_key do %>
|
657
|
+
<%= render component %>
|
658
|
+
<% end %>
|
733
659
|
```
|
734
660
|
|
735
|
-
---
|
736
|
-
|
737
|
-
## gem: vident-typed-phlex
|
738
|
-
|
739
|
-
# Vident::Typed::Phlex
|
740
|
-
|
741
|
-
Adds typed attributes to Vident Phlex based components.
|
742
|
-
|
743
|
-
```ruby
|
744
|
-
class ApplicationComponent < ::Vident::Typed::Phlex::HTML
|
745
|
-
end
|
746
|
-
```
|
747
661
|
|
748
|
-
|
662
|
+
## Testing
|
749
663
|
|
750
|
-
|
751
|
-
How to use my plugin.
|
664
|
+
Vident components work seamlessly with testing frameworks that support ViewComponent or Phlex.
|
752
665
|
|
753
|
-
|
754
|
-
Add this line to your application's Gemfile:
|
666
|
+
## Development
|
755
667
|
|
756
|
-
|
757
|
-
gem "vident-typed-phlex"
|
758
|
-
```
|
668
|
+
### Running Tests
|
759
669
|
|
760
|
-
And then execute:
|
761
670
|
```bash
|
762
|
-
|
671
|
+
# Run all tests
|
672
|
+
bin/rails test
|
763
673
|
```
|
764
674
|
|
765
|
-
|
766
|
-
```bash
|
767
|
-
$ gem install vident-typed-phlex
|
768
|
-
```
|
675
|
+
### Local Development
|
769
676
|
|
770
|
-
|
677
|
+
```bash
|
678
|
+
# Clone the repository
|
679
|
+
git clone https://github.com/stevegeek/vident.git
|
680
|
+
cd vident
|
771
681
|
|
682
|
+
# Install dependencies
|
683
|
+
bundle install
|
772
684
|
|
773
|
-
|
685
|
+
# Run the dummy app
|
686
|
+
cd test/dummy
|
687
|
+
rails s
|
688
|
+
```
|
774
689
|
|
775
|
-
|
776
|
-
Short description and motivation.
|
690
|
+
## Contributing
|
777
691
|
|
778
|
-
|
779
|
-
|
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
|
780
698
|
|
781
|
-
|
782
|
-
Add this line to your application's Gemfile:
|
699
|
+
## License
|
783
700
|
|
784
|
-
|
785
|
-
gem "vident-typed"
|
786
|
-
```
|
701
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
787
702
|
|
788
|
-
|
789
|
-
```bash
|
790
|
-
$ bundle
|
791
|
-
```
|
703
|
+
## Credits
|
792
704
|
|
793
|
-
|
794
|
-
```bash
|
795
|
-
$ gem install vident-typed
|
796
|
-
```
|
705
|
+
Vident is maintained by [Stephen Ierodiaconou](https://github.com/stevegeek).
|
797
706
|
|
798
|
-
|
707
|
+
Special thanks to the ViewComponent and Phlex communities for their excellent component frameworks that Vident builds upon.
|