vc_shortcut 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 121fe909a048b53b9e19a3add48e31fd050563cfd73aedcc58d4783b3326c8c3
4
+ data.tar.gz: 9c71c7101640ec293254a719734cf0cf635687827ad819d77a4fa8fbb679b8e9
5
+ SHA512:
6
+ metadata.gz: 1b58829cf8e0cda1a48130d87c1b35fa5708761afe9d9e6316cc072c22205797480c5a970bd2f1d3c7bf4661bb650e55060c7237613cc47f9688d1e5941ce52e
7
+ data.tar.gz: 46a9011945220b5e4eec8deab64fd3b2ef7774882b7a4a963e4e0cd2b9300f5555dd87e210a1960c4ec9ff1dd5e6523b73de5c3fe2924ca21abadc17ecbd6c94
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Owais
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # VcShortcut Gem
2
+
3
+ VcShortcut simplifies the API for rendering [ViewComponent](https://viewcomponent.org/)s and [Phlex](https://www.phlex.fun/) components in Ruby on Rails applications, reducing verbosity and streamlining component usage.
4
+
5
+ It also features caching for lookups, ensuring that it operates with minimal overhead and maximal speed.
6
+
7
+ ```erb
8
+ <%# Instead of: %>
9
+ <%= render Admin::Dashboard::TabsComponent.new(style: :compact) do |tabs| %>
10
+ <% tabs.with_tab('Sales') { ... } %>
11
+ <% tabs.with_divider %>
12
+ <% tabs.with_tab('Settings') { ... } %>
13
+ <% end %>
14
+ <%= render ButtonComponent.new('Sign up', '/sign_up') %>
15
+ <%= render Wysiwyg::Toolbar::FontSelectComponent.new %>
16
+
17
+ <%# You can now also do: %>
18
+ <%= vc.button('Sign up', '/sign_up') %>
19
+ <%= vc.wysiwyg.toolbar.font_select %>
20
+ <%= vc.admin.dashboard.tabs(style: :compact) do |tabs| %>
21
+ <% tabs.with_tab('Sales') { ... } %>
22
+ <% tabs.with_divider %>
23
+ <% tabs.with_tab('Settings') { ... } %>
24
+ <% end %>
25
+ ```
26
+
27
+ It works out of the box with just a `bundle add vc_shortcut`, and is also highly customizable.
28
+
29
+ By default, two shortcuts are set up: `vc` for rendering and `vci` for instantiating components. You can change the shortcut prefix.
30
+
31
+ For more advanced use-cases, it also allows you to change the lookup logic as well as setup custom shortcuts.
32
+
33
+ ## Installation
34
+
35
+ Add `vc_shortcut` to your Gemfile:
36
+
37
+ ```shell
38
+ bundle add vc_shortcut
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ If you're using ViewComponent or Phlex in your Rails app and your component class names end with `Component` or `::Component` (which should be the standard), there's nothing else you need to do.
44
+
45
+ By default, two shortcuts are set up: `vc` for rendering and `vci` for instantiating components.
46
+
47
+ You can start using these helpers from any views or from your components.
48
+
49
+ #### Example:
50
+
51
+ ```ruby
52
+ ### Instead of:
53
+ render Wysiwyg::UploadFieldComponent.new(limit: 50.megabytes) do |upload_field|
54
+ upload_field.with_footer do
55
+ render IconComponent.new('file')
56
+ end
57
+ end
58
+ ### You can now also do:
59
+ vc.wysiwyg.upload_field(limit: 50.megabytes) do |upload_field|
60
+ upload_field.with_footer do
61
+ vc.icon('file')
62
+ end
63
+ end
64
+
65
+ ### Instead of:
66
+ instance = ProgressBarComponent.new(progress: 75)
67
+ ### You can now also do:
68
+ instance = vci.progress_bar(progress: 75)
69
+ ```
70
+
71
+ <br/>
72
+ <br/>
73
+
74
+ ## Interested in a powerful Rails UI library?
75
+
76
+ I am working on a super-powerful Rails UI library - components as well as templates & patterns.
77
+
78
+ [Please check this out if you're interested](https://owaiskhan.me/rails-ui-library).
79
+ <br/>
80
+ <br/>
81
+ <br/>
82
+
83
+ ## Advanced
84
+
85
+ You can customize things by creating an initializer file at `config/initializers/vc_shortcut.rb`.
86
+
87
+ #### Customize name of default shortcuts
88
+
89
+ You can rename the default shortcuts or disable them entirely:
90
+
91
+ ```ruby
92
+ # In config/initializers/vc_shortcut.rb:
93
+ VcShortcut.render_shortcut = :vc # Or whatever you prefer
94
+ VcShortcut.instantiate_shortcut = :vci # Or whatever you prefer
95
+ ```
96
+
97
+ You can disable a shortcut by setting its value to `false`.
98
+
99
+ #### Custom Component Lookup
100
+
101
+ By default, we assume your component class names end with `Component` or `::Component`, which is the standard and should cover the majority of cases.
102
+
103
+ However, if you're doing something non-standard, you can customize the logic that finds a component:
104
+
105
+ ```ruby
106
+ # In config/initializers/vc_shortcut.rb:
107
+ VcShortcut.find_component = ->(camelized_name) {
108
+ "#{camelized_name}Component".safe_constantize || "#{camelized_name}::Component".safe_constantize
109
+ }
110
+ ```
111
+
112
+ For example, if all your components are namespaced under `Polaris` and are suffixed with `Primitive` instead of `Component`, you can set:
113
+ ```ruby
114
+ # In config/initializers/vc_shortcut.rb:
115
+ VcShortcut.find_component = ->(camelized_name) {
116
+ "Polaris::#{camelized_name}Primitive".safe_constantize
117
+ }
118
+ ## So instead of:
119
+ # `render Polaris::Admin::NavbarPrimitive.new`
120
+ # You can now also do:
121
+ # `vc.admin.navbar`
122
+ ```
123
+
124
+ ## Super Advanced
125
+
126
+ You can take customization one level further if needed:
127
+
128
+ #### Registering Additional Custom Shortcuts
129
+
130
+ ```ruby
131
+ # In config/initializers/vc_shortcut.rb:
132
+ VcShortcut.register :admin,
133
+ find_component: ->(camelized_name) {
134
+ "Ui::Admin::#{camelized_name}::Component".safe_constantize
135
+ },
136
+ process: ->(context) {
137
+ context.view_context.render(
138
+ context.component.new(*context.call_args, **context.call_kwargs),
139
+ &context.call_block
140
+ )
141
+ }
142
+ ## So instead of:
143
+ # `render Ui::Admin::Navbar::Component.new`
144
+ # You can now also do:
145
+ # `admin.navbar`
146
+ ```
147
+
148
+ #### Taking Full Control
149
+
150
+ You can customize the find process even more by specifying `find` instead of `find_component`:
151
+
152
+ ```ruby
153
+ # In config/initializers/vc_shortcut.rb:
154
+ VcShortcut.register :admin,
155
+ find: ->(context) {
156
+ # If you return :has_more, we'll assume there's another component coming up in the chain.
157
+ # If you return a non-nil value, we'll assume this is the leaf component and move on to call `process`.
158
+ # If you return nil, we'll assume nothing was found for the given chain and raise an error.
159
+ chain_camelized = context.chain_camelized
160
+ component = "Ui::#{chain_camelized}::Component".safe_constantize
161
+ next component if component
162
+ :has_more if chain_camelized.safe_constantize
163
+ },
164
+ process: ->(context) {
165
+ context.view_context.render(
166
+ context.component.new(*context.call_args, **context.call_kwargs),
167
+ &context.call_block
168
+ )
169
+ }
170
+ ## So instead of:
171
+ # `render Ui::Admin::Navbar::Component.new`
172
+ # You can now also do:
173
+ # `admin.navbar`
174
+ ```
175
+
176
+ The return value of the `register` call is a module that you can include in places where you wish to make the helper available. It's, however, automatically included for all your views, view components, and phlex components.
177
+
178
+ #### What is `context` in the above examples?
179
+
180
+ `context` is provided to the `find` and `process` proc. It's an object that responds to the following methods:
181
+
182
+ ##### 1. `context.chain`:
183
+ Chain until now. E.g., if you call `vc.admin.button`:
184
+ * When processing :admin, chain will be [:admin].
185
+ * Then, when processing :button, chain will be [:admin, :button].
186
+
187
+ ##### 2. `context.chain_camelized`:
188
+ Just does `context.chain.join('/').camelize`. So, if chain was [:admin, :buttton], it'd return `Admin::Button`.
189
+
190
+ ##### 3. `context.component`:
191
+ Only set when called in the `process` proc. It is the component returned by the `find` or `find_component` proc.
192
+
193
+ ##### 4. `context.call_args` `context.call_kwargs`, `context.call_block`:
194
+ Only set when called in the `process` proc. These are the arguments used when calling the shortcut. E.g.:
195
+ ```ruby
196
+ vc.admin.navbar('Treact', size: :sm, style: :compact) do |navbar|
197
+ navbar.with_menu_item(...)
198
+ end
199
+ ```
200
+ Here, `context.call_args` will be `['Treact']`, `context.call_kwargs` will be `{ size: :sm, style: :compact }` and `context.call_block` will be the block above.
201
+
202
+ ##### 5. `context.view_context`:
203
+
204
+ The view context when the shortcut was called. You can use it to render the component. E.g.:
205
+
206
+ ```ruby
207
+ instance = context.component.new(*context.call_args, **context.call_kwargs)
208
+ html = context.view_context.render(instance, &context.call_block)
209
+ ```
210
+
211
+
212
+ ## Contributing
213
+
214
+ Bug reports and pull requests are welcome on GitHub.
215
+
216
+ ## License
217
+
218
+ The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ module VcShortcut
2
+ class ChainContext
3
+ # The view_context (e.g an ActionView::Base or a ViewComponent instance)
4
+ attr_reader :view_context
5
+
6
+ # Chain until now. E.g if you call vc.admin.button
7
+ # When processing :admin, chain will be [:admin]
8
+ # Then, when processing :button, chain will be [:admin, :button]
9
+ attr_reader :chain
10
+
11
+ # The leaf component found by the `find` proc when registering a shortcut
12
+ attr_accessor :component
13
+
14
+ # Arguments for the leaf component
15
+ attr_accessor :call_args, :call_kwargs, :call_block
16
+
17
+ def initialize(view_context)
18
+ @view_context = view_context
19
+ @chain = []
20
+ @component = nil
21
+ @call_args = []
22
+ @call_kwargs = {}
23
+ @call_block = nil
24
+ end
25
+
26
+ def chain_camelized(rindex = -1)
27
+ @chain[0..rindex].join('/').camelize
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ module VcShortcut
2
+ class ChainManager
3
+ TREE = {}
4
+
5
+ def initialize(shortcut, process, find, view_context)
6
+ @tree = TREE[shortcut] ||= {}
7
+ @process = process
8
+ @find = find
9
+ @context = ChainContext.new(view_context)
10
+ end
11
+
12
+ def method_missing(method, *args, **kwargs, &block)
13
+ @context.chain << method
14
+
15
+ # If we haven't seen this path before, let's attempt constantization
16
+ if @tree[method].nil?
17
+ result = @find.call(@context)
18
+
19
+ if result == :has_more
20
+ # We matched with a module
21
+ @tree[method] = {}
22
+ elsif result
23
+ # We matched with a leaf
24
+ @tree[method] = result
25
+ else
26
+ raise NameError, "Cannot find a component or module matching. Chain: #{@context.chain.join('.')}"
27
+ end
28
+ end
29
+
30
+ leaf_or_subtree = @tree[method]
31
+ # We are at a module. Continue chaining
32
+ if leaf_or_subtree.is_a?(Hash)
33
+ @tree = leaf_or_subtree
34
+ return self
35
+ end
36
+
37
+ # Otherwise, we are at the leaf node (i.e a component)
38
+ @context.component = leaf_or_subtree
39
+ @context.call_args = args
40
+ @context.call_kwargs = kwargs
41
+ @context.call_block = block
42
+ @process.call(@context)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ module VcShortcut
2
+ class Railtie < ::Rails::Railtie
3
+ initializer :vc_shortcut do
4
+ config.after_initialize do
5
+ VcShortcut.define_render_shortcut
6
+ VcShortcut.define_instantiate_shortcut
7
+ end
8
+
9
+ config.to_prepare do
10
+ VcShortcut::ChainManager::TREE.clear
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ def self.define_render_shortcut
17
+ return unless VcShortcut.render_shortcut
18
+
19
+ VcShortcut.register(VcShortcut.render_shortcut,
20
+ process: ->(context) { context.view_context.render(context.component.new(*context.call_args, **context.call_kwargs), &context.call_block) }
21
+ )
22
+ end
23
+
24
+
25
+ def self.define_instantiate_shortcut
26
+ return unless VcShortcut.instantiate_shortcut
27
+
28
+ VcShortcut.register(VcShortcut.instantiate_shortcut,
29
+ process: ->(context) { context.component.new(*context.call_args, **context.call_kwargs) }
30
+ )
31
+ end
32
+ end
@@ -0,0 +1,53 @@
1
+ module VcShortcut
2
+ # @param [Symbol] shortcut What the shortcut helper is going to be called by
3
+ #
4
+ # @param [Proc] process Proc to process what to do with the leaf component. E.g instantiating a new class or rendering a component.
5
+ # It's invoked with a single arguments: An instance of VcHelper::Context. See it's doc for what's available.
6
+ #
7
+ # @param [Proc] find_component Proc to find the component class given the current camelized name
8
+ # Lower level than find. If you supply find_component, you shouldn't supply find.
9
+ # And vice-versa.
10
+ #
11
+ # @param [Proc] find Proc to find the component class given the current chain
12
+ # Optional. If you don't pass it, we'll try to camelize the chain, and try finding a component or module.
13
+ # You might want to define this if you have got some custom lookup logic.
14
+ # It's invoked with a single argument: An instance of VcHelper::Context. See it's doc for what's available
15
+ # If you return :has_more, we'll assume there's another component coming up in the chain
16
+ # If you return a non-nil value, we'll assume this is the leaf component.
17
+ # If you return nil, we'll assume we couldn't find any component for the given chain and raise an error
18
+ #
19
+ def self.register(shortcut, process:, find_component: nil, find: nil)
20
+ raise "You should only provide either find_component: or find:. Not both" if find_component && find
21
+ find_component ||= VcShortcut.find_component
22
+ find ||= ->(context) {
23
+ chain_camelized = context.chain_camelized
24
+ component = find_component.call(chain_camelized)
25
+ next component if component
26
+ :has_more if chain_camelized.safe_constantize
27
+ }
28
+
29
+ helper_module = Module.new do
30
+ define_method(shortcut) do |view_context = nil|
31
+ ChainManager.new(shortcut, process, find, view_context || self)
32
+ end
33
+ end
34
+
35
+ ActiveSupport.on_load(:action_view) do
36
+ include helper_module
37
+ end
38
+
39
+ ViewComponent::Base.class_eval do
40
+ define_method(shortcut) do
41
+ helpers.send(shortcut, self)
42
+ end
43
+ end if defined?(ViewComponent::Base)
44
+
45
+ Phlex::HTML.class_eval do
46
+ define_method(shortcut) do
47
+ helpers.send(shortcut, self)
48
+ end
49
+ end if defined?(Phlex::HTML)
50
+
51
+ helper_module
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module VcShortcut
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,18 @@
1
+ require "vc_shortcut/version"
2
+ require "vc_shortcut/chain_context"
3
+ require "vc_shortcut/chain_manager"
4
+ require "vc_shortcut/register"
5
+ require "vc_shortcut/railtie"
6
+
7
+ module VcShortcut
8
+ # If you've got a custom/non-standard lookup logic for you components, you can define that here.
9
+ mattr_accessor :find_component, default: ->(camelized_name) {
10
+ "#{camelized_name}Component".safe_constantize || "#{camelized_name}::Component".safe_constantize || (defined?(Phlex::HTML) && "#{camelized_name}View".safe_constantize)
11
+ }
12
+
13
+ # Configures the name of the shortcut for rendering a component. You can set it to false if you don't want to define one by default
14
+ mattr_accessor :render_shortcut, default: :vc
15
+
16
+ # Configures the name of the shortcut for instantiating a component. You can set it to false if you don't want to define one by default
17
+ mattr_accessor :instantiate_shortcut, default: :vci
18
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vc_shortcut
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Owais
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-02-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ description: A simpler and less verbose-y way to render view_components
28
+ email:
29
+ - owaiswiz@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/vc_shortcut.rb
38
+ - lib/vc_shortcut/chain_context.rb
39
+ - lib/vc_shortcut/chain_manager.rb
40
+ - lib/vc_shortcut/railtie.rb
41
+ - lib/vc_shortcut/register.rb
42
+ - lib/vc_shortcut/version.rb
43
+ homepage: https://github.com/owaiswiz/vc_shortcut
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ homepage_uri: https://github.com/owaiswiz/vc_shortcut
48
+ source_code_uri: https://github.com/owaiswiz/vc_shortcut
49
+ changelog_uri: https://github.com/owaiswiz/vc_shortcut/releases
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.4.10
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: A simpler and less verbose-y way to render view_components
69
+ test_files: []