subcomponent 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: 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: []