vident 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +108 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +154 -391
- data/lib/vident/base.rb +1 -0
- data/lib/vident/caching.rb +120 -0
- data/lib/vident/component.rb +1 -0
- data/lib/vident/root_component.rb +9 -1
- data/lib/vident/version.rb +1 -1
- data/sig/vident.rbs +4 -0
- data/vident.gemspec +30 -0
- metadata +34 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95a2b00a3176fdc3fbd06569b20665b3e1d1bc8032758b0945d2dc58990d2fa5
|
4
|
+
data.tar.gz: e946108732b2abd5af47e5437b13e72983838cad032fb6b5c4c460504dc4cf8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3fd4da39749c72675965bf507287db253b305a2203c29a323a6342fa593709c06480ce1a3671e6044b562503853b78c56ca9a698e5962217a4a1becf23a29722
|
7
|
+
data.tar.gz: 11e8f9e658bb2ff4459719a205ae13bcecb7f2c65b1e4d89edb718879d5a54fe947922c0328cf5c15b19f066d46695250a935599fa37fcfd8de0fbfb9e2975af
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3.0
|
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
# Change Log
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
7
|
+
|
8
|
+
|
9
|
+
## [Unreleased]
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
### Changed
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
## [0.10.0] - 2024-02-21
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- `outlets` option for components, used to specify Stimulus outlets
|
22
|
+
|
23
|
+
## [0.9.0] - 2023-08-11
|
24
|
+
|
25
|
+
### Added
|
26
|
+
|
27
|
+
- `#cache_key` support is now part of the core gem, and can be added to components using `Vident::Caching` module
|
28
|
+
|
29
|
+
## [0.8.0] - 2023-03-31
|
30
|
+
|
31
|
+
### Added
|
32
|
+
|
33
|
+
- new gems for Vident related functionality, eg `vident-typed` and `vident-tailwind`
|
34
|
+
- `vident` is now the core gem which can be used with any component system. Gems for Phlex and ViewComponent are available, `vident-phlex` and `vident-view_component`, and `vident-typed-phlex` and `vident-typed-view_component` are available with typed attributes support.
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
|
38
|
+
- removed functionality for `better_html`, `dry-types`, `view_component`, and `phlex` from the core gem
|
39
|
+
- gem is now a Rails Engine and supports eager and autoloading
|
40
|
+
|
41
|
+
### Fixed
|
42
|
+
|
43
|
+
- Fix untyped attributes inheritance
|
44
|
+
|
45
|
+
## [0.7.0] - 2023-03-08
|
46
|
+
|
47
|
+
### Added
|
48
|
+
|
49
|
+
- new `Vident::Tailwind` module which uses [tailwind_merge](https://github.com/gjtorikian/tailwind_merge) to merge TailwindCSS classes
|
50
|
+
|
51
|
+
### Changed
|
52
|
+
|
53
|
+
- Removed a dependency on intenal constants from `phlex`
|
54
|
+
|
55
|
+
## [0.6.3] - 2023-03-03
|
56
|
+
|
57
|
+
### Fixed
|
58
|
+
|
59
|
+
- Fix for changes to HTML tag collection in Phlex
|
60
|
+
|
61
|
+
|
62
|
+
## [0.6.2] - 2023-02-23
|
63
|
+
|
64
|
+
### Fixed
|
65
|
+
|
66
|
+
- Element tag options are not set when no ID is provided
|
67
|
+
|
68
|
+
|
69
|
+
## [0.6.1] - 2023-02-20
|
70
|
+
|
71
|
+
### Fixed
|
72
|
+
|
73
|
+
- `better_html` support fix for aliased dsl methods
|
74
|
+
|
75
|
+
|
76
|
+
## [0.6.0] - 2023-02-20
|
77
|
+
|
78
|
+
### Added
|
79
|
+
|
80
|
+
- Experimental support for `better_html` in the root components (the stimulus attributes are generated with `html_attributes`)
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
## [0.5.1] - 2023-02-17
|
85
|
+
|
86
|
+
### Added
|
87
|
+
|
88
|
+
- N/A
|
89
|
+
|
90
|
+
### Changed
|
91
|
+
|
92
|
+
- N/A
|
93
|
+
|
94
|
+
### Fixed
|
95
|
+
|
96
|
+
- Typed attributes was not always using custom coercion methods if they were defined
|
97
|
+
|
98
|
+
### Removed
|
99
|
+
|
100
|
+
- N/A
|
101
|
+
|
102
|
+
### Deprecated
|
103
|
+
|
104
|
+
- N/A
|
105
|
+
|
106
|
+
### Security
|
107
|
+
|
108
|
+
- N/A
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at stevegeek@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Stephen Ierodiaconou
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
**Vident** is a collection of gems that help you create **flexible** & **maintainable** component libraries for your Rails application.
|
4
4
|
|
5
|
-
<a href="https://github.com/stevegeek/vident"><img alt="Vident logo" src="https://raw.githubusercontent.com/stevegeek/vident/main/logo-by-sd-256-colors.png" width="180" /></a>
|
5
|
+
<a href="https://github.com/stevegeek/vident"><img alt="Vident logo" src="https://raw.githubusercontent.com/stevegeek/vident/main/docs/images/logo-by-sd-256-colors.png" width="180" /></a>
|
6
6
|
|
7
7
|
Vident also provides a neat Ruby DSL to make wiring up **Stimulus easier & less error prone** in your view components.
|
8
8
|
|
@@ -11,243 +11,69 @@ Vident also provides a neat Ruby DSL to make wiring up **Stimulus easier & less
|
|
11
11
|
# Motivation
|
12
12
|
|
13
13
|
I love working with Stimulus, but I find manually crafting the data attributes for
|
14
|
-
targets and actions error
|
14
|
+
targets and actions error-prone and tedious. Vident aims to make this process easier
|
15
15
|
and keep me thinking in Ruby.
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
Vident has been used with `ViewComponent` and `Phlex` in production apps for a while now
|
18
|
+
but is still evolving.
|
19
19
|
|
20
|
-
|
20
|
+
I would love to get your feedback and contributions!
|
21
21
|
|
22
|
+
## Example
|
22
23
|
|
24
|
+
The Greeter ViewComponent (that uses Vident):
|
23
25
|
|
24
|
-
|
26
|
+
![docs/images/ex1.gif](docs/images/ex1.gif)
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
## What does Vident provide?
|
29
|
-
|
30
|
-
- `Vident::Component`: A mixin for your `ViewComponent` components or `Phlex` components that provides the a helper to create the
|
31
|
-
root element component (in templated or template-less components).
|
32
|
-
|
33
|
-
- `Vident::TypedComponent`: like `Vident::Component` but uses `dry-types` to define typed attributes for your components.
|
34
|
-
|
35
|
-
### Various utilities
|
36
|
-
|
37
|
-
- `Vident::Tailwind`: a mixin for your vident component which uses [tailwind_merge](https://github.com/gjtorikian/tailwind_merge) to merge TailwindCSS classes
|
38
|
-
so you can easily override classes when rendering a component.
|
39
|
-
|
40
|
-
- `Vident::Caching::CacheKey`: a mixin for your vident component which provides a `cache_key` method that can be used to generate a cache key for
|
41
|
-
fragment caching or etag generation.
|
42
|
-
|
43
|
-
- `Vident::RootComponent::*` which are components for creating the 'root' element in your view components. Similar to `Primer::BaseComponent` but
|
44
|
-
exposes a simple API for configuring and adding Stimulus controllers, targets and actions. Normally you create these
|
45
|
-
using the `root` helper method on `Vident::Component`/`Vident::TypedComponent`.
|
46
|
-
|
47
|
-
# Features
|
48
|
-
|
49
|
-
- A helper to create the root HTML element for your component, which then handles creation of attributes.
|
50
|
-
- Component arguments are defined using the `attribute` method which allows you to define default values, (optionally) types and
|
51
|
-
if blank or nil values should be allowed.
|
52
|
-
- You can use the same component in multiple contexts and configure the root element differently in each context by passing
|
53
|
-
options to the component when instantiating it.
|
54
|
-
- Stimulus support is built in and sets a default controller name based on the component name.
|
55
|
-
- Stimulus actions, targets and classes can be setup using a simple DSL to avoid hand crafting the data attributes.
|
56
|
-
- Since data attribute names are generated from the component class name, you can rename easily refactor and move components without
|
57
|
-
having to update the data attributes in your views.
|
58
|
-
- Components are rendered with useful class names and IDs to make debugging easier (autogenerated IDs are 'random' but deterministic so they
|
59
|
-
are the same each time a given view is rendered to avoid content changing/Etag changing).
|
60
|
-
- (experimental) Support for fragment caching of components (only with ViewComponent and with caveats)
|
61
|
-
- (experimental) A test helper to make testing components easier by utilising type information from the component arguments to render
|
62
|
-
automatically configured good and bad examples of the component.
|
63
|
-
- (experimental) support for `better_html`
|
64
|
-
|
65
|
-
|
66
|
-
## Things still to do...
|
67
|
-
|
68
|
-
This is a work in progress. Here's what's left to do for first release:
|
69
|
-
|
70
|
-
- Iterate on the interfaces and functionality
|
71
|
-
- Add tests
|
72
|
-
- Make the gem more configurable to fit more use cases
|
73
|
-
- Create an example library of a few components for some design system
|
74
|
-
- Create a demo app with `lookbook` and those components
|
75
|
-
- Add more documentation
|
76
|
-
- split `vident` into `vident` + `vident-rails` gems (and maybe `vident-rspec`) (Phlex can be used outside of Rails)
|
77
|
-
- possibly also split into `vident-phlex` and `vident-view_component` gems ?
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Examples
|
82
|
-
|
83
|
-
Before we dive into a specific example note that there are some components implemented with
|
84
|
-
both ViewComponent and Phlex (with and without Vident) in the `test/dummy`.
|
85
|
-
- https://github.com/stevegeek/vident/tree/main/test/dummy/app/components
|
86
|
-
- https://github.com/stevegeek/vident/tree/main/test/dummy/app/views
|
87
|
-
|
88
|
-
Start Rails:
|
89
|
-
|
90
|
-
```bash
|
91
|
-
cd test/dummy
|
92
|
-
bundle install
|
93
|
-
rails assets:precompile
|
94
|
-
rails s
|
95
|
-
```
|
96
|
-
|
97
|
-
and visit http://localhost:3000
|
98
|
-
|
99
|
-
|
100
|
-
## A Vident component example (without Stimulus)
|
101
|
-
|
102
|
-
First is an example component that uses `Vident::TypedComponent` but no Stimulus features.
|
103
|
-
|
104
|
-
It is an avatar component that can either be displayed as an image or as initials.
|
105
|
-
|
106
|
-
It supports numerous sizes and shapes and can optionally have a border. It also generates a cache key for use in fragment caching or etag generation.
|
28
|
+
Consider a component, the `GreeterComponent`:
|
107
29
|
|
108
30
|
```ruby
|
109
|
-
|
110
|
-
include ::Vident::TypedComponent
|
111
|
-
include ::Vident::Tailwind
|
112
|
-
include ::Vident::Caching::CacheKey
|
113
|
-
|
114
|
-
no_stimulus_controller
|
115
|
-
with_cache_key :attributes
|
116
|
-
|
117
|
-
attribute :url, String, allow_nil: true, allow_blank: false
|
118
|
-
attribute :initials, String, allow_blank: false
|
119
|
-
|
120
|
-
attribute :shape, Symbol, in: %i[circle square], default: :circle
|
121
|
-
|
122
|
-
attribute :border, :boolean, default: false
|
123
|
-
|
124
|
-
attribute :size, Symbol, in: %i[tiny small normal medium large x_large xx_large], default: :normal
|
125
|
-
|
126
|
-
private
|
127
|
-
|
128
|
-
def html_options
|
129
|
-
if image_avatar?
|
130
|
-
{ class: "inline-block object-contain", src: url, alt: t(".image") }
|
131
|
-
else
|
132
|
-
{ class: "inline-flex items-center justify-center bg-gray-500" }
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def element_classes
|
137
|
-
[size_classes, shape_class, border? ? "border" : ""]
|
138
|
-
end
|
139
|
-
|
140
|
-
alias_method :image_avatar?, :url?
|
141
|
-
|
142
|
-
def shape_class
|
143
|
-
(shape == :circle) ? "rounded-full" : "rounded-md"
|
144
|
-
end
|
145
|
-
|
146
|
-
def size_classes
|
147
|
-
case size
|
148
|
-
when :tiny
|
149
|
-
"w-6 h-6"
|
150
|
-
when :small
|
151
|
-
"w-8 h-8"
|
152
|
-
when :medium
|
153
|
-
"w-12 h-12"
|
154
|
-
when :large
|
155
|
-
"w-14 h-14"
|
156
|
-
when :x_large
|
157
|
-
"sm:w-24 sm:h-24 w-16 h-16"
|
158
|
-
when :xx_large
|
159
|
-
"sm:w-32 sm:h-32 w-24 h-24"
|
160
|
-
else
|
161
|
-
"w-10 h-10"
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def text_size_class
|
166
|
-
case size
|
167
|
-
when :tiny
|
168
|
-
"text-xs"
|
169
|
-
when :small
|
170
|
-
"text-xs"
|
171
|
-
when :medium
|
172
|
-
"text-lg"
|
173
|
-
when :large
|
174
|
-
"sm:text-xl text-lg"
|
175
|
-
when :extra_large
|
176
|
-
"sm:text-2xl text-xl"
|
177
|
-
else
|
178
|
-
"text-medium"
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
```
|
183
|
-
|
184
|
-
```erb
|
185
|
-
<%= render root(
|
186
|
-
element_tag: image_avatar? ? :img : :div,
|
187
|
-
html_options: html_options
|
188
|
-
) do %>
|
189
|
-
<% unless image_avatar? %>
|
190
|
-
<span class="<%= text_size_class %> font-medium leading-none text-white"><%= initials %></span>
|
191
|
-
<% end %>
|
192
|
-
<% end %>
|
31
|
+
# app/components/greeter_component.rb
|
193
32
|
|
33
|
+
class GreeterComponent < ::Vident::ViewComponent::Base
|
34
|
+
renders_one :trigger, ButtonComponent
|
35
|
+
end
|
194
36
|
```
|
195
37
|
|
196
|
-
|
38
|
+
with ERB as follows:
|
197
39
|
|
198
40
|
```erb
|
199
|
-
|
200
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", initials: "AB" size: :large) %>
|
201
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", html_options: {alt: "My alt text", class: "object-scale-down"}) %>
|
202
|
-
<%= render AvatarComponent.new(initials: "SG", size: :small) %>
|
203
|
-
<%= render AvatarComponent.new(initials: "SG", size: :large, html_options: {class: "border-2 border-red-600"}) %>
|
204
|
-
|
205
|
-
<!-- These will raise an error -->
|
206
|
-
<!-- missing initals -->
|
207
|
-
<%= render AvatarComponent.new(url: "https://someurl.com/avatar.jpg", size: :large) %>
|
208
|
-
<!-- initials blank -->
|
209
|
-
<%= render AvatarComponent.new(initials: "", size: :large) %>
|
210
|
-
<!-- invalid size -->
|
211
|
-
<%= render AvatarComponent.new(initials: "SG", size: :foo_bar) %>
|
212
|
-
```
|
213
|
-
|
214
|
-
|
215
|
-
The following is rendered when used `render AvatarComponent.new(initials: "SG", size: :small, border: true)`:
|
216
|
-
|
217
|
-
```html
|
218
|
-
<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">
|
219
|
-
<span class="text-xs font-medium leading-none text-white">SG</span>
|
220
|
-
</div>
|
221
|
-
```
|
41
|
+
<%# app/components/greeter_component.html.erb %>
|
222
42
|
|
223
|
-
|
43
|
+
<%# Rendering the `root` element creates a tag which has stimulus `data-*`s, a unique id & other attributes set. %>
|
44
|
+
<%# The stimulus controller name (identifier) is derived from the component name, and then used to generate the relavent data attribute names. %>
|
224
45
|
|
225
|
-
|
226
|
-
|
46
|
+
<%= render root named_classes: {
|
47
|
+
pre_click: "text-md text-gray-500", # named classes are exposed to Stimulus as `data-<controller>-<name>-class` attributes
|
48
|
+
post_click: "text-xl text-blue-700",
|
49
|
+
html_options: {class: "py-2"}
|
50
|
+
} do |greeter| %>
|
51
|
+
<%# `greeter` is the root element and exposes methods to generate stimulus targets and actions %>
|
52
|
+
<input type="text"
|
53
|
+
<%= greeter.as_target(:name) %>
|
54
|
+
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
55
|
+
|
56
|
+
<%# Render the slot %>
|
57
|
+
<%= trigger %>
|
58
|
+
|
59
|
+
<%# you can also use the `target_tag` helper to render targets %>
|
60
|
+
<%= greeter.target_tag(
|
61
|
+
:span,
|
62
|
+
:output,
|
63
|
+
# Stimulus named classes can be referenced to set class attributes at render time
|
64
|
+
class: "ml-4 #{greeter.named_classes(:pre_click)}"
|
65
|
+
) do %>
|
66
|
+
...
|
67
|
+
<% end %>
|
68
|
+
<% end %>
|
227
69
|
```
|
228
70
|
|
229
|
-
|
230
|
-
|
231
|
-
![Example](examples/avatar.png)
|
232
|
-
|
233
|
-
## Another ViewComponent + Vident example with Stimulus
|
234
|
-
|
235
|
-
Consider the following ERB that might be part of an application's views. The app uses `ViewComponent`, `Stimulus` and `Vident`.
|
236
|
-
|
237
|
-
The Greeter is a component that displays a text input and a button. When the button is clicked, the text input's value is
|
238
|
-
used to greet the user. At the same time the button changes to be a 'reset' button, which resets the greeting when clicked again.
|
239
|
-
|
240
|
-
![ex1.gif](examples%2Fex1.gif)
|
71
|
+
Now, imagine we render it in a view, and render a `ButtonComponent` in the `trigger` slot:
|
241
72
|
|
242
73
|
```erb
|
243
|
-
<%# app/views/home/index.html.erb %>
|
244
|
-
|
245
|
-
<!-- ... -->
|
246
|
-
|
247
|
-
<!-- render the Greeter ViewComponent (that uses Vident) -->
|
248
74
|
<%= render ::GreeterComponent.new(cta: "Hey!", html_options: {class: "my-4"}) do |greeter| %>
|
249
75
|
<%# this component has a slot called `trigger` that renders a `ButtonComponent` (which also uses Vident) %>
|
250
|
-
<% greeter.
|
76
|
+
<% greeter.with_trigger(
|
251
77
|
|
252
78
|
# The button component has attributes that are typed
|
253
79
|
before_clicked: "Greet",
|
@@ -265,8 +91,6 @@ used to greet the user. At the same time the button changes to be a 'reset' butt
|
|
265
91
|
}
|
266
92
|
) %>
|
267
93
|
<% end %>
|
268
|
-
|
269
|
-
<!-- ... -->
|
270
94
|
```
|
271
95
|
|
272
96
|
The output HTML of the above, using Vident, is:
|
@@ -294,237 +118,178 @@ The output HTML of the above, using Vident, is:
|
|
294
118
|
</div>
|
295
119
|
```
|
296
120
|
|
297
|
-
|
121
|
+
To see this example in more detail, see the [vident-typed-view_component](https://github.com/stevegeek/vident-typed-view_component/tree/main/test/dummy/app/components) test dummy app.
|
298
122
|
|
299
|
-
|
123
|
+
# Vident is a collection of gems
|
300
124
|
|
301
|
-
|
302
|
-
# app/components/greeter_component.rb
|
125
|
+
The core gems are:
|
303
126
|
|
304
|
-
|
305
|
-
|
127
|
+
- [`vident`](https://github.com/stevegeek/vident) to get the base functionality
|
128
|
+
- [`vident-typed`](https://github.com/stevegeek/vident-typed) to optionally define typed attributes for your view components
|
306
129
|
|
307
|
-
|
308
|
-
end
|
309
|
-
```
|
130
|
+
Gems that provide support for `ViewComponent` and `Phlex`:
|
310
131
|
|
311
|
-
|
312
|
-
|
132
|
+
- [`vident-view_component`](https://github.com/stevegeek/vident-view_component) for using with `ViewComponent` and untyped attributes
|
133
|
+
- [`vident-typed-view_component`](https://github.com/stevegeek/vident-typed-view_component) for using with `ViewComponent` and typed attributes
|
134
|
+
- [`vident-phlex`](https://github.com/stevegeek/vident-phlex) for using with `Phlex` and untyped attributes
|
135
|
+
- [`vident-typed-phlex`](https://github.com/stevegeek/vident-typed-phlex) for using with `Phlex` and typed attributes
|
313
136
|
|
314
|
-
|
315
|
-
<%# The stimulus controller name (identifier) is derived from the component name, and then used to generate the relavent data attribute names. %>
|
137
|
+
There is also:
|
316
138
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
html_options: {class: "py-2"}
|
321
|
-
} do |greeter| %>
|
139
|
+
- [`vident-typed-minitest`](https://github.com/stevegeek/vident-typed-minitest) to get some test helpers for typed attributes (auto generates inputs to test attributes)
|
140
|
+
- [`vident-better_html`](https://github.com/stevegeek/vident-better_html) to support `better_html` if you use it in your Rails app
|
141
|
+
- [`vident-tailwind`](https://github.com/stevegeek/vident-tailwind) to get all the benefits of the amazing [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge/).
|
322
142
|
|
323
|
-
<%# `greeter` is the root element and exposes methods to generate stimulus targets and actions %>
|
324
|
-
<input type="text"
|
325
|
-
<%= greeter.as_target(:name) %>
|
326
|
-
class="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
|
327
|
-
|
328
|
-
<%# Render the slot %>
|
329
|
-
<%= trigger %>
|
330
|
-
|
331
|
-
<%# you can also use the `target_tag` helper to render targets %>
|
332
|
-
<%= greeter.target_tag(
|
333
|
-
:span,
|
334
|
-
:output,
|
335
|
-
# Stimulus named classes can be referenced to set class attributes at render time
|
336
|
-
class: "ml-4 #{greeter.named_classes(:pre_click)}"
|
337
|
-
) do %>
|
338
|
-
...
|
339
|
-
<% end %>
|
340
|
-
<% end %>
|
341
143
|
|
342
|
-
|
144
|
+
# Things still to do...
|
343
145
|
|
344
|
-
|
345
|
-
// app/components/greeter_component_controller.js
|
346
|
-
|
347
|
-
import { Controller } from "@hotwired/stimulus"
|
348
|
-
|
349
|
-
// This is a Stimulus controller that is automatically registered for the `GreeterComponent`
|
350
|
-
// and is 'sidecar' to the component. You can see that while in the ERB we use Ruby naming conventions
|
351
|
-
// with snake_case Symbols, here they are converted to camelCase names. We can also just use camelCase
|
352
|
-
// in the ERB if we want.
|
353
|
-
export default class extends Controller {
|
354
|
-
static targets = [ "name", "output" ]
|
355
|
-
static classes = [ "preClick", "postClick" ]
|
356
|
-
|
357
|
-
greet() {
|
358
|
-
this.clicked = !this.clicked;
|
359
|
-
this.outputTarget.classList.toggle(this.preClickClasses, !this.clicked);
|
360
|
-
this.outputTarget.classList.toggle(this.postClickClasses, this.clicked);
|
361
|
-
|
362
|
-
if (this.clicked)
|
363
|
-
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
|
364
|
-
else
|
365
|
-
this.clear();
|
366
|
-
}
|
367
|
-
|
368
|
-
clear() {
|
369
|
-
this.outputTarget.textContent = '...';
|
370
|
-
this.nameTarget.value = '';
|
371
|
-
}
|
372
|
-
}
|
373
|
-
```
|
146
|
+
This is a work in progress. Here's what's left to do for first release:
|
374
147
|
|
375
|
-
|
148
|
+
- Iterate on the interfaces and functionality
|
149
|
+
- Add tests
|
150
|
+
- Make the gem more configurable to fit more use cases
|
151
|
+
- Create an example library of a few components for some design system
|
152
|
+
- Create a demo app with `lookbook` and those components
|
153
|
+
- Add more documentation
|
376
154
|
|
377
|
-
|
378
|
-
# app/components/button_component.rb
|
379
|
-
|
380
|
-
class ButtonComponent < ViewComponent::Base
|
381
|
-
# This component uses Vident::TypedComponent which uses dry-types to define typed attributes.
|
382
|
-
include Vident::TypedComponent
|
383
|
-
|
384
|
-
# The attributes can specify an expected type, a default value and if nil is allowed.
|
385
|
-
attribute :after_clicked, String, default: "Greeted!"
|
386
|
-
attribute :before_clicked, String, allow_nil: false
|
387
|
-
|
388
|
-
# This example is a templateless ViewComponent.
|
389
|
-
def call
|
390
|
-
# The button is rendered as a <button> tag with an click action on its own controller.
|
391
|
-
render root(
|
392
|
-
element_tag: :button,
|
393
|
-
|
394
|
-
# We can define actions as arrays of Symbols, or pass manually manually crafted strings.
|
395
|
-
# Here we specify the action name only, implying an action on the current components controller
|
396
|
-
# and the default event type of `click`.
|
397
|
-
actions: [:change_message],
|
398
|
-
# Alternatively: [:click, :change_message] or ["click", "changeMessage"] or even "click->button-component#changeMessage"
|
399
|
-
|
400
|
-
# A couple of data values are also set which will be available to the controller
|
401
|
-
data_maps: [{after_clicked_message: after_clicked, before_clicked_message: before_clicked}],
|
402
|
-
|
403
|
-
# The <button> tag has a default styling set directly on it. Note that
|
404
|
-
# if not using utility classes, you can style the component using its
|
405
|
-
# canonical class name (which is equal to the component's stimulus identifier),
|
406
|
-
# in this case `button-component`.
|
407
|
-
html_options: {class: "ml-4 whitespace-no-wrap bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"}
|
408
|
-
) do
|
409
|
-
@before_clicked
|
410
|
-
end
|
411
|
-
end
|
412
|
-
end
|
413
|
-
```
|
155
|
+
# About Vident
|
414
156
|
|
415
|
-
|
416
|
-
// app/components/button_component_controller.js
|
157
|
+
## What does Vident provide?
|
417
158
|
|
418
|
-
|
159
|
+
- Base classes for your `ViewComponent` components or `Phlex` components that provides a helper to create the
|
160
|
+
all important 'root' element component (can be used with templated or template-less components).
|
419
161
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
162
|
+
- implementations of these root components for creating the 'root' element in your view components. Similar to `Primer::BaseComponent` but
|
163
|
+
exposes a simple API for configuring and adding Stimulus controllers, targets and actions. The root component also handles deduplication
|
164
|
+
of classes, creating a unique ID, setting the element tag type, handling possible overrides set at the render site, and determining stimulus controller identifiers etc
|
165
|
+
|
166
|
+
- a way to define attributes for your components, either typed or untyped, with default values and optional validation.
|
167
|
+
|
168
|
+
### Various utilities
|
169
|
+
|
170
|
+
Such as...
|
171
|
+
|
172
|
+
- for Taiwind users, a mixin for your vident component which uses [tailwind_merge](https://github.com/gjtorikian/tailwind_merge) to merge TailwindCSS classes
|
173
|
+
so you can easily override classes when rendering a component.
|
174
|
+
- a mixin for your Vident Components which provides a `#cache_key` method that can be used to generate a cache key for
|
175
|
+
fragment caching or etag generation.
|
176
|
+
- a test helper for your typed Vident ViewComponents which can be used to generate good and bad attribute/params/inputs
|
177
|
+
|
178
|
+
## All the Features...
|
179
|
+
|
180
|
+
- use Vident with `ViewComponent` or `Phlex` or your own view component system
|
181
|
+
- A helper to create the root HTML element for your component, which then handles creation of attributes.
|
182
|
+
- Component arguments are defined using the `attribute` method which allows you to define default values, (optionally) types and
|
183
|
+
if blank or nil values should be allowed.
|
184
|
+
- You can use the same component in multiple contexts and configure the root element differently in each context by passing
|
185
|
+
options to the component when instantiating it.
|
186
|
+
- Stimulus support is built in and sets a default controller name based on the component name.
|
187
|
+
- Stimulus actions, targets and classes can be setup using a simple DSL to avoid hand crafting the data attributes.
|
188
|
+
- Since data attribute names are generated from the component class name, you can rename easily refactor and move components without
|
189
|
+
having to update the data attributes in your views.
|
190
|
+
- Components are rendered with useful class names and IDs to make debugging easier (autogenerated IDs are 'random' but deterministic so they
|
191
|
+
are the same each time a given view is rendered to avoid content changing/Etag changing).
|
192
|
+
- (experimental) Support for fragment caching of components (`Vident::Caching` and `Vident::<ViewComponent | Phlex>::Caching`... implementation has caveats)
|
193
|
+
- (experimental) A test helper to make testing components easier by utilising type information from the component arguments to render
|
194
|
+
automatically configured good and bad examples of the component.
|
195
|
+
- (experimental) support for `better_html`
|
428
196
|
|
429
|
-
```
|
430
197
|
|
431
198
|
## Installation
|
432
199
|
|
433
200
|
This gem (`vident`) provides only base functionality but there are a number of gems that provide additional functionality
|
434
|
-
or
|
201
|
+
or an "out of the box" experience.
|
435
202
|
|
436
203
|
It's a "pick your own adventure" approach. You decide what frameworks and features you want to use
|
437
204
|
and add the gems as needed.
|
438
205
|
|
439
|
-
|
206
|
+
First, add this line to your application's Gemfile:
|
440
207
|
|
441
|
-
|
208
|
+
```ruby
|
209
|
+
gem 'vident'
|
210
|
+
```
|
442
211
|
|
443
|
-
|
212
|
+
Then go on to choose the gems you want to use:
|
444
213
|
|
445
|
-
|
446
|
-
then depending on your answer to Q2, you can choose from either the untyped or `*-typed-*` gems.
|
214
|
+
#### Q1. Do you want to use [`ViewComponent`](https://viewcomponent.org/) or [`Phlex`](https://www.phlex.fun/) for your view components?
|
447
215
|
|
448
|
-
|
216
|
+
For ViewComponent use:
|
449
217
|
|
450
|
-
- [`vident-view_component`](https://github.com/stevegeek/vident-view_component)
|
451
|
-
- [`vident-typed-view_component`](https://github.com/stevegeek/vident-typed-view_component) for using with `ViewComponent` and typed attributes
|
452
|
-
- [`vident-phlex`](https://github.com/stevegeek/vident-phlex) for using with `Phlex` and untyped attributes
|
453
|
-
- [`vident-typed-phlex`](https://github.com/stevegeek/vident-typed-phlex) for using with `Phlex` and typed attributes
|
218
|
+
- [`vident-view_component`](https://github.com/stevegeek/vident-view_component)
|
454
219
|
|
455
|
-
|
220
|
+
For Phlex use:
|
456
221
|
|
457
|
-
|
458
|
-
Note that `vident-better_html` automatically enables `better_html` support in Vident root components.
|
222
|
+
- [`vident-phlex`](https://github.com/stevegeek/vident-phlex)
|
459
223
|
|
460
|
-
_Q4. Do you use or want to use [TailwindCSS](https://tailwindcss.com/)?_
|
461
224
|
|
462
|
-
|
463
|
-
your components you can then include `Vident::Tailwind` to get all the benefits of the amazing [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge/).
|
225
|
+
Note: you can also use both in the same app.
|
464
226
|
|
465
|
-
|
466
|
-
solution:
|
227
|
+
For example, if you want to use ViewComponent and Phlex in the same app, you might end up with:
|
467
228
|
|
468
|
-
|
469
|
-
|
229
|
+
```ruby
|
230
|
+
gem 'vident'
|
231
|
+
gem 'vident-view_component'
|
232
|
+
gem 'vident-phlex'
|
233
|
+
```
|
470
234
|
|
235
|
+
#### Q2. Do you want to build components where the attributes have runtime type checking (powered by [`dry-types`](https://github.com/dry-rb/dry-types))?
|
471
236
|
|
472
|
-
|
237
|
+
If yes, then add `vident-typed` to your Gemfile:
|
473
238
|
|
474
|
-
|
239
|
+
```ruby
|
240
|
+
gem 'vident-typed'
|
241
|
+
```
|
475
242
|
|
476
|
-
|
243
|
+
and then use the relavent `*-typed-*` gems for your chosen view component system:
|
477
244
|
|
478
|
-
|
245
|
+
- use [`vident-typed-view_component`](https://github.com/stevegeek/vident-typed-view_component)
|
246
|
+
- and/or [`vident-typed-phlex`](https://github.com/stevegeek/vident-typed-phlex)
|
479
247
|
|
480
|
-
|
248
|
+
Note you must also include the gem for the view component system you are using.
|
481
249
|
|
482
|
-
|
250
|
+
For example, for ViewComponent, you might end up with:
|
483
251
|
|
484
252
|
```ruby
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
name = controller.relative_path_from(components_path).to_s.remove(/\.js$/)
|
490
|
-
pin "#{prefix}/#{name}", to: name
|
491
|
-
end
|
492
|
-
end
|
253
|
+
gem 'vident'
|
254
|
+
gem 'vident-view_component'
|
255
|
+
gem 'vident-typed'
|
256
|
+
gem 'vident-typed-view_component'
|
493
257
|
```
|
494
258
|
|
495
|
-
|
496
|
-
See this for more: https://stackoverflow.com/a/73228193/268602
|
259
|
+
#### Q3. Do you use or want to use [BetterHTML](https://github.com/Shopify/better-html) in your Rails project?
|
497
260
|
|
498
|
-
|
499
|
-
to the `app/assets/config/manifest.js`:
|
261
|
+
If yes, then include [`vident-better_html`](https://github.com/stevegeek/vident-better_html) in your Gemfile alongside `better_html` and your vident gems of choice.
|
500
262
|
|
501
|
-
```
|
502
|
-
|
503
|
-
|
263
|
+
```ruby
|
264
|
+
...
|
265
|
+
gem 'better_html'
|
266
|
+
gem 'vident-better_html'
|
504
267
|
```
|
505
268
|
|
506
|
-
|
269
|
+
Note that `vident-better_html` automatically enables `better_html` support in Vident root components.
|
270
|
+
|
271
|
+
### Q4. Do you use or want to use [TailwindCSS](https://tailwindcss.com/)?
|
272
|
+
|
273
|
+
If yes, then consider adding [`vident-tailwind`](https://github.com/stevegeek/vident-tailwind) to your Gemfile alongside your vident gems of choice.
|
507
274
|
|
508
275
|
```ruby
|
509
|
-
|
510
|
-
|
276
|
+
...
|
277
|
+
gem 'vident-tailwind'
|
511
278
|
```
|
512
279
|
|
513
|
-
|
514
|
-
|
515
|
-
TODO
|
280
|
+
When creating your components you can then include `Vident::Tailwind` to get all the benefits of the amazing [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge/).
|
516
281
|
|
517
|
-
###
|
282
|
+
### Q5. Did none of the above gems suit your needs?
|
518
283
|
|
519
|
-
|
284
|
+
You can always just use base `vident` gems and then roll your own solutions:
|
520
285
|
|
521
|
-
|
286
|
+
- [`vident`](https://github.com/stevegeek/vident) to get the base functionality to mix with your own view component system
|
287
|
+
- [`vident-typed`](https://github.com/stevegeek/vident-typed) to define typed attributes for your own view component system
|
522
288
|
|
523
|
-
TODO
|
524
289
|
|
525
|
-
##
|
290
|
+
## Documentation
|
526
291
|
|
527
|
-
|
292
|
+
See the [docs](docs/) directory and visit the individual gem pages for more information.
|
528
293
|
|
529
294
|
## Development
|
530
295
|
|
@@ -532,8 +297,6 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
532
297
|
|
533
298
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
534
299
|
|
535
|
-
|
536
|
-
|
537
300
|
## Contributing
|
538
301
|
|
539
302
|
Bug reports and pull requests are welcome on GitHub at https://github.com/stevegeek/vident. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/vident/blob/master/CODE_OF_CONDUCT.md).
|
data/lib/vident/base.rb
CHANGED
@@ -145,6 +145,7 @@ module Vident
|
|
145
145
|
) + Array.wrap(options[:controllers]) + attribute(:controllers),
|
146
146
|
actions: attribute(:actions) + Array.wrap(options[:actions]),
|
147
147
|
targets: attribute(:targets) + Array.wrap(options[:targets]),
|
148
|
+
outlets: attribute(:outlets) + Array.wrap(options[:outlets]),
|
148
149
|
named_classes: merge_stimulus_option(options, :named_classes),
|
149
150
|
data_maps: prepare_stimulus_option(options, :data_maps)
|
150
151
|
}
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vident
|
4
|
+
# Rails fragment caching works by either expecting the cached key object to respond to `cache_key` or for that object
|
5
|
+
# to be an array or hash.
|
6
|
+
module Caching
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def inherited(subclass)
|
11
|
+
subclass.instance_variable_set(:@named_cache_key_attributes, @named_cache_key_attributes.clone)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_cache_key(*attrs, name: :_collection)
|
16
|
+
# Add view file to cache key
|
17
|
+
attrs << :component_modified_time
|
18
|
+
attrs << :attributes
|
19
|
+
named_cache_key_includes(name, *attrs.uniq)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :named_cache_key_attributes
|
23
|
+
|
24
|
+
# Components can be used with fragment caching, but you need to be careful! Read on...
|
25
|
+
#
|
26
|
+
# <% cache component do %>
|
27
|
+
# <%= render component %>
|
28
|
+
# <% end %>
|
29
|
+
#
|
30
|
+
# The most important point is that Rails cannot track dependencies on the component itself, so you need to
|
31
|
+
# be careful to be explicit on the attributes, and manually specify any sub Viewcomponent dependencies that the
|
32
|
+
# component has. The assumption is that the subcomponent takes any attributes from the parent, so the cache key
|
33
|
+
# depends on the parent component attributes. Otherwise changes to the parent or sub component views/Ruby class
|
34
|
+
# will result in different cache keys too. Of course if you invalidate all cache keys with a modifier on deploy
|
35
|
+
# then no need to worry about changing the cache key on component changes, only on attribute/data changes.
|
36
|
+
#
|
37
|
+
# A big caveat is that the cache key cannot depend on anything related to the view_context of the component (such
|
38
|
+
# as `helpers` as the key is created before the rending pipline is invoked (which is when the view_context is set).
|
39
|
+
def depends_on(*klasses)
|
40
|
+
@component_dependencies ||= []
|
41
|
+
@component_dependencies += klasses
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :component_dependencies
|
45
|
+
|
46
|
+
def component_modified_time
|
47
|
+
return @component_modified_time if Rails.env.production? && @component_modified_time
|
48
|
+
|
49
|
+
raise StandardError, "Must implement current_component_modified_time" unless respond_to?(:current_component_modified_time)
|
50
|
+
|
51
|
+
# FIXME: This could stack overflow if there are circular dependencies
|
52
|
+
deps = component_dependencies&.map(&:component_modified_time)&.join("-") || ""
|
53
|
+
@component_modified_time = deps + current_component_modified_time
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def named_cache_key_includes(name, *attrs)
|
59
|
+
define_cache_key_method unless @named_cache_key_attributes
|
60
|
+
@named_cache_key_attributes ||= {}
|
61
|
+
@named_cache_key_attributes[name] = attrs
|
62
|
+
end
|
63
|
+
|
64
|
+
def define_cache_key_method
|
65
|
+
# If the presenter defines cache key setup then define the method. Otherwise Rails assumes this
|
66
|
+
# will return a valid key if the class will respond to this
|
67
|
+
define_method :cache_key do |n = :_collection|
|
68
|
+
if defined?(@cache_key)
|
69
|
+
return @cache_key[n] if @cache_key.key?(n)
|
70
|
+
else
|
71
|
+
@cache_key ||= {}
|
72
|
+
end
|
73
|
+
generate_cache_key(n)
|
74
|
+
@cache_key[n]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Component modified time which is combined with other cache key attributes to generate cache key for an instance
|
80
|
+
def component_modified_time
|
81
|
+
self.class.component_modified_time
|
82
|
+
end
|
83
|
+
|
84
|
+
def cacheable?
|
85
|
+
respond_to? :cache_key
|
86
|
+
end
|
87
|
+
|
88
|
+
def cache_key_modifier
|
89
|
+
ENV["RAILS_CACHE_ID"]
|
90
|
+
end
|
91
|
+
|
92
|
+
def cache_keys_for_sources(key_attributes)
|
93
|
+
sources = key_attributes.flat_map { |n| n.is_a?(Proc) ? instance_eval(&n) : send(n) }
|
94
|
+
sources.compact.map do |item|
|
95
|
+
next if item == self
|
96
|
+
generate_item_cache_key_from(item)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def generate_item_cache_key_from(item)
|
101
|
+
if item.respond_to? :cache_key_with_version
|
102
|
+
item.cache_key_with_version
|
103
|
+
elsif item.respond_to? :cache_key
|
104
|
+
item.cache_key
|
105
|
+
elsif item.is_a?(String)
|
106
|
+
Digest::SHA1.hexdigest(item)
|
107
|
+
else
|
108
|
+
Digest::SHA1.hexdigest(Marshal.dump(item))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def generate_cache_key(index)
|
113
|
+
key_attributes = self.class.named_cache_key_attributes[index]
|
114
|
+
return nil unless key_attributes
|
115
|
+
key = "#{self.class.name}/#{cache_keys_for_sources(key_attributes).join("/")}"
|
116
|
+
raise StandardError, "Cache key for key #{key} is blank!" if key.blank?
|
117
|
+
@cache_key[index] = cache_key_modifier.present? ? "#{key}/#{cache_key_modifier}" : key
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/vident/component.rb
CHANGED
@@ -16,6 +16,7 @@ module Vident
|
|
16
16
|
attribute :controllers, default: [], delegates: false
|
17
17
|
attribute :actions, default: [], delegates: false
|
18
18
|
attribute :targets, default: [], delegates: false
|
19
|
+
attribute :outlets, default: [], delegates: false
|
19
20
|
attribute :data_maps, default: [], delegates: false
|
20
21
|
attribute :named_classes, delegates: false
|
21
22
|
end
|
@@ -6,6 +6,7 @@ module Vident
|
|
6
6
|
controllers: nil,
|
7
7
|
actions: nil,
|
8
8
|
targets: nil,
|
9
|
+
outlets: nil,
|
9
10
|
named_classes: nil, # https://stimulus.hotwired.dev/reference/css-classes
|
10
11
|
data_maps: nil,
|
11
12
|
element_tag: nil,
|
@@ -18,6 +19,7 @@ module Vident
|
|
18
19
|
@controllers = Array.wrap(controllers)
|
19
20
|
@actions = actions
|
20
21
|
@targets = targets
|
22
|
+
@outlets = outlets
|
21
23
|
@named_classes = named_classes
|
22
24
|
@data_map_kvs = {}
|
23
25
|
@data_maps = data_maps
|
@@ -123,11 +125,17 @@ module Vident
|
|
123
125
|
def tag_data_attributes
|
124
126
|
{controller: controller_list(@controllers), action: action_list(@actions)}
|
125
127
|
.merge!(target_list)
|
128
|
+
.merge!(outlet_list)
|
126
129
|
.merge!(named_classes_list)
|
127
130
|
.merge!(data_map_attributes)
|
128
131
|
.compact_blank!
|
129
132
|
end
|
130
133
|
|
134
|
+
def outlet_list
|
135
|
+
return {} unless @outlets&.size&.positive?
|
136
|
+
@outlets.each_with_object({}) { |o, obj| obj["#{implied_controller_name}-#{o.stimulus_identifier}-outlet"] = "[data-controller~=#{o.stimulus_identifier}]" }
|
137
|
+
end
|
138
|
+
|
131
139
|
# Actions can be specified as a symbol, in which case they imply an action on the primary
|
132
140
|
# controller, or as a string in which case it implies an action that is already fully qualified
|
133
141
|
# stimulus action.
|
@@ -173,7 +181,7 @@ module Vident
|
|
173
181
|
end
|
174
182
|
|
175
183
|
def build_target_data_attributes(targets)
|
176
|
-
targets.map { |t| ["#{t[:controller]}-target"
|
184
|
+
targets.map { |t| [:"#{t[:controller]}-target", t[:name]] }.to_h
|
177
185
|
end
|
178
186
|
|
179
187
|
def parse_actions(actions)
|
data/lib/vident/version.rb
CHANGED
data/sig/vident.rbs
ADDED
data/vident.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/vident/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "vident"
|
7
|
+
spec.version = Vident::VERSION
|
8
|
+
spec.authors = ["Stephen Ierodiaconou"]
|
9
|
+
spec.email = ["stevegeek@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Vident is the base of your design system implementation, which provides helpers for working with Stimulus. For component libraries with ViewComponent or Phlex."
|
12
|
+
spec.description = "Vident makes using Stimulus with your `ViewComponent` or `Phlex` view components as easy as writing Ruby. Vident is the base of your design system implementation, which provides helpers for working with Stimulus. For component libraries with ViewComponent or Phlex."
|
13
|
+
spec.homepage = "https://github.com/stevegeek/vident"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = spec.homepage + "/blob/main/CHANGELOG.md"
|
20
|
+
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
(File.expand_path(f) == __FILE__) ||
|
24
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ examples/ docs/ .git .github appveyor Gemfile])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
spec.add_dependency "railties", ">= 7", "< 8.0"
|
29
|
+
spec.add_dependency "activesupport", ">= 7", "< 8.0"
|
30
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vident
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Ierodiaconou
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: railties
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '7'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '8'
|
22
|
+
version: '8.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,27 @@ dependencies:
|
|
29
29
|
version: '7'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '8'
|
32
|
+
version: '8.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activesupport
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '7'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '8.0'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '7'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '8.0'
|
33
53
|
description: Vident makes using Stimulus with your `ViewComponent` or `Phlex` view
|
34
54
|
components as easy as writing Ruby. Vident is the base of your design system implementation,
|
35
55
|
which provides helpers for working with Stimulus. For component libraries with ViewComponent
|
@@ -40,17 +60,25 @@ executables: []
|
|
40
60
|
extensions: []
|
41
61
|
extra_rdoc_files: []
|
42
62
|
files:
|
63
|
+
- ".ruby-version"
|
64
|
+
- ".standard.yml"
|
65
|
+
- CHANGELOG.md
|
66
|
+
- CODE_OF_CONDUCT.md
|
67
|
+
- LICENSE.txt
|
43
68
|
- README.md
|
44
69
|
- Rakefile
|
45
70
|
- lib/tasks/vident_tasks.rake
|
46
71
|
- lib/vident.rb
|
47
72
|
- lib/vident/attributes/not_typed.rb
|
48
73
|
- lib/vident/base.rb
|
74
|
+
- lib/vident/caching.rb
|
49
75
|
- lib/vident/component.rb
|
50
76
|
- lib/vident/engine.rb
|
51
77
|
- lib/vident/root_component.rb
|
52
78
|
- lib/vident/stable_id.rb
|
53
79
|
- lib/vident/version.rb
|
80
|
+
- sig/vident.rbs
|
81
|
+
- vident.gemspec
|
54
82
|
homepage: https://github.com/stevegeek/vident
|
55
83
|
licenses:
|
56
84
|
- MIT
|
@@ -73,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
101
|
- !ruby/object:Gem::Version
|
74
102
|
version: '0'
|
75
103
|
requirements: []
|
76
|
-
rubygems_version: 3.
|
104
|
+
rubygems_version: 3.5.3
|
77
105
|
signing_key:
|
78
106
|
specification_version: 4
|
79
107
|
summary: Vident is the base of your design system implementation, which provides helpers
|