vident 0.6.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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