bulma-rails-helpers 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: 778c3dafae213061ae5a31634952cb0c0afddec441ac6c47eb37584ae3ba782a
4
+ data.tar.gz: 2774b17df0e7ae39652bb62f5a715d029d0bd4782ee9a1f1e6b132ceb34ccc1b
5
+ SHA512:
6
+ metadata.gz: af60202790e1e7c9daf8e0819419c1972fbc227a7ad2166a4619bb2e5bbbc517b511f78308050c50008fd7cff0085f964839693cf9e94bb4768437e7a9d77f1d
7
+ data.tar.gz: 36c86bbbf37c810e9022252e8823493298ab4737eaa550e11334a9c25b915f052b0b04633f91d74d8f88645a7f1f6260405ebe6d04632b429e795cee2b584984
@@ -0,0 +1,254 @@
1
+ module Bulma
2
+ # # Bulma Form Helper
3
+ #
4
+ # Module Bulma FormHelper overrides the Rails form helpers to generate elements that are decorated with Bulma CSS
5
+ # classes. Here is an example of the generated html from the `text_field` helper (with a User model):
6
+ #
7
+ # f.text_field :name, class: "input"
8
+ #
9
+ # <div class="field">
10
+ # <label for="name" class="label">Name</label>
11
+ # <div class="control">
12
+ # <input id="user_name" class="input" type="text">
13
+ # </div>
14
+ # </div>
15
+ #
16
+ # In addition to the standard options, the following options are supported for inputs:
17
+ # - suppress_label: Do not render the label tag
18
+ # - icon_left: Add an icon to the left of the input field
19
+ # - icon_right: Add an icon to the right of the input field
20
+ #
21
+ # The two icon options should be a string that represents the icon class. For example, "fas fa-user" would render a user
22
+ # icon from Font Awesome.
23
+ module FormHelper
24
+ def text_field(object_name, method, options = {})
25
+ options = options.dup
26
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
27
+ end
28
+
29
+ def password_field(object_name, method, options = {})
30
+ options = options.dup
31
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
32
+ end
33
+
34
+ def telephone_field(object_name, method, options = {})
35
+ options = options.dup
36
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
37
+ end
38
+
39
+ def date_field(object_name, method, options = {})
40
+ options = options.dup
41
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
42
+ end
43
+
44
+ def time_field(object_name, method, options = {})
45
+ options = options.dup
46
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
47
+ end
48
+
49
+ def datetime_field(object_name, method, options = {})
50
+ options = options.dup
51
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
52
+ end
53
+
54
+ def month_field(object_name, method, options = {})
55
+ options = options.dup
56
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
57
+ end
58
+
59
+ def week_field(object_name, method, options = {})
60
+ options = options.dup
61
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
62
+ end
63
+
64
+ def url_field(object_name, method, options = {})
65
+ options = options.dup
66
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
67
+ end
68
+
69
+ def email_field(object_name, method, options = {})
70
+ options = options.dup
71
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
72
+ end
73
+
74
+ def number_field(object_name, method, options = {})
75
+ options = options.dup
76
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
77
+ end
78
+
79
+ def range_field(object_name, method, options = {})
80
+ options = options.dup
81
+ wrap_input_field(object_name, method, options) { super object_name, method, options }
82
+ end
83
+
84
+ def submit_tag(value = "Save", options = {})
85
+ return super if options.delete(:delivered)
86
+
87
+ append_classes_to_options(options, "button")
88
+ wrap_with_control { super }
89
+ end
90
+
91
+ def checkbox(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
92
+ tag.div(class: "field") do
93
+ tag.div(class: "control") do
94
+ label(object_name, method, class: "checkbox") do
95
+ concat super
96
+ concat " #{t(method, scope: [ :helpers, :label, object_name ])}"
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def checkbox_display(object_name, method, checked)
103
+ icon = checked ? "far fa-lg fa-square-check" : "fa-regular fa-square"
104
+
105
+ tag.div(class: "field") do
106
+ tag.div(class: "control") do
107
+ concat icon_span(icon, "is-medium")
108
+ concat tag.span t(method, scope: [ :helpers, :label, object_name ])
109
+ end
110
+ end
111
+ end
112
+
113
+ def select(object, method, choices = nil, options = {}, html_options = {}, &block)
114
+ wrap_with_field_and_label(object, method, options, suppress_label: options.delete(:suppress_label)) do
115
+ wrap_with_control do
116
+ tag.div(class: class_names(:select, is_danger: attribute_has_errors?(options, method))) { super }
117
+ end
118
+ end
119
+ end
120
+
121
+ def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
122
+ wrap_with_field_and_label(object, method, options, suppress_label: options.delete(:suppress_label), column: options.delete(:column)) do
123
+ wrap_with_control do
124
+ tag.div(class: class_names(:select, is_danger: attribute_has_errors?(options, method))) { super }
125
+ end
126
+ end
127
+ end
128
+
129
+ def file_field(object_name, method, options = {})
130
+ append_classes_to_options(options, "file-input")
131
+ append_data_attributes_to_options(options, bulma__file_input_display_target: "fileInput", action: "bulma--file-input-display#show")
132
+ accepted_file_types = options.fetch(:accept, "").split(",").map(&:strip)
133
+
134
+ # [File Upload](https://bulma.io/documentation/form/file/)
135
+ tag.div(class: "file is-boxed", data: { controller: "bulma--file-input-display", bulma__file_input_display_accepted_file_types_value: accepted_file_types }) do
136
+ tag.label(class: "file-label") do
137
+ concat super
138
+ concat(tag.span(class: "file-cta") do
139
+ concat tag.span(class: "file-icon") { tag.i(class: "fas fa-upload") }
140
+ concat tag.span(class: "file-label") { t(method, scope: [ "helpers", "label", object_name ]) }
141
+ end)
142
+ concat tag.div(class: "content", data: { bulma__file_input_display_target: "fileList" }) { "No file(s) selected" }
143
+ end
144
+ end
145
+ end
146
+
147
+ def textarea(object_name, method, options = {})
148
+ append_classes_to_options(options, "textarea")
149
+
150
+ wrap_with_field_and_label(object_name, method, options, suppress_label: options.delete(:suppress_label), column: options.delete(:column)) do
151
+ wrap_with_control { super }
152
+ end
153
+ end
154
+
155
+ def text_area(object_name, method, options = {})
156
+ textarea(object_name, method, options)
157
+ end
158
+
159
+ private
160
+
161
+ # [Form Control](https://bulma.io/documentation/form/general/#form-control)
162
+ def wrap_with_control
163
+ tag.div(class: "control") { yield }
164
+ end
165
+
166
+ # [Form Input](https://bulma.io/documentation/form/general/#complete-form-example)
167
+ def wrap_input_field(object_name, method, options)
168
+ options.extend(InputOptionsParser)
169
+ append_classes_to_options(options, "input")
170
+ append_classes_to_options(options, "is-danger") if attribute_has_errors?(options, method)
171
+
172
+ wrap_with_field_and_label(object_name, method, options, suppress_label: options.suppress_label?, column: options.column?) do
173
+ tag.div(class: class_names(:control, options.icon_control_classes)) do
174
+ concat yield
175
+ concat icon_span(options.icon_left, "is-small is-left") if options.icon_left?
176
+ concat icon_span(options.icon_right, "is-small is-right") if options.icon_right?
177
+ end
178
+ end
179
+ end
180
+
181
+ # [Form Field](https://bulma.io/documentation/form/general/#form-field)
182
+ # In addition to being able to add the "column" class by setting `column: true`, you can provide sizing for the
183
+ # column by passing in the class name as a string. For example, `column: "is-one-third"` will add the class
184
+ # "column is-one-third" to the field div.
185
+ def wrap_with_field_and_label(object_name, method, options, suppress_label: false, column: false)
186
+ tag.div(class: class_names(:field, column: column, column => column.is_a?(String))) do
187
+ concat label(object_name, method, class: "label") unless suppress_label
188
+ concat yield
189
+ end
190
+ end
191
+
192
+ # [Icon](https://bulma.io/documentation/elements/icon/)
193
+ def icon_span(icon, additional_classes = nil)
194
+ tag.span(class: "icon #{additional_classes}".strip) do
195
+ tag.i(class: icon)
196
+ end
197
+ end
198
+
199
+ def append_classes_to_options(options, *additional_classes)
200
+ classes = options.fetch(:class, "")
201
+ adds = additional_classes.reject { classes.include?(it) }
202
+ options[:class] = "#{classes} #{adds.join(' ')}".strip
203
+ end
204
+
205
+ def append_data_attributes_to_options(options, **additional_attributes)
206
+ data = options.fetch(:data, {})
207
+ options[:data] = data.merge(additional_attributes)
208
+ end
209
+
210
+ def attribute_has_errors?(options, attribute_name)
211
+ object = options[:object]
212
+ object.respond_to?(:errors) && object.errors.key?(attribute_name)
213
+ end
214
+
215
+ # # Input Options Parser
216
+ #
217
+ # Module InputOptionsParser is mixed into the options hash to provide accessors and predicate methods for the custom
218
+ # options suppress_label, icon_left, and icon_right.
219
+ #
220
+ # Method `icon_control_classes` returns a hash suitable for passing to the `class_names` helper method.
221
+ #
222
+ # TODO: Handle options for [input modifiers color, size, and state](https://bulma.io/documentation/form/input/).
223
+ module InputOptionsParser
224
+ def self.extended(base)
225
+ attr_reader :icon_left, :icon_right
226
+
227
+ base.instance_variable_set(:@suppress_label, base.delete(:suppress_label))
228
+ base.instance_variable_set(:@icon_left, base.delete(:icon_left))
229
+ base.instance_variable_set(:@icon_right, base.delete(:icon_right))
230
+ base.instance_variable_set(:@column, base.delete(:column))
231
+ end
232
+
233
+ def suppress_label?
234
+ @suppress_label
235
+ end
236
+
237
+ def icon_left?
238
+ @icon_left.present?
239
+ end
240
+
241
+ def icon_right?
242
+ @icon_right.present?
243
+ end
244
+
245
+ def icon_control_classes
246
+ { "has-icons-left": icon_left?, "has-icons-right": icon_right? }
247
+ end
248
+
249
+ def column?
250
+ @column
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,82 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["fileInput", "fileList"]
5
+
6
+ acceptValues = []
7
+
8
+ connect() {
9
+ this.show()
10
+ this.#parseAcceptAttribute()
11
+ }
12
+
13
+ show() {
14
+ const preview = this.fileListTarget;
15
+ this.#clear(preview)
16
+
17
+ const curFiles = this.fileInputTarget.files;
18
+ if (curFiles.length === 0) {
19
+ const para = document.createElement("p")
20
+ para.textContent = "No files currently selected for upload"
21
+ preview.appendChild(para)
22
+ } else {
23
+ const list = document.createElement("ul")
24
+ preview.appendChild(list)
25
+
26
+ for (const file of curFiles) {
27
+ const listItem = document.createElement("li")
28
+ listItem.textContent = this.#fileTextContent(file)
29
+ list.appendChild(listItem);
30
+ }
31
+ }
32
+ }
33
+
34
+ #clear(preview) {
35
+ while (preview.firstChild) {
36
+ preview.removeChild(preview.firstChild);
37
+ }
38
+ }
39
+
40
+ #fileTextContent(file) {
41
+ if (this.#validFileType(file)) {
42
+ return this.#fileInfo(file);
43
+ } else {
44
+ return `File name ${file.name}: Not a valid file type. Update your selection.`;
45
+ }
46
+ }
47
+
48
+ #validFileType(file) {
49
+ // If no accept attribute is specified, accept all files
50
+ if (this.acceptValues.length === 0) {
51
+ return true
52
+ }
53
+
54
+ const fileExtension = `.${file.name.split('.').pop().toLowerCase()}`
55
+ const fileMimeType = file.type.toLowerCase()
56
+
57
+ // Check if the file matches any of the accept criteria
58
+ return this.acceptValues.some(acceptValue => {
59
+ if (acceptValue.startsWith('.')) {
60
+ // Check file extension (e.g. ".jpg")
61
+ return acceptValue.toLowerCase() === fileExtension
62
+ } else if (acceptValue.includes('/*')) {
63
+ // Check MIME type with wildcard (e.g. "image/*")
64
+ const acceptGroup = acceptValue.split('/')[0]
65
+ return fileMimeType.startsWith(`${acceptGroup}/`)
66
+ } else {
67
+ // Check specific MIME type (e.g. "image/jpeg")
68
+ return acceptValue === fileMimeType
69
+ }
70
+ })
71
+ }
72
+
73
+ #fileInfo(file) {
74
+ return `${file.name} (${file.size} bytes)`
75
+ }
76
+
77
+ #parseAcceptAttribute() {
78
+ if (this.fileInputTarget.accept) {
79
+ this.acceptValues = this.fileInputTarget.accept.split(',').map(type => type.trim())
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,10 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = [ "burger", "menu" ]
5
+
6
+ toggle(_event) {
7
+ this.burgerTarget.classList.toggle('is-active');
8
+ this.menuTarget.classList.toggle('is-active');
9
+ }
10
+ }
@@ -0,0 +1 @@
1
+ pin_all_from File.expand_path("../app/javascript/controllers/bulma", __dir__), under: "controllers/bulma"
@@ -0,0 +1,13 @@
1
+ require "rails/engine"
2
+
3
+ module Bulma
4
+ class Engine < ::Rails::Engine
5
+ initializer "bulma.importmap", before: "importmap" do |app|
6
+ app.config.importmap.paths << Engine.root.join("config/importmap.rb")
7
+ end
8
+
9
+ initializer "bulma.assets" do |app|
10
+ app.config.assets.paths << root.join("app/javascript")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Bulma
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ require "bulma/engine"
2
+
3
+ module Bulma
4
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bulma-rails-helpers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Todd Kummer
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 8.0.2
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 8.0.2
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest-difftastic
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ description: Override the Rails tag helpers to generate HTML that plays well with
41
+ the Bulma CSS framework.
42
+ email:
43
+ - todd@rockridgesolutions.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - app/helpers/bulma/form_helper.rb
49
+ - app/javascript/controllers/bulma/file_input_display_controller.js
50
+ - app/javascript/controllers/bulma/navigation_bar_controller.js
51
+ - config/importmap.rb
52
+ - lib/bulma-rails-helpers.rb
53
+ - lib/bulma/engine.rb
54
+ - lib/bulma/version.rb
55
+ homepage: https://github.com/RockSolt/bulma-rails-helpers
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ rubygems_mfa_required: 'true'
60
+ homepage_uri: https://github.com/RockSolt/bulma-rails-helpers
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.4.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.7
76
+ specification_version: 4
77
+ summary: Build Rails forms with Bulma CSS framework.
78
+ test_files: []