vident 0.6.3 → 0.8.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34159659a1af3114685b2170e3c2f9667f1b87cc1a03082f99abb45d2b86bec4
4
- data.tar.gz: 4bf98392df9f0f4c531930bcd26245c91287d48065941374eb4715ccfa15d439
3
+ metadata.gz: 6907ba81655f5cf18c500edcdfa43c8786d8b2463573a1dadd0dd51ba1637ea3
4
+ data.tar.gz: 72964008e3a0c64172726f4edfe2ba030f66018a08713e8bad529eae4dda45d7
5
5
  SHA512:
6
- metadata.gz: d5826f1ccef2cbfec3fe8fbf717e81441c438c5091d0ff60ff48a84272700bef7c4fdc33b9f51e4a6293cecae8d0d59802b03667d11eb8fd3c69ff25ca88d552
7
- data.tar.gz: bd33d01fef040b09f5ca97a259d4e2f035d4fb4aead3c7a4115f758423ec247b676b8f37eee5b1a96fe8ba2dbb20dd0498c9e68fb2de972f3dd93dc9eca7885a
6
+ metadata.gz: dea9a3d4e70478870b829fe050cfbbcd89dad15866a4a4334d8661d217f56e30a47b81285f279008bfaf762a406e7356ca33d56c607e98a34ddde6b2bf26df13
7
+ data.tar.gz: ea963a8ba620e0c510c8e4fadf02eb0293159398c576dbd9554bffc01fd7bb930e7b9d31e2011710ab7a4c41a2929a2e8964cceee2bdfe35827a8dedcad148b4
data/README.md CHANGED
@@ -1,33 +1,30 @@
1
1
  # Vident
2
2
 
3
- Vident helps you create flexible & maintainable component libraries for your application.
3
+ **Vident** is a collection of gems that help you create **flexible** & **maintainable** component libraries for your Rails application.
4
4
 
