vc_shortcut 0.1.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 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: []