polaris_form_builder 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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/lib/polaris_form_builder/form_builder.rb +159 -0
- data/lib/polaris_form_builder/helpers.rb +11 -0
- data/lib/polaris_form_builder/railtie.rb +13 -0
- data/lib/polaris_form_builder/tag.rb +60 -0
- data/lib/polaris_form_builder/version.rb +5 -0
- data/lib/polaris_form_builder.rb +9 -0
- metadata +52 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d6bcd0b60a0df4d1edc4b00f89144a17eab765a8d51e3374abc4fc37c4dbc8a5
|
|
4
|
+
data.tar.gz: d641170ee84b65b7cfc22852674fedc102d86629128a5945b90d03a851095a09
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 65edcb87ea84066e95edcf7a52286b54a98135b97b82aa44c8b9fcb245dae6ba36f4e9bf40bbefa6bb33f9e923fa861ca63d9e70807a7bc4978a4cd0f751380e
|
|
7
|
+
data.tar.gz: 2a6a77e9e8582b672e6bef9386bf9ff4e0ce838365e993f91168cae34a3ad72e7c8dc13acfe4daba6dd0afc7454edec722774ee0282cec2e95d5257d7570fcea
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Lex Cao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# PolarisFormBuilder
|
|
2
|
+
|
|
3
|
+
Use Shopify Polaris Web Components as a Rails form builder, while keeping Rails form semantics.
|
|
4
|
+
|
|
5
|
+
- Polaris Web Components docs: https://shopify.dev/docs/api/app-home/polaris-web-components
|
|
6
|
+
|
|
7
|
+
## What This Gem Does
|
|
8
|
+
|
|
9
|
+
- Provides `PolarisFormBuilder::FormBuilder` as a drop-in Rails form builder.
|
|
10
|
+
- Provides `polaris_form_with` for explicit per-form opt-in.
|
|
11
|
+
- Integrates via Rails Engine so helpers are available in ActionView.
|
|
12
|
+
- Maps model validation errors to Polaris component `error` attributes.
|
|
13
|
+
- Preserves Rails helper behavior (`name`, `value`, `checked`, etc.) by building on top of `super`.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Ruby `>= 3.1.0`
|
|
18
|
+
- Rails app using `form_with` / `ActionView::Helpers::FormBuilder`
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle add polaris_form_builder
|
|
24
|
+
bundle install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Recommended: Opt in per form
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<%= polaris_form_with model: @user do |f| %>
|
|
33
|
+
<%= f.text_field :email, placeholder: "Enter email" %>
|
|
34
|
+
<%= f.password_field :password %>
|
|
35
|
+
<%= f.text_area :bio %>
|
|
36
|
+
<%= f.check_box :agree_terms %>
|
|
37
|
+
<%= f.submit "Create Account" %>
|
|
38
|
+
<% end %>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Global default builder
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# config/application.rb (or environment files)
|
|
45
|
+
config.action_view.default_form_builder = PolarisFormBuilder::FormBuilder
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then regular `form_with` will use Polaris components automatically.
|
|
49
|
+
|
|
50
|
+
```erb
|
|
51
|
+
<%= form_with model: @user do |f| %>
|
|
52
|
+
<%= f.text_field :email %>
|
|
53
|
+
<%= f.password_field :password %>
|
|
54
|
+
<%= f.submit %>
|
|
55
|
+
<% end %>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Supported Helpers
|
|
59
|
+
|
|
60
|
+
### Core Rails-style fields
|
|
61
|
+
|
|
62
|
+
- `text_field`
|
|
63
|
+
- `number_field`
|
|
64
|
+
- `email_field`
|
|
65
|
+
- `password_field`
|
|
66
|
+
- `url_field`
|
|
67
|
+
- `search_field`
|
|
68
|
+
- `color_field`
|
|
69
|
+
- `date_field`
|
|
70
|
+
- `file_field` (`drop_zone` alias)
|
|
71
|
+
- `text_area`
|
|
72
|
+
- `check_box`
|
|
73
|
+
- `select`
|
|
74
|
+
- `submit`
|
|
75
|
+
|
|
76
|
+
### Polaris-oriented helpers
|
|
77
|
+
|
|
78
|
+
- `drop_zone`
|
|
79
|
+
- `money_field`
|
|
80
|
+
- `color_picker`
|
|
81
|
+
- `date_picker`
|
|
82
|
+
- `switch`
|
|
83
|
+
- `choice_list`
|
|
84
|
+
|
|
85
|
+
## Validation Error Mapping
|
|
86
|
+
|
|
87
|
+
When your model has errors, corresponding Polaris components receive `error` automatically.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
class User < ApplicationRecord
|
|
91
|
+
validates :email, presence: true
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```erb
|
|
96
|
+
<%= polaris_form_with model: @user do |f| %>
|
|
97
|
+
<%= f.text_field :email %>
|
|
98
|
+
<% end %>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If `@user.errors[:email]` is present, the rendered Polaris field gets an `error` attribute.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
bin/setup
|
|
107
|
+
bin/rubocop
|
|
108
|
+
rake test
|
|
109
|
+
bin/ci
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Useful commands:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
rake test_unit
|
|
116
|
+
rake test_integration
|
|
117
|
+
rake test_playground
|
|
118
|
+
bin/console
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Release
|
|
122
|
+
|
|
123
|
+
1. Update `lib/polaris_form_builder/version.rb`.
|
|
124
|
+
2. Update `CHANGELOG.md`.
|
|
125
|
+
3. Run `bin/ci`.
|
|
126
|
+
4. Publish:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
bundle exec rake release
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Contributing
|
|
133
|
+
|
|
134
|
+
Issues and pull requests are welcome:
|
|
135
|
+
|
|
136
|
+
This project follows the Contributor Covenant Code of Conduct.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
Released under MIT. See `LICENSE.txt`.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_view"
|
|
4
|
+
require_relative "tag"
|
|
5
|
+
|
|
6
|
+
module PolarisFormBuilder
|
|
7
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
8
|
+
include ActionView::Helpers::FormTagHelper
|
|
9
|
+
|
|
10
|
+
# Generate input field methods with identical structure
|
|
11
|
+
# Maps Rails helper name to Polaris component tag
|
|
12
|
+
{
|
|
13
|
+
text_field: "s-text-field",
|
|
14
|
+
number_field: "s-number-field",
|
|
15
|
+
email_field: "s-email-field",
|
|
16
|
+
password_field: "s-password-field",
|
|
17
|
+
url_field: "s-url-field",
|
|
18
|
+
search_field: "s-search-field",
|
|
19
|
+
color_field: "s-color-field",
|
|
20
|
+
date_field: "s-date-field",
|
|
21
|
+
file_field: "s-drop-zone"
|
|
22
|
+
}.each do |helper_name, polaris_tag|
|
|
23
|
+
define_method(helper_name) do |method, options = {}, &block|
|
|
24
|
+
error = method_error(method)
|
|
25
|
+
attrs = { error: error }.compact
|
|
26
|
+
|
|
27
|
+
html = without_field_error_proc do
|
|
28
|
+
super(method, options.merge(attrs))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
polaris_input(polaris_tag, html, &block)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
alias_method :drop_zone, :file_field
|
|
36
|
+
|
|
37
|
+
def text_area(method, options = {}, &block)
|
|
38
|
+
error = method_error(method)
|
|
39
|
+
attrs = { error: error }.compact
|
|
40
|
+
|
|
41
|
+
html = without_field_error_proc do
|
|
42
|
+
super(method, options.merge(attrs))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@template.raw Tag.new("s-text-area", "textarea", remove_attributes: %w[type size])
|
|
46
|
+
.child_to_attr("value")
|
|
47
|
+
.apply(html)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
|
51
|
+
error = method_error(method)
|
|
52
|
+
attrs = { error: error }.compact
|
|
53
|
+
|
|
54
|
+
html = without_field_error_proc do
|
|
55
|
+
super(method, options.merge(attrs), checked_value, unchecked_value)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
polaris_input("s-checkbox", html)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
|
62
|
+
error = method_error(method)
|
|
63
|
+
attrs = { error: error }.compact
|
|
64
|
+
|
|
65
|
+
html = without_field_error_proc do
|
|
66
|
+
super(method, choices, options.merge(attrs), html_options)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
content = capture_block(&block)
|
|
70
|
+
|
|
71
|
+
select_tag = Tag.new("s-select", "select")
|
|
72
|
+
select_tag.attr("value", object.send(method)) if object.respond_to?(method)
|
|
73
|
+
|
|
74
|
+
html = select_tag.apply(html, content)
|
|
75
|
+
html = Tag.new("s-option-group", "optgroup").apply(html)
|
|
76
|
+
html = Tag.new("s-option", "option").apply(html)
|
|
77
|
+
|
|
78
|
+
@template.raw html
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def submit(value = nil, options = {})
|
|
82
|
+
html = without_field_error_proc do
|
|
83
|
+
super(value, options)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@template.raw Tag.new("s-button", "input")
|
|
87
|
+
.attr_to_child("value")
|
|
88
|
+
.apply(html)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def money_field(method, options = {})
|
|
92
|
+
html = text_field(method, options)
|
|
93
|
+
|
|
94
|
+
@template.raw Tag.new("s-money-field", "s-text-field").apply(html)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def color_picker(method, options = {})
|
|
98
|
+
html = text_field(method, options)
|
|
99
|
+
|
|
100
|
+
@template.raw Tag.new("s-color-picker", "s-text-field").apply(html)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def date_picker(method, options = {})
|
|
104
|
+
options = options.dup
|
|
105
|
+
picker_type = options.delete(:type)
|
|
106
|
+
html = date_field(method, options)
|
|
107
|
+
|
|
108
|
+
tag = Tag.new("s-date-picker", "s-date-field")
|
|
109
|
+
tag.attr("type", picker_type) if picker_type
|
|
110
|
+
@template.raw tag.apply(html)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def switch(method, options = {})
|
|
114
|
+
html = check_box(method, options, options.fetch(:value, "1"))
|
|
115
|
+
|
|
116
|
+
@template.raw Tag.new("s-switch", "s-checkbox").apply(html)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def choice_list(method, options = {}, &block)
|
|
120
|
+
select(method, options.delete(:choices), options, &block)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def method_error(method)
|
|
126
|
+
if object.respond_to?(:errors) && object.errors[method].present?
|
|
127
|
+
object.errors[method].to_sentence
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def without_field_error_proc
|
|
132
|
+
original = ::ActionView::Base.field_error_proc
|
|
133
|
+
::ActionView::Base.field_error_proc = ->(html_tag, _instance) { html_tag }
|
|
134
|
+
yield
|
|
135
|
+
ensure
|
|
136
|
+
::ActionView::Base.field_error_proc = original
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def polaris_input(tag_name, html, &block)
|
|
140
|
+
tag = Tag.new(tag_name, "input", remove_attributes: %w[type size])
|
|
141
|
+
@template.raw tag.apply(html, capture_block(&block))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def capture_block(&block)
|
|
145
|
+
return unless block_given?
|
|
146
|
+
|
|
147
|
+
# Use the buffer from the block's binding to avoid writing to a different
|
|
148
|
+
# output buffer and duplicating content when the builder is reused.
|
|
149
|
+
capture_buffer = block.binding.eval("@output_buffer") rescue nil
|
|
150
|
+
capture_buffer ||= @template.output_buffer
|
|
151
|
+
|
|
152
|
+
if capture_buffer.respond_to?(:capture)
|
|
153
|
+
capture_buffer.capture(&block)
|
|
154
|
+
else
|
|
155
|
+
@template.capture(&block)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "helpers"
|
|
4
|
+
|
|
5
|
+
module PolarisFormBuilder
|
|
6
|
+
class Railtie < ::Rails::Railtie
|
|
7
|
+
initializer "polaris_form_builder.view_helpers" do
|
|
8
|
+
ActiveSupport.on_load(:action_view) do
|
|
9
|
+
include PolarisFormBuilder::Helpers
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module PolarisFormBuilder
|
|
6
|
+
class Tag
|
|
7
|
+
attr_accessor :name
|
|
8
|
+
|
|
9
|
+
def initialize(name, replace, remove_attributes: [])
|
|
10
|
+
@name = name
|
|
11
|
+
@replace = replace
|
|
12
|
+
@remove_attributes = remove_attributes
|
|
13
|
+
@transformations = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def attr_to_child(attr_name)
|
|
17
|
+
@transformations << ->(node) {
|
|
18
|
+
if (attr = node.attr(attr_name))
|
|
19
|
+
node.add_child(attr)
|
|
20
|
+
end
|
|
21
|
+
}
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def child_to_attr(attr_name)
|
|
26
|
+
@transformations << ->(node) {
|
|
27
|
+
content = node.content
|
|
28
|
+
if content.present?
|
|
29
|
+
node.set_attribute(attr_name, content)
|
|
30
|
+
node.children.remove
|
|
31
|
+
end
|
|
32
|
+
}
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def attr(attr_name, value)
|
|
37
|
+
return self unless value
|
|
38
|
+
|
|
39
|
+
@transformations << ->(node) {
|
|
40
|
+
node.set_attribute(attr_name, value)
|
|
41
|
+
}
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def apply(html, content = nil)
|
|
46
|
+
fragment = Nokogiri::HTML5.fragment(html)
|
|
47
|
+
fragment.css("#{@replace}:not([type=\"hidden\"])").each do |node|
|
|
48
|
+
node.name = @name
|
|
49
|
+
node.inner_html = content if content
|
|
50
|
+
|
|
51
|
+
@transformations.each { |transform| transform.call(node) }
|
|
52
|
+
|
|
53
|
+
@remove_attributes.each do |key|
|
|
54
|
+
node.remove_attribute(key)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
fragment.to_html
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "polaris_form_builder/version"
|
|
4
|
+
require_relative "polaris_form_builder/form_builder"
|
|
5
|
+
require_relative "polaris_form_builder/railtie" if defined?(Rails::Railtie)
|
|
6
|
+
|
|
7
|
+
module PolarisFormBuilder
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: polaris_form_builder
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Lex Cao
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Rails form helpers and view bindings to build forms with Shopify Polaris
|
|
13
|
+
web components.
|
|
14
|
+
email:
|
|
15
|
+
- lexcao@foxmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE.txt
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/polaris_form_builder.rb
|
|
23
|
+
- lib/polaris_form_builder/form_builder.rb
|
|
24
|
+
- lib/polaris_form_builder/helpers.rb
|
|
25
|
+
- lib/polaris_form_builder/railtie.rb
|
|
26
|
+
- lib/polaris_form_builder/tag.rb
|
|
27
|
+
- lib/polaris_form_builder/version.rb
|
|
28
|
+
homepage: https://polaris-form-builder.lexcao.io
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata:
|
|
32
|
+
allowed_push_host: https://rubygems.org
|
|
33
|
+
homepage_uri: https://polaris-form-builder.lexcao.io
|
|
34
|
+
source_code_uri: https://github.com/lexcao/polaris_form_builder
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: 3.1.0
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
requirements: []
|
|
49
|
+
rubygems_version: 3.6.9
|
|
50
|
+
specification_version: 4
|
|
51
|
+
summary: Rails form builder helpers for Shopify Polaris web components.
|
|
52
|
+
test_files: []
|