5
- Vident makes using Stimulus with your [`ViewComponent`](https://viewcomponent.org/) or [`Phlex`](https://phlex.fun) components easier.
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>
6
6
 
7
- ## Things still to do...
7
+ Vident also provides a neat Ruby DSL to make wiring up **Stimulus easier & less error prone** in your view components.
8
8
 
9
- This is a work in progress. Here's what's left to do for first release:
10
-
11
- - Iterate on the interfaces and functionality
12
- - Add tests
13
- - Make the gem more configurable to fit more use cases
14
- - Create an example library of a few components for some design system
15
- - Create a demo app with `lookbook` and those components
16
- - Add more documentation
17
- - split `vident` into `vident` + `vident-rails` gems (and maybe `vident-rspec`) (Phlex can be used outside of Rails)
18
- - possibly also split into `vident-phlex` and `vident-view_component` gems ?
9
+ [`ViewComponent`](https://viewcomponent.org/) and [`Phlex`](https://phlex.fun) supported.
19
10
 
20
11
  # Motivation
21
12
 
22
13
  I love working with Stimulus, but I find manually crafting the data attributes for
23
14
  targets and actions error prone and tedious. Vident aims to make this process easier
24
- and keep me thinking in Ruby.
15
+ and keep me thinking in Ruby.
25
16
 
26
- I have been using Vident with `ViewComponent` in production apps for a while now and it has been constantly
27
- evolving.
17
+ I have been using Vident with `ViewComponent` in production apps for a while now (and recently `Phlex`!)
18
+ and it has been constantly evolving.
28
19
 
29
20
  This gem is a work in progress and I would love to get your feedback and contributions!
30
21
 
22
+
23
+
24
+ **The docs below need updating**
25
+
26
+
27
+
31
28
  ## What does Vident provide?
32
29
 
33
30
  - `Vident::Component`: A mixin for your `ViewComponent` components or `Phlex` components that provides the a helper to create the
@@ -35,10 +32,51 @@ This gem is a work in progress and I would love to get your feedback and contrib
35
32
 
36
33
  - `Vident::TypedComponent`: like `Vident::Component` but uses `dry-types` to define typed attributes for your components.
37
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
+
38
43
  - `Vident::RootComponent::*` which are components for creating the 'root' element in your view components. Similar to `Primer::BaseComponent` but
39
44
  exposes a simple API for configuring and adding Stimulus controllers, targets and actions. Normally you create these
40
45
  using the `root` helper method on `Vident::Component`/`Vident::TypedComponent`.
41
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
+
42
80
 
43
81
  # Examples
44
82
 
@@ -58,7 +96,141 @@ rails s
58
96
 
59
97
  and visit http://localhost:3000
60
98
 
61
- ## ViewComponent + Vident example
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.
107
+
108
+ ```ruby
109
+ class AvatarComponent < ViewComponent::Base
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 %>
193
+
194
+ ```
195
+
196
+ Example usages:
197
+
198
+ ```erb
199
+ <!-- These will render -->
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
+ ```
222
+
223
+ The following is rendered when used `render AvatarComponent.new(url: "https://i.pravatar.cc/300", initials: "AB", html_options: {alt: "My alt text", class: "block"})`:
224
+
225
+ ```html
226
+ <img src="https://i.pravatar.cc/300" alt="My alt text" class="avatar-component w-10 h-10 rounded-full object-contain block" id="avatar-component-7083941-11">
227
+ ```
228
+
229
+ ----
230
+
231
+ ![Example](examples/avatar.png)
232
+
233
+ ## Another ViewComponent + Vident example with Stimulus
62
234
 
63
235
  Consider the following ERB that might be part of an application's views. The app uses `ViewComponent`, `Stimulus` and `Vident`.
64
236
 
@@ -73,7 +245,7 @@ used to greet the user. At the same time the button changes to be a 'reset' butt
73
245
  <!-- ... -->
74
246
 
75
247
  <!-- render the Greeter ViewComponent (that uses Vident) -->
76
- <%= render ::GreeterComponent.new do |greeter| %>
248
+ <%= render ::GreeterComponent.new(cta: "Hey!", html_options: {class: "my-4"}) do |greeter| %>
77
249
  <%# this component has a slot called `trigger` that renders a `ButtonComponent` (which also uses Vident) %>
78
250
  <% greeter.trigger(
79
251
 
@@ -100,7 +272,7 @@ used to greet the user. At the same time the button changes to be a 'reset' butt
100
272
  The output HTML of the above, using Vident, is:
101
273
 
102
274
  ```html
103
- <div class="greeter-component"
275
+ <div class="greeter-component py-2 my-4"
104
276
  data-controller="greeter-component"
105
277
  data-greeter-component-pre-click-class="text-md text-gray-500"
106
278
  data-greeter-component-post-click-class="text-xl text-blue-700"
@@ -113,7 +285,7 @@ The output HTML of the above, using Vident, is:
113
285
  data-action="click->greeter-component#greet button-component#changeMessage"
114
286
  data-button-component-after-clicked-message="Greeted! Reset?"
115
287
  data-button-component-before-clicked-message="Greet"
116
- id="button-component-7799479-7">Greet</button>
288
+ id="button-component-7799479-7">Hey!</button>
117
289
  <!-- you can also use the `target_tag` helper to render targets -->
118
290
  <span class="ml-4 text-md text-gray-500"
119
291
  data-greeter-component-target="output">
@@ -144,7 +316,8 @@ end
144
316
 
145
317
  <%= render root named_classes: {
146
318
  pre_click: "text-md text-gray-500", # named classes are exposed to Stimulus as `data-<controller>-<name>-class` attributes
147
- post_click: "text-xl text-blue-700"
319
+ post_click: "text-xl text-blue-700",
320
+ html_options: {class: "py-2"}
148
321
  } do |greeter| %>
149
322
 
150
323
  <%# `greeter` is the root element and exposes methods to generate stimulus targets and actions %>
@@ -257,31 +430,44 @@ export default class extends Controller {
257
430
 
258
431
  ## Installation
259
432
 
260
- Add this line to your application's Gemfile:
433
+ This gem (`vident`) provides only base functionality but there are a number of gems that provide additional functionality
434
+ or a "out of the box" experience.
261
435
 
262
- ```ruby
263
- gem 'vident'
264
- ```
436
+ It's a "pick your own adventure" approach. You decide what frameworks and features you want to use
437
+ and add the gems as needed.
265
438
 
266
- Also ensure you have installed your chosen view component library, eg:
439
+ Start by asking yorself these questions:
267
440
 
268
- ```ruby
269
- gem 'view_component'
270
- ```
441
+ _Q1. Do you want to use [`ViewComponent`](https://viewcomponent.org/) or [`Phlex`](https://www.phlex.fun/) for your view components?_
271
442
 
272
- or
443
+ _Q2. Do you want to use attributes with runtime type checking (powered by [`dry-types`](https://github.com/dry-rb/dry-types)), or not?_
273
444
 
274
- ```ruby
275
- gem 'phlex' # Must be version 0.5 or higher
276
- ```
445
+ Depending on your answer to Q1, you can choose from either the `*-view_component` or `*-phlex` gems,
446
+ then depending on your answer to Q2, you can choose from either the untyped or `*-typed-*` gems.
277
447
 
278
- or **both**!
448
+ Note you can also use some or all of them in the same app.
279
449
 
280
- If you want to use typed attributes you must also include `dry-struct`
450
+ - [`vident-view_component`](https://github.com/stevegeek/vident-view_component) for using with `ViewComponent` and untyped attributes
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
454
+
455
+ _Q3. Do you use or want to use [BetterHTML](https://github.com/Shopify/better-html) in your Rails project?_
456
+
457
+ 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.
458
+ Note that `vident-better_html` automatically enables `better_html` support in Vident root components.
459
+
460
+ _Q4. Do you use or want to use [TailwindCSS](https://tailwindcss.com/)?_
461
+
462
+ If yes, then consider adding [`vident-tailwind`](https://github.com/stevegeek/vident-tailwind) to your Gemfile alongside your vident gems of choice. When creating
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/).
464
+
465
+ Finally if none of the above gems suit your needs, you can always just use base `vident` gems to roll your own
466
+ solution:
467
+
468
+ - [`vident`](https://github.com/stevegeek/vident) to get the base functionality to mix with your own view component system
469
+ - [`vident-typed`](https://github.com/stevegeek/vident-typed) to define typed attributes for your own view component system
281
470
 
282
- ```ruby
283
- gem 'dry-struct'
284
- ```
285
471
 
286
472
  And then execute:
287
473
 
data/Rakefile CHANGED
@@ -1,14 +1,3 @@
1
- # frozen_string_literal: true
1
+ require "bundler/setup"
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
10
- end
11
-
12
- require "standard/rake"
13
-
14
- task default: %i[test standard]
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :vident do
3
+ # # Task goes here
4
+ # end
@@ -15,6 +15,7 @@ module Vident
15
15
  options = self.class.attribute_options
16
16
  default = options&.dig(attr_name, :default)
17
17
  allow_nil = options[attr_name] ? options[attr_name].fetch(:allow_nil, true) : true
18
+
18
19
  if attributes&.include? attr_name
19
20
  value = attributes[attr_name]
20
21
  @__attributes[attr_name] = (value.nil? && default) ? default : value
@@ -41,6 +42,8 @@ module Vident
41
42
  class_methods do
42
43
  def inherited(subclass)
43
44
  subclass.instance_variable_set(:@attribute_ivar_names, @attribute_ivar_names.clone)
45
+ subclass.instance_variable_set(:@attribute_names, @attribute_names.clone)
46
+ subclass.instance_variable_set(:@attribute_options, @attribute_options.clone)
44
47
  super
45
48
  end
46
49
 
data/lib/vident/base.rb CHANGED
@@ -33,15 +33,7 @@ module Vident
33
33
  end
34
34
 
35
35
  def identifier_name_path
36
- if phlex_component?
37
- name.remove("Views::").underscore
38
- else
39
- name.underscore
40
- end
41
- end
42
-
43
- def phlex_component?
44
- @phlex_component ||= ancestors.map(&:name).include?("Phlex::HTML")
36
+ name.underscore
45
37
  end
46
38
 
47
39
  private
@@ -52,7 +44,7 @@ module Vident
52
44
  def #{attr_name}
53
45
  #{@attribute_ivar_names[attr_name]}
54
46
  end
55
-
47
+
56
48
  def #{attr_name}?
57
49
  #{@attribute_ivar_names[attr_name]}.present?
58
50
  end
@@ -95,25 +87,6 @@ module Vident
95
87
 
96
88
  # HTML and attribute definition and creation
97
89
 
98
- # Helper to create the main element
99
- def parent_element(**options)
100
- @parent_element ||= begin
101
- # Note: we cant mix phlex and view_component render contexts
102
- klass = if self.class.phlex_component?
103
- RootComponent::UsingPhlexHTML
104
- else
105
- RootComponent::UsingViewComponent
106
- end
107
- element_attrs = options
108
- .except(:id, :element_tag, :html_options, :controller, :controllers, :actions, :targets, :named_classes, :data_maps)
109
- .merge(
110
- stimulus_options_for_component(options)
111
- )
112
- klass.new(**element_attrs)
113
- end
114
- end
115
- alias_method :root, :parent_element
116
-
117
90
  # FIXME: if we call them before `root` we will setup the root element before we intended
118
91
  # The separation between component and root element is a bit messy. Might need rethinking.
119
92
  delegate :action, :target, :named_classes, to: :root
@@ -155,6 +128,12 @@ module Vident
155
128
 
156
129
  private
157
130
 
131
+ def parent_element_attributes(options)
132
+ options
133
+ .except(:id, :element_tag, :html_options, :controller, :controllers, :actions, :targets, :named_classes, :data_maps)
134
+ .merge(stimulus_options_for_component(options))
135
+ end
136
+
158
137
  # Prepare the stimulus attributes for a StimulusComponent
159
138
  def stimulus_options_for_component(options)
160
139
  {
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./attributes/not_typed"
4
-
5
3
  module Vident
6
4
  module Component
7
5
  extend ActiveSupport::Concern
@@ -0,0 +1,15 @@
1
+ module Vident
2
+ class Engine < ::Rails::Engine
3
+ lib_path = File.expand_path("../../../lib/", __FILE__)
4
+ config.autoload_paths << lib_path
5
+ config.eager_load_paths << lib_path
6
+
7
+ config.before_initialize do
8
+ Rails.autoloaders.each do |autoloader|
9
+ autoloader.inflector.inflect(
10
+ "version" => "VERSION"
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end