subcomponent 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: 0e45581419c661089fedb66b9c803a42f9edc3554585c24ddc0b8fa97e26a448
4
+ data.tar.gz: e5863ed447bf64a00b37c50afb8d22ae68020ef3c6887f0872edcf1d42c0d463
5
+ SHA512:
6
+ metadata.gz: ca8c18a231cfc387a1545e5c894903890396365d1776d6432c2d7aa702ec0e07b4e7979934476fc1b77099837349b407a5fac4888dcba4fdcd65b30c3e598d24
7
+ data.tar.gz: d8644df39b061f70c0213449f6b34e9b0f94f7cd69f8ecaa5b61388ca6cd9888e3e09e038b6b35c2631c5472b9f5dcaa66ffb8a399a356685623199b37fc83e1
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 candland
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,139 @@
1
+ # Subcomponent
2
+
3
+ When you want to create HTML simple components, using partials.
4
+
5
+ ## Usage
6
+
7
+ Components are stored in `app/views/components` and are named `_component.html.erb`.
8
+ They can also be stored in a subdirectory of `app/views/components` and are named using the component name for the directory and the partial `component/_component.html.erb`.
9
+
10
+ Additional subcomponent partials can be included in the component directory allowing
11
+ to break up the component into smaller pieces.
12
+
13
+ ### Example Simple Component
14
+
15
+ Create a button component in `app/views/components/_button.html.erb`:
16
+ ```erb
17
+ <button>
18
+ <%= this.text %>
19
+ </button>
20
+ ```
21
+
22
+ Use `this` to access the component instance containing locals and subcomponents.
23
+
24
+ To use the component in a view:
25
+ ```erb
26
+ <%= component :button, text: 'Click Me' %>
27
+ ```
28
+
29
+ Using with the method_missing shorthand:
30
+ ```erb
31
+ <%= button text: 'Click Me' %>
32
+ ```
33
+
34
+ Locals can also be set within the component block:
35
+ ```erb
36
+ <%= component :button do |c| %>
37
+ <% c.text = 'Click Me' %>
38
+ <% end %>
39
+ ```
40
+
41
+ ### Example Component with Subcomponents
42
+
43
+ Create a card component in `app/views/components/card/_card.html.erb`:
44
+ ```erb
45
+ <div>
46
+ <%= this.render :title %>
47
+ <%= this.text || this.yield %>
48
+ </div>
49
+ ```
50
+
51
+ Create the title subcomponent in `app/views/components/card/_title.html.erb`:
52
+ ```erb
53
+ <h1>
54
+ <%= this.text %>
55
+ </h1>
56
+ ```
57
+
58
+ You can use `this.render` to render a subcomponent. The subcomponent will have access
59
+ to the locals and subcomponents passed in the view.
60
+
61
+ You can use `this.yield` to render the block passed in the view.
62
+
63
+ To use the component in a view:
64
+ ```erb
65
+ <%= component :card do |c| %>
66
+ <%= c.title text: 'Card Title' %>
67
+ <p>Card Text</p>
68
+ <% end %>
69
+ ```
70
+
71
+ This will render:
72
+ ```html
73
+ <div>
74
+ <h1>Card Title</h1>
75
+ <p>Card Text</p>
76
+ </div>
77
+ ```
78
+
79
+ ### Example Using Locals without method_missing.
80
+
81
+ In some cases you may want to use a local variable that
82
+ clashes with existing methods on `Object`. In these cases you can access
83
+ the locals hash directly.
84
+ ```erb
85
+ <h1>
86
+ <%= this.local(:method) %>
87
+ </h1>
88
+ ```
89
+
90
+ ### Example Using Subcomponents without method_missing.
91
+
92
+ In some cases you may want to use a subcomponent that
93
+ clashes with existing methods on `Object`. In these cases you can access
94
+ the subcomponents hash directly.
95
+ ```erb
96
+ <%= this.components(:method) %>
97
+ ```
98
+
99
+ This returns and Array of subcomponents. You can render the first subcomponent with
100
+ a given key using, `this.render(:header)`.
101
+ ```erb
102
+ <%= this.render(:header) %>
103
+ ```
104
+
105
+ If you have multiple subcomponents with the same key, you can render them all using
106
+ `this.render_all(:header)`.
107
+ ```erb
108
+ <%= this.render_all(:header) %>
109
+ ```
110
+
111
+ If you want to render all subcomponents, but with HTML between them,
112
+ you can use `this.components(:actions)`.
113
+ ```erb
114
+ <ul>
115
+ <% this.components(:actions).each do |sub| %>
116
+ <li><%= sub.render %></li>
117
+ <% end %>
118
+ </ul>
119
+ ```
120
+
121
+ ## Installation
122
+
123
+ Add this line to your application's Gemfile:
124
+ ```bash
125
+ $ bundle add subcomponent
126
+ ```
127
+
128
+ Or install it yourself as:
129
+ ```bash
130
+ $ gem install subcomponent
131
+ ```
132
+
133
+ ## Contributing
134
+
135
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/candland/subcomponent](https://github.com/candland/subcomponent).
136
+
137
+ ## License
138
+
139
+ 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,59 @@
1
+ module ComponentHelper
2
+ # Create a component in a view.
3
+ #
4
+ # @param [Symbol] name The name of the component.
5
+ # @param [Hash] kwargs The locals to pass to the component.
6
+ # @param [Proc] block The block to pass to the component. You can use
7
+ # other components inside this block. You can set locals inside this
8
+ # block.
9
+ #
10
+ # @example
11
+ #
12
+ # = component :header do |c|
13
+ # - c.title = "Hello"
14
+ # p My Extra Content
15
+ #
16
+ def component name, **kwargs, &block
17
+ component = Component.new(name, kwargs, lookup_context, nil, block)
18
+
19
+ component._renderer = proc do |partial, locals, captured|
20
+ render partial, locals do
21
+ captured
22
+ end
23
+ end
24
+
25
+ component._capture = proc do |component, block|
26
+ if block
27
+ capture do
28
+ block.call(component)
29
+ end
30
+ end
31
+ end
32
+
33
+ component._capture_self
34
+ component._yield_renderer
35
+ end
36
+
37
+ # Alias for component.
38
+ alias_method :comp, :component
39
+
40
+ # Create a component in a view with shorthand syntax.
41
+ # This is the same as calling component with the same arguments.
42
+ #
43
+ # @example
44
+ #
45
+ # = header do |c|
46
+ # - c.title = "Hello"
47
+ #
48
+ def method_missing symbol, *args, **kwargs, &block
49
+ super unless respond_to?(symbol)
50
+
51
+ component symbol, *args, **kwargs, &block
52
+ end
53
+
54
+ # :nodoc:
55
+ def respond_to_missing? symbol, *args
56
+ lookup_context.exists?("components/#{symbol}/#{symbol}", [], true) ||
57
+ lookup_context.exists?("components/#{symbol}", [], true)
58
+ end
59
+ end
@@ -0,0 +1,209 @@
1
+ class Component
2
+ attr_accessor :_renderer
3
+ attr_accessor :_capture
4
+
5
+ # Use Component::ComponentHelper to create components.
6
+ def initialize name, locals, lookup_context, parent, block
7
+ @_name = name
8
+ @_parent = parent
9
+ @_locals = locals
10
+ @_lookup_context = lookup_context
11
+ @_block = block
12
+
13
+ @_components = {}
14
+ @_building = false
15
+ end
16
+
17
+ # Specify and use local values or sub-components using method calls.
18
+ #
19
+ # == Locals
20
+ #
21
+ # @example
22
+ #
23
+ # = component :header do |c|
24
+ # c.title = "Hello"
25
+ #
26
+ # In the component:
27
+ #
28
+ # @example
29
+ #
30
+ # = this.title
31
+ #
32
+ # @return "Hello"
33
+ #
34
+ # == Sub-components
35
+ #
36
+ # @example
37
+ #
38
+ # = component :card do |c|
39
+ # - c.header do |c|
40
+ # - c.title = "Hello"
41
+ #
42
+ # In the component:
43
+ #
44
+ # @example
45
+ #
46
+ # = this.render :header
47
+ #
48
+ # = this.header.render
49
+ #
50
+ # @return The rendered component
51
+ #
52
+ # == Checking for locals or sub-components
53
+ #
54
+ # @example
55
+ #
56
+ # = this.title?
57
+ # = this.header?
58
+ #
59
+ # @return true or false
60
+ #
61
+ def method_missing symbol, *args, **kwargs, &block
62
+ # This is used when building a component
63
+ if _building && symbol != :to_ary
64
+ if symbol.ends_with?("=") && args.length == 1
65
+ _locals[symbol[0..-2].to_sym] = args.first
66
+ else
67
+ _components[symbol] ||= []
68
+
69
+ child = Component.new(symbol, args.first || kwargs, _lookup_context, self, block)
70
+ child._renderer = _renderer
71
+ child._capture = _capture
72
+ child._capture_self
73
+
74
+ _components[symbol] << child
75
+ end
76
+ nil
77
+
78
+ # This is used when rendering a component
79
+ elsif symbol.ends_with?("?")
80
+ _locals.key?(symbol[0..-2].to_sym) || _components.key?(symbol[0..-2].to_sym)
81
+ else
82
+ _locals[symbol] || _components[symbol]
83
+ end
84
+ end
85
+
86
+ # :nodoc:
87
+ def respond_to_missing? symbol, *args
88
+ if symbol != :to_ary
89
+ true
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ # This is used to require local keys or sub-components.
96
+ #
97
+ # @param [Array<Symbol>] loacl_keys
98
+ #
99
+ # require :title, :body
100
+ #
101
+ def require *local_keys
102
+ missing = local_keys.reject { |k| _locals.key?(k) || _components.key?(k) }
103
+ if missing.count > 0
104
+ raise "The #{_name} component requires #{missing.join(", ")} local(s) or component(s)."
105
+ end
106
+ nil
107
+ end
108
+
109
+ # This is used to access sub-components passed to the component.
110
+ #
111
+ # @param [Symbol] key The name of the sub-component
112
+ #
113
+ # @example
114
+ #
115
+ # components :header
116
+ #
117
+ # returns: [<Component>, <Component>, ...]
118
+ #
119
+ def components key
120
+ _components[key] || []
121
+ end
122
+
123
+ # This is used to access locals passed to the component.
124
+ def local key
125
+ _locals[key]
126
+ end
127
+
128
+ # This is used to render a sub-component.
129
+ #
130
+ # From within a component:
131
+ #
132
+ # this.render :header
133
+ #
134
+ # Or calling directly on a sub-component:
135
+ #
136
+ # this.header.render
137
+ #
138
+ def render symbol = nil
139
+ if symbol.nil?
140
+ if _parent.nil?
141
+ raise "Cannot render a component without a symbol when it has a parent."
142
+ else
143
+ return _yield_renderer
144
+ end
145
+ end
146
+ _components[symbol]&.first&._yield_renderer
147
+ end
148
+
149
+ # Render all sub-components of a given name.
150
+ #
151
+ # this.render_all :header
152
+ #
153
+ # Returns a string of all rednered sub-components.
154
+ #
155
+ def render_all symbol
156
+ _components[symbol]&.map(&:_yield_renderer)&.join&.html_safe
157
+ end
158
+
159
+ # Yield the block passed to the component.
160
+ #
161
+ # this.yield
162
+ #
163
+ def yield
164
+ _captured
165
+ end
166
+
167
+ # :nodoc:
168
+ def _yield_renderer
169
+ locals = _locals.merge(this: self)
170
+ _renderer.call(_partial, locals, _captured)
171
+ end
172
+
173
+ # :nodoc:
174
+ def _capture_self
175
+ self._building = true
176
+ self._captured = _capture.call(self, _block)
177
+ ensure
178
+ self._building = false
179
+ end
180
+
181
+ protected
182
+
183
+ attr_reader :_name
184
+ attr_reader :_locals
185
+ attr_reader :_components
186
+ attr_reader :_block
187
+ attr_reader :_parent
188
+ attr_accessor :_captured
189
+ attr_reader :_lookup_context
190
+ attr_accessor :_building
191
+
192
+ # :nodoc:
193
+ def _base_name
194
+ on = self
195
+ until on._parent.nil?
196
+ on = on._parent
197
+ end
198
+ on._name
199
+ end
200
+
201
+ # :nodoc:
202
+ def _partial
203
+ @partial ||= if _lookup_context.exists?("components/#{_base_name}/#{_name}", [], true)
204
+ "components/#{_base_name}/#{_name}"
205
+ else
206
+ "components/#{_name}"
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,9 @@
1
+ module Subcomponent
2
+ class Railtie < ::Rails::Railtie
3
+ initializer "subcomponent.helper" do
4
+ ActiveSupport.on_load(:action_view) do
5
+ include ComponentHelper
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Subcomponent
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "subcomponent/version"
2
+ require "subcomponent/railtie"
3
+ require "subcomponent/component"
4
+ require_relative "../app/helpers/component_helper"
5
+
6
+ module Subcomponent
7
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :subcomponent do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: subcomponent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - candland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-08 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: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ description: Very simple parial based components.
28
+ email:
29
+ - candland@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - app/helpers/component_helper.rb
38
+ - lib/subcomponent.rb
39
+ - lib/subcomponent/component.rb
40
+ - lib/subcomponent/railtie.rb
41
+ - lib/subcomponent/version.rb
42
+ - lib/tasks/subcomponent_tasks.rake
43
+ homepage: https://candland.net/subcomponent
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ homepage_uri: https://candland.net/subcomponent
48
+ source_code_uri: https://github.com/candland/subcomponent
49
+ changelog_uri: https://github.com/candland/subcomponent/CHANGELOG.md
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.3.7
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Very simple parial based components
69
+ test_files: []