vident-phlex 0.13.0 → 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 +31 -1
- data/README.md +522 -681
- data/lib/vident/phlex/html.rb +49 -6
- data/lib/vident/phlex.rb +0 -2
- metadata +10 -12
- data/lib/vident/phlex/core.rb +0 -29
- data/lib/vident/phlex/root_component.rb +0 -55
data/README.md
CHANGED
@@ -1,866 +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
|
-
- `vident-better_html`: Better HTML integration for Vident
|
9
|
-
- `vident-phlex`: Phlex integration for Vident
|
10
|
-
- `vident-tailwind`: Tailwind CSS integration for Vident
|
11
|
-
- `vident-typed`: Type system for Vident components
|
12
|
-
- `vident-typed-minitest`: Minitest integration for typed Vident components
|
13
|
-
- `vident-typed-phlex`: Phlex integration for typed Vident components
|
14
|
-
- `vident-typed-view_component`: ViewComponent integration for typed Vident
|
15
|
-
- `vident-view_component`: ViewComponent integration for Vident
|
16
|
-
- `vident-view_component-caching`: Caching support for Vident ViewComponents
|
8
|
+
## Table of Contents
|
17
9
|
|
18
|
-
|
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)
|
19
19
|
|
20
|
-
|
20
|
+
## Introduction
|
21
21
|
|
22
|
-
|
23
|
-
vident/
|
24
|
-
├── lib/ # All gem code
|
25
|
-
│ ├── vident.rb # Core entry point
|
26
|
-
│ ├── vident-phlex.rb # Gem entry points
|
27
|
-
│ ├── vident-better_html.rb
|
28
|
-
│ ├── vident/ # Shared code
|
29
|
-
│ ├── base.rb
|
30
|
-
│ ├── phlex/ # Phlex integration
|
31
|
-
│ ├── better_html/ # Better HTML integration
|
32
|
-
│ └── ...
|
33
|
-
├── test/ # All tests
|
34
|
-
│ ├── vident/ # Core tests
|
35
|
-
│ ├── vident-phlex/ # Tests for each gem
|
36
|
-
│ └── ...
|
37
|
-
├── docs/ # Documentation
|
38
|
-
├── examples/ # Examples
|
39
|
-
├── vident.gemspec # Gemspec for core gem
|
40
|
-
├── vident-phlex.gemspec # Gemspecs for each gem
|
41
|
-
└── ...
|
42
|
-
```
|
43
|
-
|
44
|
-
## Development
|
45
|
-
|
46
|
-
### Setting Up Development Environment
|
22
|
+
Vident is a collection of gems that enhance Rails view components with:
|
47
23
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
52
30
|
|
53
|
-
|
54
|
-
bundle install
|
55
|
-
```
|
31
|
+
### Why Vident?
|
56
32
|
|
57
|
-
|
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).
|
58
35
|
|
59
|
-
|
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.
|
60
38
|
|
61
|
-
|
62
|
-
|
63
|
-
```
|
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.
|
64
41
|
|
65
|
-
|
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.
|
66
44
|
|
67
|
-
|
68
|
-
rake test:vident-phlex
|
69
|
-
```
|
45
|
+
## Installation
|
70
46
|
|
71
|
-
|
47
|
+
Add the core gem and your preferred rendering engine integration to your Gemfile:
|
72
48
|
|
73
|
-
|
49
|
+
```ruby
|
50
|
+
# Core gem (required)
|
51
|
+
gem "vident"
|
74
52
|
|
75
|
-
|
76
|
-
|
53
|
+
# Choose your rendering engine (at least one required)
|
54
|
+
gem "vident-view_component" # For ViewComponent support
|
55
|
+
gem "vident-phlex" # For Phlex support
|
77
56
|
```
|
78
57
|
|
79
|
-
|
58
|
+
Then run:
|
80
59
|
|
81
60
|
```bash
|
82
|
-
|
61
|
+
bundle install
|
83
62
|
```
|
84
63
|
|
85
|
-
##
|
86
|
-
|
87
|
-
1. Fork the repository
|
88
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
89
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
90
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
91
|
-
5. Create a new Pull Request
|
92
|
-
|
93
|
-
## License
|
64
|
+
## Quick Start
|
94
65
|
|
95
|
-
|
66
|
+
Here's a simple example of a Vident component using ViewComponent:
|
96
67
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
68
|
+
```ruby
|
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" }
|
90
|
+
end
|
105
91
|
|
106
|
-
|
107
|
-
|
92
|
+
def call
|
93
|
+
root_element do
|
94
|
+
@text
|
95
|
+
end
|
96
|
+
end
|
108
97
|
|
109
|
-
|
110
|
-
BetterHtml.config = BetterHtml::Config.new(YAML.load(File.read(".better-html.yml")))
|
98
|
+
private
|
111
99
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
alias_method :to_s_without_html_safe, :to_s
|
100
|
+
def root_element_attributes
|
101
|
+
{
|
102
|
+
element_tag: @url ? :a : :button,
|
103
|
+
html_options: { href: @url }.compact
|
104
|
+
}
|
105
|
+
end
|
119
106
|
|
120
|
-
def
|
121
|
-
|
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"
|
114
|
+
end
|
122
115
|
end
|
123
116
|
end
|
124
117
|
```
|
125
118
|
|
126
|
-
### Installation
|
127
|
-
Add this line to your application's Gemfile:
|
128
119
|
|
129
|
-
|
130
|
-
gem "vident-better_html"
|
131
|
-
```
|
120
|
+
Add the corresponding Stimulus controller would be:
|
132
121
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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"
|
137
126
|
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
+
}
|
141
152
|
```
|
142
153
|
|
143
|
-
|
154
|
+
Use the component in your views:
|
144
155
|
|
145
|
-
|
156
|
+
```erb
|
157
|
+
<!-- Default clicked count of 0 -->
|
158
|
+
<%= render ButtonComponent.new(text: "Save", style: :primary) %>
|
146
159
|
|
147
|
-
|
160
|
+
<!-- Pre-set clicked count -->
|
161
|
+
<%= render ButtonComponent.new(text: "Submit", style: :primary, clicked_count: 5) %>
|
148
162
|
|
149
|
-
|
163
|
+
<!-- Link variant -->
|
164
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home", style: :secondary) %>
|
150
165
|
|
151
|
-
|
152
|
-
|
153
|
-
end
|
166
|
+
<!-- Override things -->
|
167
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home" classes: "bg-red-900", html_options: {role: "button"}) %>
|
154
168
|
```
|
155
169
|
|
156
|
-
|
157
|
-
|
158
|
-
### Usage
|
159
|
-
How to use my plugin.
|
170
|
+
The rendered HTML includes all Stimulus data attributes:
|
160
171
|
|
161
|
-
|
162
|
-
|
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:
|
163
201
|
|
164
202
|
```ruby
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
176
219
|
```
|
177
220
|
|
178
|
-
|
221
|
+
### Built-in Properties
|
179
222
|
|
180
|
-
|
223
|
+
Every Vident component includes these properties:
|
181
224
|
|
182
|
-
|
183
|
-
|
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
|
184
229
|
|
185
|
-
###
|
186
|
-
How to use my plugin.
|
230
|
+
### Root Element Rendering
|
187
231
|
|
188
|
-
|
189
|
-
Add this line to your application's Gemfile:
|
232
|
+
The `root_element` helper method renders your component's root element with all configured attributes:
|
190
233
|
|
191
234
|
```ruby
|
192
|
-
|
193
|
-
|
235
|
+
# In your component class
|
236
|
+
def element_classes
|
237
|
+
["card", featured? ? "card-featured" : nil]
|
238
|
+
end
|
194
239
|
|
195
|
-
|
196
|
-
```bash
|
197
|
-
$ bundle
|
198
|
-
```
|
240
|
+
private
|
199
241
|
|
200
|
-
|
201
|
-
|
202
|
-
|
242
|
+
def root_element_attributes
|
243
|
+
{
|
244
|
+
html_options: { role: "article", "aria-label": title }
|
245
|
+
}
|
246
|
+
end
|
203
247
|
```
|
204
248
|
|
205
|
-
|
249
|
+
```erb
|
250
|
+
<%# In your template %>
|
251
|
+
<%= root_element do %>
|
252
|
+
<h2><%= title %></h2>
|
253
|
+
<p><%= subtitle %></p>
|
254
|
+
<% end %>
|
255
|
+
```
|
206
256
|
|
207
|
-
##
|
257
|
+
## Component DSL
|
208
258
|
|
209
|
-
|
210
|
-
Short description and motivation.
|
259
|
+
### ViewComponent Integration
|
211
260
|
|
212
|
-
|
213
|
-
|
261
|
+
```ruby
|
262
|
+
class MyComponent < Vident::ViewComponent::Base
|
263
|
+
# Component code
|
264
|
+
end
|
214
265
|
|
215
|
-
|
216
|
-
|
266
|
+
# Or with an application base class
|
267
|
+
class ApplicationComponent < Vident::ViewComponent::Base
|
268
|
+
# Shared configuration
|
269
|
+
end
|
217
270
|
|
218
|
-
|
219
|
-
|
271
|
+
class MyComponent < ApplicationComponent
|
272
|
+
# Component code
|
273
|
+
end
|
220
274
|
```
|
221
275
|
|
222
|
-
|
223
|
-
```bash
|
224
|
-
$ bundle
|
225
|
-
```
|
276
|
+
### Phlex Integration
|
226
277
|
|
227
|
-
|
228
|
-
|
229
|
-
|
278
|
+
```ruby
|
279
|
+
class MyComponent < Vident::Phlex::HTML
|
280
|
+
def view_template
|
281
|
+
root do
|
282
|
+
h1 { "Hello from Phlex!" }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
230
286
|
```
|
231
287
|
|
232
|
-
|
288
|
+
## Stimulus Integration
|
233
289
|
|
234
|
-
|
290
|
+
Vident provides comprehensive Stimulus.js integration to add interactivity to your components.
|
235
291
|
|
236
|
-
|
292
|
+
### Declarative Stimulus DSL
|
237
293
|
|
238
|
-
|
294
|
+
Use the `stimulus` block for clean, declarative configuration:
|
239
295
|
|
240
296
|
```ruby
|
241
|
-
class
|
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
|
242
322
|
end
|
243
323
|
```
|
244
324
|
|
245
|
-
|
246
|
-
|
247
|
-
### Usage
|
248
|
-
How to use my plugin.
|
325
|
+
### Dynamic Values and Classes with Procs
|
249
326
|
|
250
|
-
|
251
|
-
Add this line to your application's Gemfile:
|
327
|
+
The Stimulus DSL supports dynamic values and classes using procs or lambdas that are evaluated in the component instance context:
|
252
328
|
|
253
329
|
```ruby
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
265
364
|
```
|
266
365
|
|
267
|
-
|
268
|
-
|
269
|
-
## gem: vident-typed-view_component
|
270
|
-
|
271
|
-
# Vident::Typed::ViewComponent
|
366
|
+
Procs have access to instance variables, component methods, and Rails helpers.
|
272
367
|
|
273
|
-
|
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:
|
274
369
|
|
275
370
|
```ruby
|
276
|
-
|
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)
|
277
381
|
end
|
278
382
|
```
|
279
383
|
|
280
|
-
|
281
|
-
|
282
|
-
### Examples
|
384
|
+
### Scoped Custom Events
|
283
385
|
|
284
|
-
|
386
|
+
Vident provides helper methods to generate scoped event names for dispatching custom events that are unique to your component:
|
285
387
|
|
286
|
-
|
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
|
287
404
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
rails assets:precompile
|
292
|
-
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"
|
293
408
|
```
|
294
409
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
First is an example component that uses `Vident::Typed::ViewComponent::Base` but no Stimulus features.
|
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
|
301
414
|
|
302
|
-
|
415
|
+
### Manual Stimulus Configuration
|
303
416
|
|
304
|
-
|
417
|
+
For more control, configure Stimulus attributes manually:
|
305
418
|
|
306
419
|
```ruby
|
307
|
-
class
|
308
|
-
include ::Vident::Tailwind
|
309
|
-
include ::Vident::Caching
|
310
|
-
|
311
|
-
no_stimulus_controller
|
312
|
-
with_cache_key :attributes
|
313
|
-
|
314
|
-
attribute :url, String, allow_nil: true, allow_blank: false
|
315
|
-
attribute :initials, String, allow_blank: false
|
316
|
-
|
317
|
-
attribute :shape, Symbol, in: %i[circle square], default: :circle
|
318
|
-
|
319
|
-
attribute :border, :boolean, default: false
|
320
|
-
|
321
|
-
attribute :size, Symbol, in: %i[tiny small normal medium large x_large xx_large], default: :normal
|
322
|
-
|
420
|
+
class CustomComponent < Vident::ViewComponent::Base
|
323
421
|
private
|
324
|
-
|
325
|
-
def
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
end
|
342
|
-
|
343
|
-
def size_classes
|
344
|
-
case size
|
345
|
-
when :tiny
|
346
|
-
"w-6 h-6"
|
347
|
-
when :small
|
348
|
-
"w-8 h-8"
|
349
|
-
when :medium
|
350
|
-
"w-12 h-12"
|
351
|
-
when :large
|
352
|
-
"w-14 h-14"
|
353
|
-
when :x_large
|
354
|
-
"sm:w-24 sm:h-24 w-16 h-16"
|
355
|
-
when :xx_large
|
356
|
-
"sm:w-32 sm:h-32 w-24 h-24"
|
357
|
-
else
|
358
|
-
"w-10 h-10"
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
def text_size_class
|
363
|
-
case size
|
364
|
-
when :tiny
|
365
|
-
"text-xs"
|
366
|
-
when :small
|
367
|
-
"text-xs"
|
368
|
-
when :medium
|
369
|
-
"text-lg"
|
370
|
-
when :large
|
371
|
-
"sm:text-xl text-lg"
|
372
|
-
when :extra_large
|
373
|
-
"sm:text-2xl text-xl"
|
374
|
-
else
|
375
|
-
"text-medium"
|
376
|
-
end
|
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
|
+
}
|
377
439
|
end
|
378
440
|
end
|
379
441
|
```
|
380
442
|
|
381
|
-
|
382
|
-
<%= render root(
|
383
|
-
element_tag: image_avatar? ? :img : :div,
|
384
|
-
html_options: default_html_options
|
385
|
-
) do %>
|
386
|
-
<% unless image_avatar? %>
|
387
|
-
<span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
|
388
|
-
<% end %>
|
389
|
-
<% end %>
|
390
|
-
|
391
|
-
```
|
392
|
-
|
393
|
-
Example usages:
|
443
|
+
or you can use tag helpers to generate HTML with Stimulus attributes:
|
394
444
|
|
395
445
|
```erb
|
396
|
-
|
397
|
-
<%=
|
398
|
-
<%=
|
399
|
-
|
400
|
-
<%=
|
401
|
-
|
402
|
-
<!-- These will raise an error -->
|
403
|
-
<!-- missing initals -->
|
404
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", size: :large) %>
|
405
|
-
<!-- initials blank -->
|
406
|
-
<%= render AvatarComponent.new(initials: "", size: :large) %>
|
407
|
-
<!-- invalid size -->
|
408
|
-
<%= render AvatarComponent.new(initials: "SG", size: :foo_bar) %>
|
409
|
-
```
|
410
|
-
|
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)}) %>
|
411
451
|
|
412
|
-
|
452
|
+
<%# OR use the vident tag helper %>
|
413
453
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
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: "...") %>
|
418
459
|
```
|
419
460
|
|
420
|
-
|
461
|
+
or in your Phlex templates:
|
421
462
|
|
422
|
-
```
|
423
|
-
|
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
|
424
471
|
```
|
425
472
|
|
426
|
-
|
427
|
-
|
428
|
-

|
429
|
-
|
430
|
-
|
431
|
-
### Another ViewComponent + Vident example with Stimulus
|
432
|
-
|
433
|
-
Consider the following ERB that might be part of an application's views. The app uses `ViewComponent`, `Stimulus` and `Vident`.
|
434
|
-
|
435
|
-
The Greeter is a component that displays a text input and a button. When the button is clicked, the text input's value is
|
436
|
-
used to greet the user. At the same time the button changes to be a 'reset' button, which resets the greeting when clicked again.
|
437
|
-
|
438
|
-

|
473
|
+
or directly in the ViewComponent template (eg with ERB) using the `as_stimulus_*` helpers
|
439
474
|
|
440
475
|
```erb
|
441
|
-
<%#
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
# The button component has attributes that are typed
|
451
|
-
before_clicked: "Greet",
|
452
|
-
after_clicked: "Greeted! Reset?",
|
453
|
-
|
454
|
-
# A stimulus action is added to the button that triggers the `greet` action on the greeter stimulus controller.
|
455
|
-
# This action will be added to any defined on the button component itself
|
456
|
-
actions: [
|
457
|
-
greeter.action(:click, :greet),
|
458
|
-
],
|
459
|
-
|
460
|
-
# We can also override the default button classes of our component, or set other HTML attributes
|
461
|
-
html_options: {
|
462
|
-
class: "bg-red-500 hover:bg-red-700"
|
463
|
-
}
|
464
|
-
) %>
|
465
|
-
<% end %>
|
466
|
-
|
467
|
-
<!-- ... -->
|
468
|
-
```
|
469
|
-
|
470
|
-
The output HTML of the above, using Vident, is:
|
471
|
-
|
472
|
-
```html
|
473
|
-
<div class="greeter-component py-2 my-4"
|
474
|
-
data-controller="greeter-component"
|
475
|
-
data-greeter-component-pre-click-class="text-md text-gray-500"
|
476
|
-
data-greeter-component-post-click-class="text-xl text-blue-700"
|
477
|
-
id="greeter-component-1599855-6">
|
478
|
-
<input type="text"
|
479
|
-
data-greeter-component-target="name"
|
480
|
-
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
481
|
-
<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"
|
482
|
-
data-controller="button-component"
|
483
|
-
data-action="click->greeter-component#greet button-component#changeMessage"
|
484
|
-
data-button-component-after-clicked-message="Greeted! Reset?"
|
485
|
-
data-button-component-before-clicked-message="Greet"
|
486
|
-
id="button-component-7799479-7">Hey!</button>
|
487
|
-
<!-- you can also use the `target_tag` helper to render targets -->
|
488
|
-
<span class="ml-4 text-md text-gray-500"
|
489
|
-
data-greeter-component-target="output">
|
490
|
-
...
|
491
|
-
</span>
|
492
|
-
</div>
|
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>
|
493
485
|
```
|
494
486
|
|
495
|
-
Let's look at the components in more detail.
|
496
487
|
|
497
|
-
|
498
|
-
|
499
|
-
```ruby
|
500
|
-
# app/components/greeter_component.rb
|
488
|
+
### Stimulus Helpers in Templates
|
501
489
|
|
502
|
-
|
503
|
-
renders_one :trigger, ButtonComponent
|
504
|
-
end
|
505
|
-
```
|
490
|
+
Vident provides helper methods for generating Stimulus attributes:
|
506
491
|
|
507
492
|
```erb
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
<%= render root named_classes: {
|
514
|
-
pre_click: "text-md text-gray-500", # named classes are exposed to Stimulus as `data-<controller>-<n>-class` attributes
|
515
|
-
post_click: "text-xl text-blue-700",
|
516
|
-
html_options: {class: "py-2"}
|
517
|
-
} do |greeter| %>
|
518
|
-
|
519
|
-
<%# `greeter` is the root element and exposes methods to generate stimulus targets and actions %>
|
520
|
-
<input type="text"
|
521
|
-
<%= greeter.as_target(:name) %>
|
522
|
-
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
493
|
+
<%= render root do |component| %>
|
494
|
+
<!-- Create a target -->
|
495
|
+
<div <%= component.as_target(:content) %>>
|
496
|
+
Content here
|
497
|
+
</div>
|
523
498
|
|
524
|
-
|
525
|
-
<%=
|
499
|
+
<!-- Create an action -->
|
500
|
+
<button <%= component.as_action(:click, :toggle) %>>
|
501
|
+
Toggle
|
502
|
+
</button>
|
526
503
|
|
527
|
-
|
528
|
-
<%=
|
529
|
-
|
530
|
-
:output,
|
531
|
-
# Stimulus named classes can be referenced to set class attributes at render time
|
532
|
-
class: "ml-4 #{greeter.named_classes(:pre_click)}"
|
533
|
-
) do %>
|
534
|
-
...
|
504
|
+
<!-- Use the tag helper -->
|
505
|
+
<%= component.tag :div, stimulus_target: :output, class: "mt-4" do %>
|
506
|
+
Output here
|
535
507
|
<% end %>
|
508
|
+
|
509
|
+
<!-- Multiple targets/actions -->
|
510
|
+
<input <%= component.as_targets(:input, :field) %>
|
511
|
+
<%= component.as_actions([:input, :validate], [:change, :save]) %>>
|
536
512
|
<% end %>
|
537
|
-
|
538
513
|
```
|
539
514
|
|
540
|
-
|
541
|
-
// app/components/greeter_component_controller.js
|
515
|
+
### Stimulus Outlets
|
542
516
|
|
543
|
-
|
517
|
+
Connect components via Stimulus outlets:
|
544
518
|
|
545
|
-
// This is a Stimulus controller that is automatically registered for the `GreeterComponent`
|
546
|
-
// and is 'sidecar' to the component. You can see that while in the ERB we use Ruby naming conventions
|
547
|
-
// with snake_case Symbols, here they are converted to camelCase names. We can also just use camelCase
|
548
|
-
// in the ERB if we want.
|
549
|
-
export default class extends Controller {
|
550
|
-
static targets = [ "name", "output" ]
|
551
|
-
static classes = [ "preClick", "postClick" ]
|
552
|
-
|
553
|
-
greet() {
|
554
|
-
this.clicked = !this.clicked;
|
555
|
-
this.outputTarget.classList.toggle(this.preClickClasses, !this.clicked);
|
556
|
-
this.outputTarget.classList.toggle(this.postClickClasses, this.clicked);
|
557
|
-
|
558
|
-
if (this.clicked)
|
559
|
-
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
|
560
|
-
else
|
561
|
-
this.clear();
|
562
|
-
}
|
563
519
|
|
564
|
-
clear() {
|
565
|
-
this.outputTarget.textContent = '...';
|
566
|
-
this.nameTarget.value = '';
|
567
|
-
}
|
568
|
-
}
|
569
|
-
```
|
570
520
|
|
571
|
-
The slot renders a `ButtonComponent` component:
|
572
521
|
|
573
|
-
|
574
|
-
# app/components/button_component.rb
|
522
|
+
### Stimulus Controller Naming
|
575
523
|
|
576
|
-
|
577
|
-
# The attributes can specify an expected type, a default value and if nil is allowed.
|
578
|
-
attribute :after_clicked, String, default: "Greeted!"
|
579
|
-
attribute :before_clicked, String, allow_nil: false
|
524
|
+
Vident automatically generates Stimulus controller names based on your component class:
|
580
525
|
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
render root(
|
585
|
-
element_tag: :button,
|
586
|
-
|
587
|
-
# We can define actions as arrays of Symbols, or pass manually manually crafted strings.
|
588
|
-
# Here we specify the action name only, implying an action on the current components controller
|
589
|
-
# and the default event type of `click`.
|
590
|
-
actions: [:change_message],
|
591
|
-
# Alternatively: [:click, :change_message] or ["click", "changeMessage"] or even "click->button-component#changeMessage"
|
592
|
-
|
593
|
-
# A couple of data values are also set which will be available to the controller
|
594
|
-
data_maps: [{after_clicked_message: after_clicked, before_clicked_message: before_clicked}],
|
595
|
-
|
596
|
-
# The <button> tag has a default styling set directly on it. Note that
|
597
|
-
# if not using utility classes, you can style the component using its
|
598
|
-
# canonical class name (which is equal to the component's stimulus identifier),
|
599
|
-
# in this case `button-component`.
|
600
|
-
html_options: {class: "ml-4 whitespace-no-wrap bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"}
|
601
|
-
) do
|
602
|
-
@before_clicked
|
603
|
-
end
|
604
|
-
end
|
605
|
-
end
|
606
|
-
```
|
526
|
+
- `ButtonComponent` → `button-component`
|
527
|
+
- `Admin::UserCardComponent` → `admin--user-card-component`
|
528
|
+
- `MyApp::WidgetComponent` → `my-app--widget-component`
|
607
529
|
|
608
|
-
|
609
|
-
// app/components/button_component_controller.js
|
530
|
+
### Working with Child Components
|
610
531
|
|
611
|
-
|
612
|
-
|
613
|
-
export default class extends Controller {
|
614
|
-
// The action is in camelCase.
|
615
|
-
changeMessage() {
|
616
|
-
this.clicked = !this.clicked;
|
617
|
-
// The data attributes have their naming convention converted to camelCase.
|
618
|
-
this.element.textContent = this.clicked ? this.data.get("afterClickedMessage") : this.data.get("beforeClickedMessage");
|
619
|
-
}
|
620
|
-
}
|
621
|
-
|
622
|
-
```
|
623
|
-
|
624
|
-
### Usage
|
625
|
-
How to use my plugin.
|
626
|
-
|
627
|
-
### Installation
|
628
|
-
Add this line to your application's Gemfile:
|
532
|
+
Setting Stimulus configuration between parent and child components:
|
629
533
|
|
630
534
|
```ruby
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
Or install it yourself as:
|
640
|
-
```bash
|
641
|
-
$ gem install vident-typed-view_component
|
642
|
-
```
|
643
|
-
|
644
|
-
---
|
645
|
-
|
646
|
-
## gem: vident-typed
|
647
|
-
|
648
|
-
# Vident::Typed
|
649
|
-
Short description and motivation.
|
650
|
-
|
651
|
-
### Usage
|
652
|
-
How to use my plugin.
|
653
|
-
|
654
|
-
### Installation
|
655
|
-
Add this line to your application's Gemfile:
|
656
|
-
|
657
|
-
```ruby
|
658
|
-
gem "vident-typed"
|
659
|
-
```
|
660
|
-
|
661
|
-
And then execute:
|
662
|
-
```bash
|
663
|
-
$ bundle
|
535
|
+
class ParentComponent < Vident::ViewComponent::Base
|
536
|
+
renders_one :a_nested_component, ButtonComponent
|
537
|
+
|
538
|
+
stimulus do
|
539
|
+
actions :handleTrigger
|
540
|
+
end
|
541
|
+
end
|
664
542
|
```
|
665
543
|
|
666
|
-
|
667
|
-
|
668
|
-
|
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 %>
|
669
553
|
```
|
670
554
|
|
671
|
-
|
672
|
-
|
673
|
-
## gem: vident-view_component-caching
|
555
|
+
This creates a nested component that once clicked triggers the parent components `handleTrigger` action.
|
674
556
|
|
675
|
-
|
676
|
-
Short description and motivation.
|
557
|
+
## Other Features
|
677
558
|
|
678
|
-
###
|
679
|
-
How to use my plugin.
|
559
|
+
### Custom Element Tags
|
680
560
|
|
681
|
-
|
682
|
-
Add this line to your application's Gemfile:
|
561
|
+
Change the root element tag dynamically:
|
683
562
|
|
684
563
|
```ruby
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
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
|
578
|
+
end
|
696
579
|
```
|
697
580
|
|
698
|
-
|
699
|
-
|
700
|
-
## gem: vident-view_component
|
581
|
+
### Intelligent Class Management
|
701
582
|
|
702
|
-
|
703
|
-
|
704
|
-
[ViewComponent](https://viewcomponent.org/) powered [Vident](https://github.com/stevegeek/vident) components.
|
583
|
+
Vident intelligently merges CSS classes from multiple sources:
|
705
584
|
|
706
585
|
```ruby
|
707
|
-
class
|
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
|
708
603
|
end
|
709
604
|
```
|
710
605
|
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
```bash
|
720
|
-
cd test/dummy
|
721
|
-
bundle install
|
722
|
-
rails assets:precompile
|
723
|
-
rails s
|
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" -->
|
724
614
|
```
|
725
615
|
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
### A Vident component example (without Stimulus)
|
616
|
+
### Tailwind CSS Integration
|
730
617
|
|
731
|
-
|
732
|
-
|
733
|
-
It is an avatar component that can either be displayed as an image or as initials. 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.
|
618
|
+
Vident includes built-in support for Tailwind CSS class merging when the `tailwind_merge` gem is available:
|
734
619
|
|
735
620
|
```ruby
|
736
|
-
class
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
no_stimulus_controller
|
741
|
-
with_cache_key :attributes
|
742
|
-
|
743
|
-
attribute :url, allow_nil: true
|
744
|
-
attribute :initials, allow_nil: false
|
745
|
-
|
746
|
-
attribute :shape, default: :circle
|
747
|
-
|
748
|
-
attribute :border, default: false
|
749
|
-
|
750
|
-
attribute :size, default: :normal
|
751
|
-
|
621
|
+
class TailwindComponent < Vident::ViewComponent::Base
|
622
|
+
prop :size, Symbol, default: :medium
|
623
|
+
|
752
624
|
private
|
753
|
-
|
754
|
-
def default_html_options
|
755
|
-
if image_avatar?
|
756
|
-
{ class: "inline-block object-contain", src: url, alt: t(".image") }
|
757
|
-
else
|
758
|
-
{ class: "inline-flex items-center justify-center bg-gray-500" }
|
759
|
-
end
|
760
|
-
end
|
761
|
-
|
625
|
+
|
762
626
|
def element_classes
|
763
|
-
|
627
|
+
# Conflicts with size_class will be resolved automatically
|
628
|
+
"p-2 text-sm #{size_class}"
|
764
629
|
end
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
def size_classes
|
773
|
-
case size
|
774
|
-
when :tiny
|
775
|
-
"w-6 h-6"
|
776
|
-
when :small
|
777
|
-
"w-8 h-8"
|
778
|
-
when :medium
|
779
|
-
"w-12 h-12"
|
780
|
-
when :large
|
781
|
-
"w-14 h-14"
|
782
|
-
when :x_large
|
783
|
-
"sm:w-24 sm:h-24 w-16 h-16"
|
784
|
-
when :xx_large
|
785
|
-
"sm:w-32 sm:h-32 w-24 h-24"
|
786
|
-
else
|
787
|
-
"w-10 h-10"
|
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"
|
788
636
|
end
|
789
637
|
end
|
638
|
+
end
|
639
|
+
```
|
790
640
|
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
"sm:text-2xl text-xl"
|
803
|
-
else
|
804
|
-
"text-medium"
|
805
|
-
end
|
806
|
-
end
|
641
|
+
### Component Caching
|
642
|
+
|
643
|
+
Enable fragment caching for expensive components:
|
644
|
+
|
645
|
+
```ruby
|
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
|
807
652
|
end
|
808
653
|
```
|
809
654
|
|
810
655
|
```erb
|
811
|
-
|
812
|
-
|
813
|
-
html_options: default_html_options
|
814
|
-
) do %>
|
815
|
-
<% unless image_avatar? %>
|
816
|
-
<span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
|
817
|
-
<% end %>
|
656
|
+
<% cache component.cache_key do %>
|
657
|
+
<%= render component %>
|
818
658
|
<% end %>
|
819
659
|
```
|
820
660
|
|
821
|
-
Example usages:
|
822
661
|
|
823
|
-
|
824
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", initials: "AB" size: :large) %>
|
825
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", html_options: {alt: "My alt text", class: "object-scale-down"}) %>
|
826
|
-
<%= render AvatarComponent.new(initials: "SG", size: :small) %>
|
827
|
-
<%= render AvatarComponent.new(initials: "SG", size: :large, html_options: {class: "border-2 border-red-600"}) %>
|
828
|
-
```
|
662
|
+
## Testing
|
829
663
|
|
830
|
-
|
664
|
+
Vident components work seamlessly with testing frameworks that support ViewComponent or Phlex.
|
831
665
|
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
666
|
+
## Development
|
667
|
+
|
668
|
+
### Running Tests
|
669
|
+
|
670
|
+
```bash
|
671
|
+
# Run all tests
|
672
|
+
bin/rails test
|
836
673
|
```
|
837
674
|
|
838
|
-
|
675
|
+
### Local Development
|
839
676
|
|
840
|
-
```
|
841
|
-
|
677
|
+
```bash
|
678
|
+
# Clone the repository
|
679
|
+
git clone https://github.com/stevegeek/vident.git
|
680
|
+
cd vident
|
681
|
+
|
682
|
+
# Install dependencies
|
683
|
+
bundle install
|
684
|
+
|
685
|
+
# Run the dummy app
|
686
|
+
cd test/dummy
|
687
|
+
rails s
|
842
688
|
```
|
843
689
|
|
844
|
-
|
690
|
+
## Contributing
|
845
691
|
|
846
|
-
|
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
|
847
698
|
|
848
|
-
|
849
|
-
How to use my plugin.
|
699
|
+
## License
|
850
700
|
|
851
|
-
|
852
|
-
Add this line to your application's Gemfile:
|
701
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
853
702
|
|
854
|
-
|
855
|
-
gem "vident-view_component"
|
856
|
-
```
|
703
|
+
## Credits
|
857
704
|
|
858
|
-
|
859
|
-
```bash
|
860
|
-
$ bundle
|
861
|
-
```
|
705
|
+
Vident is maintained by [Stephen Ierodiaconou](https://github.com/stevegeek).
|
862
706
|
|
863
|
-
|
864
|
-
```bash
|
865
|
-
$ gem install vident-view_component
|
866
|
-
```
|
707
|
+
Special thanks to the ViewComponent and Phlex communities for their excellent component frameworks that Vident builds upon.
|