vident-phlex 0.13.1 → 1.0.0.alpha2
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 +34 -0
- data/README.md +547 -661
- 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,737 @@
|
|
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
|
100
|
-
|
101
|
-
The gems are available as open source under the terms of the [MIT License](LICENSE.txt).
|
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.
|
102
38
|
|
103
|
-
|
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.
|
104
41
|
|
105
|
-
|
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.
|
106
44
|
|
107
|
-
|
45
|
+
## Installation
|
108
46
|
|
109
|
-
|
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
|
-
```
|
119
|
-
|
120
|
-
For more details see [vident](https://github.com/stevegeek/vident).
|
50
|
+
# Core gem (required)
|
51
|
+
gem "vident"
|
121
52
|
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
136
|
-
|
64
|
+
## Quick Start
|
137
65
|
|
138
|
-
|
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 |component|
|
94
|
+
component.tag(:span, stimulus_target: :status) do
|
95
|
+
@text
|
96
|
+
end
|
97
|
+
end
|
175
98
|
end
|
176
99
|
|
177
|
-
|
100
|
+
private
|
178
101
|
|
179
|
-
def
|
180
|
-
|
102
|
+
def root_element_attributes
|
103
|
+
{
|
104
|
+
element_tag: @url ? :a : :button,
|
105
|
+
html_options: { href: @url }.compact
|
106
|
+
}
|
181
107
|
end
|
182
108
|
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
200
|
-
end
|
201
|
-
|
202
|
-
def text_size_class
|
203
|
-
case size
|
204
|
-
when :tiny
|
205
|
-
"text-xs"
|
206
|
-
when :small
|
207
|
-
"text-xs"
|
208
|
-
when :medium
|
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"
|
109
|
+
def element_classes
|
110
|
+
base_classes = "btn"
|
111
|
+
case @style
|
112
|
+
when :primary
|
113
|
+
"#{base_classes} btn-primary"
|
114
|
+
when :secondary
|
115
|
+
"#{base_classes} btn-secondary"
|
216
116
|
end
|
217
117
|
end
|
218
118
|
end
|
219
119
|
```
|
220
120
|
|
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
121
|
|
231
|
-
|
122
|
+
Add the corresponding Stimulus controller would be:
|
232
123
|
|
233
|
-
|
124
|
+
```javascript
|
125
|
+
// app/javascript/controllers/button_component_controller.js
|
126
|
+
// Can also be "side-car" in the same directory as the component, see the documentation for details
|
127
|
+
import { Controller } from "@hotwired/stimulus"
|
234
128
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
129
|
+
export default class extends Controller {
|
130
|
+
static values = {
|
131
|
+
clickedCount: Number,
|
132
|
+
loadingDuration: Number
|
133
|
+
}
|
134
|
+
static classes = ["loading"]
|
135
|
+
static targets = ["status"]
|
136
|
+
|
137
|
+
handleClick(event) {
|
138
|
+
// Increment counter
|
139
|
+
this.clickedCountValue++
|
140
|
+
|
141
|
+
// Store original text
|
142
|
+
const originalText = this.statusTarget.textContent
|
143
|
+
|
144
|
+
// Add loading state
|
145
|
+
this.element.classList.add(this.loadingClass)
|
146
|
+
this.element.disabled = true
|
147
|
+
this.statusTarget.textContent = "Loading..."
|
148
|
+
|
149
|
+
// Use the loading duration from the component
|
150
|
+
setTimeout(() => {
|
151
|
+
this.element.classList.remove(this.loadingClass)
|
152
|
+
this.element.disabled = false
|
153
|
+
|
154
|
+
// Update text to show count
|
155
|
+
this.statusTarget.textContent = `${originalText} (${this.clickedCountValue})`
|
156
|
+
}, this.loadingDurationValue)
|
157
|
+
}
|
158
|
+
}
|
249
159
|
```
|
250
160
|
|
161
|
+
Use the component in your views:
|
251
162
|
|
252
|
-
|
163
|
+
```erb
|
164
|
+
<!-- Default clicked count of 0 -->
|
165
|
+
<%= render ButtonComponent.new(text: "Save", style: :primary) %>
|
253
166
|
|
254
|
-
|
255
|
-
|
256
|
-
<span class="text-xs font-medium leading-none text-white">SG</span>
|
257
|
-
</div>
|
258
|
-
```
|
167
|
+
<!-- Pre-set clicked count -->
|
168
|
+
<%= render ButtonComponent.new(text: "Submit", style: :primary, clicked_count: 5) %>
|
259
169
|
|
260
|
-
|
170
|
+
<!-- Link variant -->
|
171
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home", style: :secondary) %>
|
261
172
|
|
262
|
-
|
263
|
-
|
173
|
+
<!-- Override things -->
|
174
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home" classes: "bg-red-900", html_options: {role: "button"}) %>
|
264
175
|
```
|
265
176
|
|
266
|
-
|
177
|
+
The rendered HTML includes all Stimulus data attributes:
|
267
178
|
|
268
|
-
|
179
|
+
```html
|
180
|
+
<!-- First button with default count -->
|
181
|
+
<button class="bg-blue-500 hover:bg-blue-700 text-white"
|
182
|
+
data-controller="button-component"
|
183
|
+
data-action="click->button-component#handleClick"
|
184
|
+
data-button-component-clicked-count-value="0"
|
185
|
+
data-button-component-loading-duration-value="1000"
|
186
|
+
data-button-component-loading-class="opacity-50 cursor-wait"
|
187
|
+
id="button-component-123">
|
188
|
+
<span data-button-component-target="status">Save</span>
|
189
|
+
</button>
|
190
|
+
|
191
|
+
<!-- Second button with pre-set count -->
|
192
|
+
<button class="bg-blue-500 hover:bg-blue-700 text-white"
|
193
|
+
data-controller="button-component"
|
194
|
+
data-action="click->button-component#handleClick"
|
195
|
+
data-button-component-clicked-count-value="5"
|
196
|
+
data-button-component-loading-duration-value="1000"
|
197
|
+
data-button-component-loading-class="opacity-50 cursor-wait"
|
198
|
+
id="button-component-456">
|
199
|
+
<span data-button-component-target="status">Submit</span>
|
200
|
+
</button>
|
201
|
+
```
|
202
|
+
|
203
|
+
## Core Concepts
|
204
|
+
|
205
|
+
### Component Properties
|
206
|
+
|
207
|
+
Vident uses the Literal gem to provide type-safe component properties:
|
269
208
|
|
209
|
+
```ruby
|
210
|
+
class CardComponent < Vident::ViewComponent::Base
|
211
|
+
# Basic property with type
|
212
|
+
prop :title, String
|
213
|
+
|
214
|
+
# Property with default value
|
215
|
+
prop :subtitle, String, default: ""
|
216
|
+
|
217
|
+
# Nullable property
|
218
|
+
prop :image_url, _Nilable(String)
|
219
|
+
|
220
|
+
# Property with validation
|
221
|
+
prop :size, _Union(:small, :medium, :large), default: :medium
|
222
|
+
|
223
|
+
# Boolean property (creates predicate method)
|
224
|
+
prop :featured, _Boolean, default: false
|
225
|
+
end
|
226
|
+
```
|
270
227
|
|
271
|
-
###
|
228
|
+
### Post-Initialization Hooks
|
272
229
|
|
273
|
-
|
230
|
+
Vident provides a hook for performing actions after component initialization:
|
274
231
|
|
275
|
-
|
276
|
-
|
232
|
+
```ruby
|
233
|
+
class MyComponent < Vident::ViewComponent::Base
|
234
|
+
prop :data, Hash, default: -> { {} }
|
235
|
+
|
236
|
+
def after_component_initialize
|
237
|
+
@processed_data = process_data(@data)
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
def process_data(data)
|
243
|
+
# Your initialization logic here
|
244
|
+
data.transform_values(&:upcase)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
```
|
277
248
|
|
278
|
-
|
249
|
+
**Important**: If you decide to override Literal's `after_initialize`, you **must** call `super` first to ensure Vident's initialization completes properly. Alternatively, use `after_component_initialize` which doesn't require calling `super`.
|
279
250
|
|
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 %>
|
251
|
+
### Built-in Properties
|
306
252
|
|
307
|
-
|
308
|
-
```
|
253
|
+
Every Vident component includes these properties:
|
309
254
|
|
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
|
-
```
|
255
|
+
- `element_tag` - The HTML tag for the root element (default: `:div`)
|
256
|
+
- `id` - The component's DOM ID (auto-generated if not provided)
|
257
|
+
- `classes` - Additional CSS classes
|
258
|
+
- `html_options` - Hash of HTML attributes
|
334
259
|
|
335
|
-
|
260
|
+
### Root Element Rendering
|
336
261
|
|
337
|
-
The
|
262
|
+
The `root_element` helper method renders your component's root element with all configured attributes:
|
338
263
|
|
339
264
|
```ruby
|
340
|
-
#
|
265
|
+
# In your component class
|
266
|
+
def element_classes
|
267
|
+
["card", featured? ? "card-featured" : nil]
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
341
271
|
|
342
|
-
|
343
|
-
|
272
|
+
def root_element_attributes
|
273
|
+
{
|
274
|
+
html_options: { role: "article", "aria-label": title }
|
275
|
+
}
|
344
276
|
end
|
345
277
|
```
|
346
278
|
|
347
279
|
```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 %>
|
280
|
+
<%# In your template %>
|
281
|
+
<%= root_element do %>
|
282
|
+
<h2><%= title %></h2>
|
283
|
+
<p><%= subtitle %></p>
|
376
284
|
<% end %>
|
377
|
-
|
378
285
|
```
|
379
286
|
|
380
|
-
|
381
|
-
// app/components/greeter_component_controller.js
|
287
|
+
## Component DSL
|
382
288
|
|
383
|
-
|
289
|
+
### ViewComponent Integration
|
384
290
|
|
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
|
-
}
|
291
|
+
```ruby
|
292
|
+
class MyComponent < Vident::ViewComponent::Base
|
293
|
+
# Component code
|
294
|
+
end
|
403
295
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
296
|
+
# Or with an application base class
|
297
|
+
class ApplicationComponent < Vident::ViewComponent::Base
|
298
|
+
# Shared configuration
|
299
|
+
end
|
300
|
+
|
301
|
+
class MyComponent < ApplicationComponent
|
302
|
+
# Component code
|
303
|
+
end
|
409
304
|
```
|
410
305
|
|
411
|
-
|
306
|
+
### Phlex Integration
|
412
307
|
|
413
308
|
```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
|
309
|
+
class MyComponent < Vident::Phlex::HTML
|
310
|
+
def view_template
|
311
|
+
root do
|
312
|
+
h1 { "Hello from Phlex!" }
|
443
313
|
end
|
444
314
|
end
|
445
315
|
end
|
446
316
|
```
|
447
317
|
|
448
|
-
|
449
|
-
// app/components/button_component_controller.js
|
318
|
+
## Stimulus Integration
|
450
319
|
|
451
|
-
|
320
|
+
Vident provides comprehensive Stimulus.js integration to add interactivity to your components.
|
452
321
|
|
453
|
-
|
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
|
-
}
|
461
|
-
|
462
|
-
```
|
322
|
+
### Declarative Stimulus DSL
|
463
323
|
|
464
|
-
|
465
|
-
How to use my plugin.
|
466
|
-
|
467
|
-
### Installation
|
468
|
-
Add this line to your application's Gemfile:
|
324
|
+
Use the `stimulus` block for clean, declarative configuration:
|
469
325
|
|
470
326
|
```ruby
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
327
|
+
class ToggleComponent < Vident::ViewComponent::Base
|
328
|
+
prop :expanded, _Boolean, default: false
|
329
|
+
|
330
|
+
stimulus do
|
331
|
+
# Define actions the controller responds to
|
332
|
+
actions :toggle, :expand, :collapse
|
333
|
+
|
334
|
+
# Define targets for DOM element references
|
335
|
+
targets :button, :content
|
336
|
+
|
337
|
+
# Define static values
|
338
|
+
values animation_duration: 300
|
339
|
+
|
340
|
+
# Define dynamic values using procs (evaluated in component context)
|
341
|
+
values item_count: -> { @items.count }
|
342
|
+
values current_state: proc { expanded? ? "open" : "closed" }
|
343
|
+
|
344
|
+
# Map values from component props
|
345
|
+
values_from_props :expanded
|
346
|
+
|
347
|
+
# Define CSS classes for different states
|
348
|
+
classes expanded: "block",
|
349
|
+
collapsed: "hidden",
|
350
|
+
transitioning: "opacity-50"
|
351
|
+
end
|
352
|
+
end
|
482
353
|
```
|
483
354
|
|
484
|
-
|
355
|
+
### Dynamic Values and Classes with Procs
|
485
356
|
|
486
|
-
|
487
|
-
## gem: vident-view_component
|
488
|
-
|
489
|
-
# Vident::ViewComponent
|
490
|
-
|
491
|
-
[ViewComponent](https://viewcomponent.org/) powered [Vident](https://github.com/stevegeek/vident) components.
|
357
|
+
The Stimulus DSL supports dynamic values and classes using procs or lambdas that are evaluated in the component instance context:
|
492
358
|
|
493
359
|
```ruby
|
494
|
-
class
|
360
|
+
class DynamicComponent < Vident::ViewComponent::Base
|
361
|
+
prop :items, _Array(Hash), default: -> { [] }
|
362
|
+
prop :loading, _Boolean, default: false
|
363
|
+
prop :user, _Nilable(User)
|
364
|
+
|
365
|
+
stimulus do
|
366
|
+
# Mix static and dynamic values in a single call
|
367
|
+
values(
|
368
|
+
static_config: "always_same",
|
369
|
+
item_count: -> { @items.count },
|
370
|
+
loading_state: proc { @loading ? "loading" : "idle" },
|
371
|
+
user_role: -> { @user&.role || "guest" },
|
372
|
+
api_endpoint: -> { Rails.application.routes.url_helpers.api_items_path }
|
373
|
+
)
|
374
|
+
|
375
|
+
# Mix static and dynamic classes
|
376
|
+
classes(
|
377
|
+
base: "component-container",
|
378
|
+
loading: -> { @loading ? "opacity-50 cursor-wait" : "" },
|
379
|
+
size: proc { @items.count > 10 ? "large" : "small" },
|
380
|
+
theme: -> { current_user&.dark_mode? ? "dark" : "light" }
|
381
|
+
)
|
382
|
+
|
383
|
+
# Dynamic actions and targets
|
384
|
+
actions -> { @loading ? [] : [:click, :submit] }
|
385
|
+
targets -> { @expanded ? [:content, :toggle] : [:toggle] }
|
386
|
+
end
|
387
|
+
|
388
|
+
private
|
389
|
+
|
390
|
+
def current_user
|
391
|
+
@current_user ||= User.current
|
392
|
+
end
|
495
393
|
end
|
496
394
|
```
|
497
395
|
|
498
|
-
|
396
|
+
Procs have access to instance variables, component methods, and Rails helpers.
|
499
397
|
|
500
|
-
|
398
|
+
**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:
|
501
399
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
400
|
+
```ruby
|
401
|
+
stimulus do
|
402
|
+
# Single proc returns a single value (even if it's an array)
|
403
|
+
actions -> { @expanded ? [:click, :submit] : :click }
|
404
|
+
|
405
|
+
# Multiple procs provide multiple values
|
406
|
+
actions -> { @can_edit ? :edit : nil },
|
407
|
+
-> { @can_delete ? :delete : nil },
|
408
|
+
:cancel # static value
|
409
|
+
|
410
|
+
# This results in: [:edit, :delete, :cancel] (assuming both conditions are true)
|
411
|
+
end
|
511
412
|
```
|
512
413
|
|
513
|
-
|
414
|
+
### Scoped Custom Events
|
514
415
|
|
515
|
-
|
516
|
-
### A Vident component example (without Stimulus)
|
517
|
-
|
518
|
-
First is an example component that uses `Vident::ViewComponent::Base` but no Stimulus features.
|
519
|
-
|
520
|
-
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.
|
416
|
+
Vident provides helper methods to generate scoped event names for dispatching custom events that are unique to your component:
|
521
417
|
|
522
418
|
```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
|
419
|
+
class MyComponent < Vident::ViewComponent::Base
|
420
|
+
stimulus do
|
421
|
+
# Define an action that responds to a scoped event
|
422
|
+
actions -> { [stimulus_scoped_event_on_window(:data_loaded), :handle_data_loaded] }
|
547
423
|
end
|
548
|
-
|
549
|
-
def
|
550
|
-
|
424
|
+
|
425
|
+
def handle_click
|
426
|
+
# Dispatch a scoped event from JavaScript
|
427
|
+
# This would generate: "my-component:dataLoaded"
|
428
|
+
puts stimulus_scoped_event(:data_loaded)
|
429
|
+
|
430
|
+
# For window events, this generates: "my-component:dataLoaded@window"
|
431
|
+
puts stimulus_scoped_event_on_window(:data_loaded)
|
551
432
|
end
|
433
|
+
end
|
552
434
|
|
553
|
-
|
435
|
+
# Available as both class and instance methods:
|
436
|
+
MyComponent.stimulus_scoped_event(:data_loaded) # => "my-component:dataLoaded"
|
437
|
+
MyComponent.new.stimulus_scoped_event(:data_loaded) # => "my-component:dataLoaded"
|
438
|
+
```
|
554
439
|
|
555
|
-
|
556
|
-
|
557
|
-
|
440
|
+
This is useful for:
|
441
|
+
- Dispatching events from Stimulus controllers to communicate between components
|
442
|
+
- Creating unique event names that won't conflict with other components
|
443
|
+
- Setting up window-level event listeners with scoped names
|
558
444
|
|
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
|
445
|
+
### Manual Stimulus Configuration
|
577
446
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
447
|
+
For more control, configure Stimulus attributes manually:
|
448
|
+
|
449
|
+
```ruby
|
450
|
+
class CustomComponent < Vident::ViewComponent::Base
|
451
|
+
private
|
452
|
+
|
453
|
+
def root_element_attributes
|
454
|
+
{
|
455
|
+
element_tag: :article,
|
456
|
+
stimulus_controllers: ["custom", "analytics"],
|
457
|
+
stimulus_actions: [
|
458
|
+
[:click, :handleClick],
|
459
|
+
[:custom_event, :handleCustom]
|
460
|
+
],
|
461
|
+
stimulus_values: {
|
462
|
+
endpoint: "/api/data",
|
463
|
+
refresh_interval: 5000
|
464
|
+
},
|
465
|
+
stimulus_targets: {
|
466
|
+
container: true
|
467
|
+
}
|
468
|
+
}
|
593
469
|
end
|
594
470
|
end
|
595
471
|
```
|
596
472
|
|
473
|
+
or you can use tag helpers to generate HTML with Stimulus attributes:
|
474
|
+
|
597
475
|
```erb
|
598
|
-
<%=
|
599
|
-
|
600
|
-
|
601
|
-
) do %>
|
602
|
-
<% unless image_avatar? %>
|
603
|
-
<span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
|
476
|
+
<%= content_tag(:input, type: "text", class: "...", data: {**greeter.stimulus_target(:name)}) %>
|
477
|
+
<%= content_tag(:button, @cta, class: "...", data: {**greeter.stimulus_action([:click, :greet])}) do %>
|
478
|
+
<%= @cta %>
|
604
479
|
<% end %>
|
605
|
-
|
606
|
-
```
|
480
|
+
<%= content_tag(:span, class: "...", data: {**greeter.stimulus_target(:output)}) %>
|
607
481
|
|
608
|
-
|
482
|
+
<%# OR use the vident tag helper %>
|
609
483
|
|
610
|
-
|
611
|
-
<%=
|
612
|
-
<%=
|
613
|
-
|
614
|
-
<%=
|
484
|
+
<%= greeter.tag(:input, stimulus_target: :name, type: "text", class: "...") %>
|
485
|
+
<%= greeter.tag(:button, stimulus_action: [:click, :greet], class: "...") do %>
|
486
|
+
<%= @cta %>
|
487
|
+
<% end %>
|
488
|
+
<%= greeter.tag(:span, stimulus_target: :output, class: "...") %>
|
615
489
|
```
|
616
490
|
|
617
|
-
|
491
|
+
or in your Phlex templates:
|
618
492
|
|
619
|
-
```
|
620
|
-
|
621
|
-
|
622
|
-
|
493
|
+
```ruby
|
494
|
+
root_element do |greeter|
|
495
|
+
input(type: "text", data: {**greeter.stimulus_target(:name)}, class: %(...))
|
496
|
+
trigger_or_default(greeter)
|
497
|
+
greeter.tag(:span, stimulus_target: :output, class: "ml-4 #{greeter.class_list_for_stimulus_classes(:pre_click)}") do
|
498
|
+
plain %( ... )
|
499
|
+
end
|
500
|
+
end
|
623
501
|
```
|
624
502
|
|
625
|
-
|
503
|
+
or directly in the ViewComponent template (eg with ERB) using the `as_stimulus_*` helpers
|
626
504
|
|
627
|
-
```
|
628
|
-
|
505
|
+
```erb
|
506
|
+
<%# HTML embellishment approach, most familiar to working with HTML in ERB, but is injecting directly into open HTML tags... %>
|
507
|
+
<input type="text"
|
508
|
+
<%= greeter.as_stimulus_targets(:name) %>
|
509
|
+
class="...">
|
510
|
+
<button <%= greeter.as_stimulus_actions([:click, :greet]) %>
|
511
|
+
class="...">
|
512
|
+
<%= @cta %>
|
513
|
+
</button>
|
514
|
+
<span <%= greeter.as_stimulus_targets(:output) %> class="..."></span>
|
629
515
|
```
|
630
516
|
|
631
|
-
----
|
632
|
-
|
633
|
-

|
634
517
|
|
635
|
-
###
|
636
|
-
How to use my plugin.
|
518
|
+
### Stimulus Helpers in Templates
|
637
519
|
|
638
|
-
|
639
|
-
Add this line to your application's Gemfile:
|
520
|
+
Vident provides helper methods for generating Stimulus attributes:
|
640
521
|
|
641
|
-
```
|
642
|
-
|
522
|
+
```erb
|
523
|
+
<%= render root do |component| %>
|
524
|
+
<!-- Create a target -->
|
525
|
+
<div <%= component.as_target(:content) %>>
|
526
|
+
Content here
|
527
|
+
</div>
|
528
|
+
|
529
|
+
<!-- Create an action -->
|
530
|
+
<button <%= component.as_action(:click, :toggle) %>>
|
531
|
+
Toggle
|
532
|
+
</button>
|
533
|
+
|
534
|
+
<!-- Use the tag helper -->
|
535
|
+
<%= component.tag :div, stimulus_target: :output, class: "mt-4" do %>
|
536
|
+
Output here
|
537
|
+
<% end %>
|
538
|
+
|
539
|
+
<!-- Multiple targets/actions -->
|
540
|
+
<input <%= component.as_targets(:input, :field) %>
|
541
|
+
<%= component.as_actions([:input, :validate], [:change, :save]) %>>
|
542
|
+
<% end %>
|
643
543
|
```
|
644
544
|
|
645
|
-
|
646
|
-
```bash
|
647
|
-
$ bundle
|
648
|
-
```
|
545
|
+
### Stimulus Outlets
|
649
546
|
|
650
|
-
|
651
|
-
```bash
|
652
|
-
$ gem install vident-view_component
|
653
|
-
```
|
547
|
+
Connect components via Stimulus outlets:
|
654
548
|
|
655
|
-
---
|
656
549
|
|
657
|
-
## gem: vident-better_html
|
658
550
|
|
659
|
-
# Vident::BetterHtml
|
660
|
-
Short description and motivation.
|
661
551
|
|
662
|
-
###
|
663
|
-
How to use my plugin.
|
552
|
+
### Stimulus Controller Naming
|
664
553
|
|
665
|
-
|
666
|
-
BetterHtml.config = BetterHtml::Config.new(YAML.load(File.read(".better-html.yml")))
|
554
|
+
Vident automatically generates Stimulus controller names based on your component class:
|
667
555
|
|
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
|
556
|
+
- `ButtonComponent` → `button-component`
|
557
|
+
- `Admin::UserCardComponent` → `admin--user-card-component`
|
558
|
+
- `MyApp::WidgetComponent` → `my-app--widget-component`
|
675
559
|
|
676
|
-
|
677
|
-
to_s_without_html_safe.html_safe
|
678
|
-
end
|
679
|
-
end
|
680
|
-
```
|
560
|
+
### Working with Child Components
|
681
561
|
|
682
|
-
|
683
|
-
Add this line to your application's Gemfile:
|
562
|
+
Setting Stimulus configuration between parent and child components:
|
684
563
|
|
685
564
|
```ruby
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
565
|
+
class ParentComponent < Vident::ViewComponent::Base
|
566
|
+
renders_one :a_nested_component, ButtonComponent
|
567
|
+
|
568
|
+
stimulus do
|
569
|
+
actions :handleTrigger
|
570
|
+
end
|
571
|
+
end
|
692
572
|
```
|
693
573
|
|
694
|
-
|
695
|
-
|
696
|
-
|
574
|
+
```erb
|
575
|
+
<%= root_element do |parent| %>
|
576
|
+
<% parent.with_a_nested_component(
|
577
|
+
text: "Click me",
|
578
|
+
stimulus_actions: [
|
579
|
+
parent.stimulus_action(:click, :handleTrigger)
|
580
|
+
]
|
581
|
+
) %>
|
582
|
+
<% end %>
|
697
583
|
```
|
698
584
|
|
699
|
-
|
585
|
+
This creates a nested component that once clicked triggers the parent components `handleTrigger` action.
|
700
586
|
|
701
|
-
##
|
587
|
+
## Other Features
|
702
588
|
|
703
|
-
|
589
|
+
### Custom Element Tags
|
704
590
|
|
705
|
-
|
591
|
+
Change the root element tag dynamically:
|
706
592
|
|
707
593
|
```ruby
|
708
|
-
class
|
594
|
+
class LinkOrButtonComponent < Vident::ViewComponent::Base
|
595
|
+
prop :url, _Nilable(String)
|
596
|
+
|
597
|
+
private
|
598
|
+
|
599
|
+
def root_element_attributes
|
600
|
+
{
|
601
|
+
element_tag: url? ? :a : :button,
|
602
|
+
html_options: {
|
603
|
+
href: url,
|
604
|
+
type: url? ? nil : "button"
|
605
|
+
}.compact
|
606
|
+
}
|
607
|
+
end
|
709
608
|
end
|
710
609
|
```
|
711
610
|
|
712
|
-
|
611
|
+
### Intelligent Class Management
|
713
612
|
|
714
|
-
|
715
|
-
How to use my plugin.
|
716
|
-
|
717
|
-
### Installation
|
718
|
-
Add this line to your application's Gemfile:
|
613
|
+
Vident intelligently merges CSS classes from multiple sources:
|
719
614
|
|
720
615
|
```ruby
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
616
|
+
class StyledComponent < Vident::ViewComponent::Base
|
617
|
+
prop :variant, Symbol, default: :default
|
618
|
+
|
619
|
+
private
|
620
|
+
|
621
|
+
# Classes on the root element
|
622
|
+
def element_classes
|
623
|
+
["base-class", variant_class]
|
624
|
+
end
|
625
|
+
|
626
|
+
def variant_class
|
627
|
+
case @variant
|
628
|
+
when :primary then "text-blue-600 bg-blue-100"
|
629
|
+
when :danger then "text-red-600 bg-red-100"
|
630
|
+
else "text-gray-600 bg-gray-100"
|
631
|
+
end
|
632
|
+
end
|
633
|
+
end
|
727
634
|
```
|
728
635
|
|
729
|
-
|
730
|
-
```
|
731
|
-
|
636
|
+
Usage:
|
637
|
+
```erb
|
638
|
+
<!-- All classes are intelligently merged -->
|
639
|
+
<%= render StyledComponent.new(
|
640
|
+
variant: :primary,
|
641
|
+
classes: "rounded-lg shadow"
|
642
|
+
) %>
|
643
|
+
<!-- Result: class="base-class text-blue-600 bg-blue-100 rounded-lg shadow" -->
|
732
644
|
```
|
733
645
|
|
734
|
-
|
735
|
-
|
736
|
-
## gem: vident-tailwind
|
737
|
-
|
738
|
-
# Vident::Tailwind
|
739
|
-
Short description and motivation.
|
646
|
+
### Tailwind CSS Integration
|
740
647
|
|
741
|
-
|
742
|
-
How to use my plugin.
|
743
|
-
|
744
|
-
### Installation
|
745
|
-
Add this line to your application's Gemfile:
|
648
|
+
Vident includes built-in support for Tailwind CSS class merging when the `tailwind_merge` gem is available:
|
746
649
|
|
747
650
|
```ruby
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
651
|
+
class TailwindComponent < Vident::ViewComponent::Base
|
652
|
+
prop :size, Symbol, default: :medium
|
653
|
+
|
654
|
+
private
|
655
|
+
|
656
|
+
def element_classes
|
657
|
+
# Conflicts with size_class will be resolved automatically
|
658
|
+
"p-2 text-sm #{size_class}"
|
659
|
+
end
|
660
|
+
|
661
|
+
def size_class
|
662
|
+
case @size
|
663
|
+
when :small then "p-1 text-xs"
|
664
|
+
when :large then "p-4 text-lg"
|
665
|
+
else "p-2 text-base"
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
759
669
|
```
|
760
670
|
|
761
|
-
|
762
|
-
|
763
|
-
## gem: vident-typed-minitest
|
764
|
-
|
765
|
-
# Vident::Typed::Minitest
|
766
|
-
Short description and motivation.
|
767
|
-
|
768
|
-
### Usage
|
769
|
-
How to use my plugin.
|
671
|
+
### Component Caching
|
770
672
|
|
771
|
-
|
772
|
-
Add this line to your application's Gemfile:
|
673
|
+
Enable fragment caching for expensive components:
|
773
674
|
|
774
675
|
```ruby
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
676
|
+
class ExpensiveComponent < Vident::ViewComponent::Base
|
677
|
+
include Vident::Caching
|
678
|
+
|
679
|
+
with_cache_key :to_h # Cache based on all attributes
|
680
|
+
# or
|
681
|
+
with_cache_key :id, :updated_at # Cache based on specific attributes
|
682
|
+
end
|
781
683
|
```
|
782
684
|
|
783
|
-
|
784
|
-
|
785
|
-
|
685
|
+
```erb
|
686
|
+
<% cache component.cache_key do %>
|
687
|
+
<%= render component %>
|
688
|
+
<% end %>
|
786
689
|
```
|
787
690
|
|
788
|
-
---
|
789
|
-
|
790
|
-
## gem: vident-typed-phlex
|
791
|
-
|
792
|
-
# Vident::Typed::Phlex
|
793
691
|
|
794
|
-
|
692
|
+
## Testing
|
795
693
|
|
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.
|
694
|
+
Vident components work seamlessly with testing frameworks that support ViewComponent or Phlex.
|
805
695
|
|
806
|
-
|
807
|
-
Add this line to your application's Gemfile:
|
696
|
+
## Development
|
808
697
|
|
809
|
-
|
810
|
-
gem "vident-typed-phlex"
|
811
|
-
```
|
698
|
+
### Running Tests
|
812
699
|
|
813
|
-
And then execute:
|
814
700
|
```bash
|
815
|
-
|
701
|
+
# Run all tests
|
702
|
+
bin/rails test
|
816
703
|
```
|
817
704
|
|
818
|
-
|
819
|
-
```bash
|
820
|
-
$ gem install vident-typed-phlex
|
821
|
-
```
|
705
|
+
### Local Development
|
822
706
|
|
823
|
-
|
707
|
+
```bash
|
708
|
+
# Clone the repository
|
709
|
+
git clone https://github.com/stevegeek/vident.git
|
710
|
+
cd vident
|
824
711
|
|
712
|
+
# Install dependencies
|
713
|
+
bundle install
|
825
714
|
|
826
|
-
|
715
|
+
# Run the dummy app
|
716
|
+
cd test/dummy
|
717
|
+
rails s
|
718
|
+
```
|
827
719
|
|
828
|
-
|
829
|
-
Short description and motivation.
|
720
|
+
## Contributing
|
830
721
|
|
831
|
-
|
832
|
-
|
722
|
+
1. Fork the repository
|
723
|
+
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
724
|
+
3. Write tests for your changes
|
725
|
+
4. Commit your changes (`git commit -am 'Add new feature'`)
|
726
|
+
5. Push to the branch (`git push origin feature/my-new-feature`)
|
727
|
+
6. Create a Pull Request
|
833
728
|
|
834
|
-
|
835
|
-
Add this line to your application's Gemfile:
|
729
|
+
## License
|
836
730
|
|
837
|
-
|
838
|
-
gem "vident-typed"
|
839
|
-
```
|
731
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
840
732
|
|
841
|
-
|
842
|
-
```bash
|
843
|
-
$ bundle
|
844
|
-
```
|
733
|
+
## Credits
|
845
734
|
|
846
|
-
|
847
|
-
```bash
|
848
|
-
$ gem install vident-typed
|
849
|
-
```
|
735
|
+
Vident is maintained by [Stephen Ierodiaconou](https://github.com/stevegeek).
|
850
736
|
|
851
|
-
|
737
|
+
Special thanks to the ViewComponent and Phlex communities for their excellent component frameworks that Vident builds upon.
|