rails_components 0.0.0 → 0.0.1
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 +4 -4
- data/README.md +219 -21
- data/lib/rails_components/configuration.rb +22 -0
- data/lib/rails_components/html_helpers.rb +8 -0
- data/lib/rails_components.rb +22 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f8e93ccb73397896fa9905a032a2134c1c0de2c
|
4
|
+
data.tar.gz: af568ee8b46b7e64aec5c6c1491d0984239a3ac8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a407dcd0bd60b1a211966988fbf454eb44ec3fa97e4d435c2ca6b43f3331bc624b6cce1dec52ca43d867cbb7e9a6d69f98e3e0be4f23f5bdeb1acbec80595fb
|
7
|
+
data.tar.gz: a34cbac6e4c88caebcbf49f46defcba19ba7a97ee57a7858d7e5b6ac4c97f9e443b9eed7484f692afbbee2e8d6a3d25ed90c7ba59bedfabb59235ba15d710975
|
data/README.md
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# Rails Components
|
2
2
|
|
3
|
-
|
3
|
+
write reusable components in your rails views. a thin wrapper around `render`
|
4
|
+
that makes passing blocks, and html attributes to templates simple as pie.
|
4
5
|
|
5
6
|
## Installation
|
6
7
|
|
7
|
-
|
8
|
+
get the gem
|
8
9
|
|
9
10
|
```rb
|
10
11
|
gem 'rails_components'
|
11
12
|
```
|
12
13
|
|
13
|
-
|
14
|
+
and make the `component` helper available in your views
|
14
15
|
|
15
16
|
```rb
|
16
17
|
module ApplicationHelper
|
@@ -18,48 +19,245 @@ module ApplicationHelper
|
|
18
19
|
end
|
19
20
|
```
|
20
21
|
|
22
|
+
## Usage
|
21
23
|
|
22
|
-
|
24
|
+
a component is a template
|
23
25
|
|
24
|
-
|
26
|
+
```erb
|
27
|
+
<!-- app/views/components/_my_cool_component.html.erb -->
|
28
|
+
<div class="my-cool-component">Hello!</div>
|
29
|
+
```
|
30
|
+
|
31
|
+
a component can yield, and you can pass a block to it
|
32
|
+
|
33
|
+
```erb
|
34
|
+
<!-- app/views/components/_my_cool_component.html.erb -->
|
35
|
+
<div class="my-cool-component"><%= yield %></div>
|
36
|
+
```
|
25
37
|
|
26
38
|
```erb
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
<%= yield %>
|
32
|
-
</div><!-- /.modal-content -->
|
33
|
-
</div><!-- /.modal-dialog -->
|
34
|
-
</div><!-- /.modal -->
|
39
|
+
<!-- in a view -->
|
40
|
+
<h1>My website!</h1>
|
41
|
+
<%= component 'my_cool_component' do %>
|
42
|
+
<p>It's great.</p>
|
35
43
|
<% end %>
|
36
44
|
```
|
37
45
|
|
46
|
+
or pass it an argument instead of a block, like `link_to` or `content_tag`
|
47
|
+
|
38
48
|
```erb
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
<!-- in a view -->
|
50
|
+
<%= component 'my_cool_component', "It's great." %>
|
51
|
+
```
|
52
|
+
|
53
|
+
they live in `app/views/components` by default.
|
54
|
+
|
55
|
+
components have a special method: `props`. `props` is the same as the
|
56
|
+
template's local variables, except it includes reserved words.
|
57
|
+
This makes it useful for passing html attributes like `class`.
|
58
|
+
|
59
|
+
```erb
|
60
|
+
<!-- component -->
|
61
|
+
<%= content_tag :div, props do %>
|
62
|
+
<%= yield %>
|
63
|
+
<% end %>
|
64
|
+
|
65
|
+
<!-- in a view -->
|
66
|
+
<%= component 'box', class: "box", data: { foo: 'bar' } do %>
|
67
|
+
<p>my box!</p>
|
68
|
+
<% end %>
|
69
|
+
```
|
70
|
+
|
71
|
+
```html
|
72
|
+
<!-- output -->
|
73
|
+
<div class="box" data-foo="bar">
|
74
|
+
<p>my box!</p>
|
42
75
|
</div>
|
43
76
|
```
|
44
77
|
|
78
|
+
`props` has a method called `html` which combines it with additional html attributes:
|
79
|
+
|
45
80
|
```erb
|
46
|
-
|
81
|
+
<!-- component -->
|
82
|
+
<%= content_tag :div, props.html(class: "box") do %>
|
47
83
|
<%= yield %>
|
84
|
+
<% end %>
|
85
|
+
|
86
|
+
<!-- in a view -->
|
87
|
+
<%= component 'box', class: "big" do %>
|
88
|
+
<p>my big box!</p>
|
89
|
+
<% end %>
|
90
|
+
```
|
91
|
+
|
92
|
+
```html
|
93
|
+
<!-- output -->
|
94
|
+
<div class="big box">
|
95
|
+
<p>my big box!</p>
|
48
96
|
</div>
|
49
97
|
```
|
50
98
|
|
99
|
+
if you're using haml, it already does this for you, and you can use props directly:
|
100
|
+
|
101
|
+
```haml
|
102
|
+
/ component
|
103
|
+
.box{ props }
|
104
|
+
= yield
|
105
|
+
```
|
106
|
+
|
107
|
+
|
108
|
+
## Bigger Examples
|
109
|
+
|
110
|
+
[Bootstrap modal][bsmodal]:
|
111
|
+
|
51
112
|
```erb
|
52
|
-
|
113
|
+
<!-- in a view -->
|
114
|
+
<%= component 'modal', id: "important-message", class: "my-fancy-modal" do %>
|
115
|
+
<%= component 'modal/header', "Cool" %>
|
116
|
+
<%= component 'modal/body' do %>
|
117
|
+
<p>
|
118
|
+
Some important stuff!
|
119
|
+
</p>
|
120
|
+
<% end %>
|
121
|
+
<%= component 'modal/footer' %>
|
122
|
+
<% end %>
|
123
|
+
```
|
124
|
+
|
125
|
+
```erb
|
126
|
+
<!-- components -->
|
127
|
+
|
128
|
+
<!-- app/views/components/_modal.html.erb -->
|
129
|
+
<%= content_tag :div, props.html(class: 'modal fade', tabindex: '-1', role: 'dialog') do %>
|
130
|
+
<div class="modal-dialog" role="document">
|
131
|
+
<div class="modal-content">
|
132
|
+
<%= yield %>
|
133
|
+
</div><!-- /.modal-content -->
|
134
|
+
</div><!-- /.modal-dialog -->
|
135
|
+
<% end %>
|
136
|
+
|
137
|
+
<!-- app/views/components/modal/_header.html.erb -->
|
138
|
+
<%= component 'modal/header_container' do %>
|
139
|
+
<%= component 'close_button', data: { dismiss: "modal" } %>
|
140
|
+
<%= component 'modal/title' do %>
|
141
|
+
<%= yield %>
|
142
|
+
<% end %>
|
143
|
+
<% end %>
|
144
|
+
|
145
|
+
<!-- app/views/components/modal/_header_container.html.erb -->
|
146
|
+
<%= content_tag :div, props.html(class: "modal-header") do %>
|
147
|
+
<%= yield %>
|
148
|
+
<% end %>
|
149
|
+
|
150
|
+
<!-- app/views/components/modal/_title.html.erb -->
|
151
|
+
<%= content_tag props.fetch(:tag, "h4"), props.html(class: "modal-title").except(:tag) do %>
|
53
152
|
<%= yield %>
|
153
|
+
<% end %>
|
154
|
+
|
155
|
+
<!-- app/views/components/_close_button.html.erb -->
|
156
|
+
<%= content_tag :button, props.html(type: 'button', class: 'close', :"aria-label" => 'close' ) do %>
|
157
|
+
<span aria-hidden="true">×</span>
|
158
|
+
<% end %>
|
159
|
+
|
160
|
+
<!-- app/views/components/modal/_body.html.erb -->
|
161
|
+
<%= content_tag :div, props.html(class: 'modal-body') do %>
|
162
|
+
<%= yield %>
|
163
|
+
<% end %>
|
164
|
+
|
165
|
+
<!-- app/views/components/modal/_footer.html.erb -->
|
166
|
+
<%= component 'modal/footer_container', props do %>
|
167
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
168
|
+
<%= yield %>
|
169
|
+
<% end %>
|
170
|
+
|
171
|
+
<!-- app/views/components/modal/_footer_container.html.erb -->
|
172
|
+
<%= content_tag :div, props.html(class: "modal-footer") do %>
|
173
|
+
<%= yield %>
|
174
|
+
<% end %>
|
175
|
+
```
|
176
|
+
|
177
|
+
```html
|
178
|
+
<!-- output -->
|
179
|
+
<div id="important-message" class="my-fancy-modal modal fade" tabindex="-1" role="dialog">
|
180
|
+
<div class="modal-dialog" role="document">
|
181
|
+
<div class="modal-content">
|
182
|
+
<div class="modal-header">
|
183
|
+
<button data-dismiss="modal" type="button" class="close" aria-label="close">
|
184
|
+
<span aria-hidden="true">×</span>
|
185
|
+
</button>
|
186
|
+
<h4 class="modal-title">Cool</h1>
|
187
|
+
</div>
|
188
|
+
<div class="modal-body">
|
189
|
+
<p>Some important stuff!</p>
|
190
|
+
</div>
|
191
|
+
<div class="modal-footer">
|
192
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
193
|
+
</div>
|
194
|
+
</div><!-- /.modal-content -->
|
195
|
+
</div><!-- /.modal-dialog -->
|
54
196
|
</div>
|
55
197
|
```
|
56
198
|
|
199
|
+
|
200
|
+
[Basscss navigation]
|
201
|
+
|
202
|
+
## How this compares to `render`
|
203
|
+
|
204
|
+
`component` is a wrapper around `render` [under the hood](./lib/rails_components.rb):
|
205
|
+
|
206
|
+
```erb
|
207
|
+
<%= component 'modal', title: "Example" do %>
|
208
|
+
Modal content!
|
209
|
+
<% end %>
|
210
|
+
```
|
211
|
+
|
57
212
|
```erb
|
58
|
-
|
213
|
+
<%= render layout: 'component/modal', locals: { title: "Example" } do %>
|
214
|
+
Modal content!
|
215
|
+
<% end %>
|
59
216
|
```
|
60
217
|
|
218
|
+
Where it shines is taking arguments instead of blocks
|
219
|
+
|
220
|
+
```erb
|
221
|
+
<%= component 'modal', 'Modal content!', title: "Example" do %>
|
222
|
+
```
|
61
223
|
|
62
|
-
|
224
|
+
And allowing you to use reserved words, which doesn't work with render
|
225
|
+
|
226
|
+
```erb
|
227
|
+
<!-- won't work! rails can't make `class` a local variable -->
|
228
|
+
<%= render layout: 'component/modal', locals: { class: "fancy-modal" } do %>
|
229
|
+
Modal content!
|
230
|
+
<% end %>
|
231
|
+
```
|
232
|
+
|
233
|
+
```erb
|
234
|
+
<!-- works! -->
|
235
|
+
<%= component 'modal', 'Modal content!', class: "fancy-modal" do %>
|
236
|
+
```
|
237
|
+
|
238
|
+
## What's the future of this project, will it be maintained, etc
|
239
|
+
|
240
|
+
Hard to say. If you're worried about dependencies, copy it into your project
|
241
|
+
as a helper. I would be surprised if `render` gets any big changes.
|
242
|
+
|
243
|
+
This project serves more as documentation and examples of how to write
|
244
|
+
components in a rails app (as opposed to adhoc files in `app/views/shared`.)
|
245
|
+
|
246
|
+
## Configuration
|
247
|
+
|
248
|
+
TODO
|
249
|
+
|
250
|
+
## Goals
|
63
251
|
|
64
252
|
- make it as easy to write reusable components
|
65
|
-
- feel familiar to existing rails helpers, like `link_to` or `content_tag`
|
253
|
+
- feel familiar to existing rails helpers, like `link_to` or `content_tag`
|
254
|
+
|
255
|
+
## TODO
|
256
|
+
|
257
|
+
- tests
|
258
|
+
- test configuration
|
259
|
+
- figure out how to make vim-rails jump to files (gf) properly
|
260
|
+
- point out that mixing erb and haml has issues
|
261
|
+
|
262
|
+
[bsmodal]: http://v4-alpha.getbootstrap.com/components/modal/
|
263
|
+
[bspanel]: http://v4-alpha.getbootstrap.com/components/card/
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# https://robots.thoughtbot.com/mygem-configure-block
|
2
|
+
module RailsComponents
|
3
|
+
class << self
|
4
|
+
attr_accessor :configuration
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.configuration
|
8
|
+
@configuration ||= Configuration.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.configure
|
12
|
+
yield(configuration) if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
class Configuration
|
16
|
+
attr_accessor :template_directory
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@template_directory = 'components'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/rails_components.rb
CHANGED
@@ -1,14 +1,35 @@
|
|
1
|
+
require 'rails_components/html_helpers'
|
2
|
+
require 'rails_components/configuration'
|
3
|
+
|
1
4
|
module RailsComponents
|
2
5
|
COMPONENT_RESERVED_WORDS = %i(class return super).freeze
|
3
6
|
|
4
7
|
def component(component_template, text_or_locals_with_block = nil, locals = nil, &block)
|
8
|
+
unless RailsComponents.configuration.template_directory.nil?
|
9
|
+
component_template = [RailsComponents.configuration.template_directory, component_template].join('/')
|
10
|
+
end
|
11
|
+
|
5
12
|
if block_given?
|
6
13
|
render({ layout: component_template, locals: component_locals(text_or_locals_with_block) }, &block)
|
14
|
+
elsif text_or_locals_with_block.is_a?(Hash) && locals.nil?
|
15
|
+
render(layout: component_template, locals: component_locals(text_or_locals_with_block)) {}
|
7
16
|
else
|
8
17
|
render(layout: component_template, locals: component_locals(locals)) { text_or_locals_with_block }
|
9
18
|
end
|
10
19
|
end
|
11
20
|
|
21
|
+
# references:
|
22
|
+
# - https://github.com/rails/rails/blob/v5.0.0/actionview/lib/action_view/helpers/tag_helper.rb#L104
|
23
|
+
def component_content_tag(props, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
24
|
+
if block_given?
|
25
|
+
content_or_options_with_block = props.merge_html(content_or_options_with_block) if content_or_options_with_block.is_a? Hash
|
26
|
+
content_tag(name, content_or_options_with_block, options, escape, &block)
|
27
|
+
else
|
28
|
+
options = props.merge_html(options)
|
29
|
+
content_tag(name, content_or_options_with_block, options, escape, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
12
33
|
private
|
13
34
|
|
14
35
|
# because rails does some weird stuff to make it easy to access the locals in
|
@@ -20,6 +41,7 @@ module RailsComponents
|
|
20
41
|
# - https://github.com/rails/rails/blob/master/actionview/lib/action_view/template.rb
|
21
42
|
def component_locals(locals)
|
22
43
|
locals ||= {}
|
44
|
+
locals.extend(HtmlHelpers)
|
23
45
|
locals.except(*COMPONENT_RESERVED_WORDS).merge(props: locals)
|
24
46
|
end
|
25
47
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_components
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Schilling
|
@@ -20,6 +20,8 @@ files:
|
|
20
20
|
- LICENSE
|
21
21
|
- README.md
|
22
22
|
- lib/rails_components.rb
|
23
|
+
- lib/rails_components/configuration.rb
|
24
|
+
- lib/rails_components/html_helpers.rb
|
23
25
|
homepage: https://github.com/schpet/rails_components
|
24
26
|
licenses:
|
25
27
|
- MIT
|