superform 0.6.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +100 -3
- data/CLAUDE.md +46 -0
- data/Gemfile.lock +6 -1
- data/README.md +208 -75
- data/examples/basic_form.rb +21 -0
- data/examples/checkbox_form.rb +28 -0
- data/examples/date_time_form.rb +18 -0
- data/examples/example_form.rb +10 -0
- data/examples/select_form.rb +26 -0
- data/examples/special_inputs_form.rb +23 -0
- data/examples/textarea_form.rb +15 -0
- data/lib/generators/superform/install/templates/base.rb +33 -7
- data/lib/superform/dom.rb +13 -1
- data/lib/superform/field.rb +14 -9
- data/lib/superform/rails/choices/choice.rb +39 -0
- data/lib/superform/rails/choices/mapper.rb +41 -0
- data/lib/superform/rails/choices.rb +6 -0
- data/lib/superform/rails/components/base.rb +9 -2
- data/lib/superform/rails/components/checkbox.rb +34 -7
- data/lib/superform/rails/components/checkboxes.rb +38 -0
- data/lib/superform/rails/components/datalist.rb +34 -0
- data/lib/superform/rails/components/input.rb +1 -1
- data/lib/superform/rails/components/label.rb +1 -1
- data/lib/superform/rails/components/radio.rb +21 -0
- data/lib/superform/rails/components/radios.rb +38 -0
- data/lib/superform/rails/components/select.rb +52 -8
- data/lib/superform/rails/field.rb +184 -0
- data/lib/superform/rails/form.rb +18 -129
- data/lib/superform/version.rb +1 -1
- data/server/components/breadcrumb.rb +11 -0
- data/server/components/form_card.rb +13 -0
- data/server/components/layout.rb +23 -0
- data/server/controllers/forms_controller.rb +94 -0
- data/server/models/example.rb +31 -0
- data/server/public/styles.css +282 -0
- data/server/rails.rb +56 -0
- data/superform.gemspec +37 -0
- metadata +28 -5
- data/lib/superform/rails/option_mapper.rb +0 -36
data/lib/superform/rails/form.rb
CHANGED
|
@@ -14,146 +14,35 @@ module Superform
|
|
|
14
14
|
include Phlex::Rails::Helpers::FormAuthenticityToken
|
|
15
15
|
include Phlex::Rails::Helpers::URLFor
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:Field,
|
|
21
|
-
:field,
|
|
22
|
-
:collection,
|
|
23
|
-
:namespace,
|
|
24
|
-
:assign,
|
|
25
|
-
:serialize,
|
|
26
|
-
to: :@namespace
|
|
27
|
-
|
|
28
|
-
# The Field class is designed to be extended to create custom forms. To override,
|
|
29
|
-
# in your subclass you may have something like this:
|
|
17
|
+
# The `Field` class is nested inside the `Form` class so it can be easily extended
|
|
18
|
+
# to customize the form inputs for your application. For example, if you wanted to
|
|
19
|
+
# add some default classes to all your inputs and labels you could do something like:
|
|
30
20
|
#
|
|
31
21
|
# ```ruby
|
|
32
22
|
# class MyForm < Superform::Rails::Form
|
|
33
|
-
# class
|
|
34
|
-
# def
|
|
35
|
-
#
|
|
23
|
+
# class Field < self::Field
|
|
24
|
+
# def input(**attributes)
|
|
25
|
+
# super(class: "input input-bordered", **attributes)
|
|
36
26
|
# end
|
|
37
|
-
# end
|
|
38
27
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# MyLabel.new(self, **attributes)
|
|
28
|
+
# def label(**attributes, &block)
|
|
29
|
+
# super(class: "label", **attributes, &block)
|
|
42
30
|
# end
|
|
43
31
|
# end
|
|
44
32
|
# end
|
|
45
33
|
# ```
|
|
46
|
-
|
|
47
|
-
# Now all calls to `label` will have the `text-bold` class applied to it.
|
|
48
|
-
class Field < Superform::Field
|
|
49
|
-
def button(**attributes)
|
|
50
|
-
Components::Button.new(self, attributes:)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def input(**attributes)
|
|
54
|
-
Components::Input.new(self, attributes:)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def text(*, **, &)
|
|
58
|
-
input(*, **, type: :text, &)
|
|
59
|
-
end
|
|
34
|
+
Field = Superform::Rails::Field
|
|
60
35
|
|
|
61
|
-
|
|
62
|
-
Components::Checkbox.new(self, attributes:)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def label(**attributes, &)
|
|
66
|
-
Components::Label.new(self, attributes:, &)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def textarea(**attributes)
|
|
70
|
-
Components::Textarea.new(self, attributes:)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def select(*collection, **attributes, &)
|
|
74
|
-
Components::Select.new(self, attributes:, collection:, &)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# HTML5 input type convenience methods - clean API without _field suffix
|
|
78
|
-
# Examples:
|
|
79
|
-
# field(:email).email(class: "form-input")
|
|
80
|
-
# field(:age).number(min: 18, max: 99)
|
|
81
|
-
# field(:birthday).date
|
|
82
|
-
# field(:secret).hidden(value: "token123")
|
|
83
|
-
# field(:gender).radio("male", id: "user_gender_male")
|
|
84
|
-
def hidden(*, **, &)
|
|
85
|
-
input(*, **, type: :hidden, &)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def password(*, **, &)
|
|
89
|
-
input(*, **, type: :password, &)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def email(*, **, &)
|
|
93
|
-
input(*, **, type: :email, &)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def url(*, **, &)
|
|
97
|
-
input(*, **, type: :url, &)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def tel(*, **, &)
|
|
101
|
-
input(*, **, type: :tel, &)
|
|
102
|
-
end
|
|
103
|
-
alias_method :phone, :tel
|
|
104
|
-
|
|
105
|
-
def number(*, **, &)
|
|
106
|
-
input(*, **, type: :number, &)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def range(*, **, &)
|
|
110
|
-
input(*, **, type: :range, &)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def date(*, **, &)
|
|
114
|
-
input(*, **, type: :date, &)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def time(*, **, &)
|
|
118
|
-
input(*, **, type: :time, &)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def datetime(*, **, &)
|
|
122
|
-
input(*, **, type: :"datetime-local", &)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def month(*, **, &)
|
|
126
|
-
input(*, **, type: :month, &)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def week(*, **, &)
|
|
130
|
-
input(*, **, type: :week, &)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def color(*, **, &)
|
|
134
|
-
input(*, **, type: :color, &)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def search(*, **, &)
|
|
138
|
-
input(*, **, type: :search, &)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def file(*, **, &)
|
|
142
|
-
input(*, **, type: :file, &)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def radio(value, *, **, &)
|
|
146
|
-
input(*, **, type: :radio, value: value, &)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Rails compatibility aliases
|
|
150
|
-
alias_method :check_box, :checkbox
|
|
151
|
-
alias_method :text_area, :textarea
|
|
36
|
+
attr_accessor :model
|
|
152
37
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
38
|
+
delegate \
|
|
39
|
+
:Field,
|
|
40
|
+
:field,
|
|
41
|
+
:collection,
|
|
42
|
+
:namespace,
|
|
43
|
+
:assign,
|
|
44
|
+
:serialize,
|
|
45
|
+
to: :@namespace
|
|
157
46
|
|
|
158
47
|
def build_field(...)
|
|
159
48
|
self.class::Field.new(...)
|
data/lib/superform/version.rb
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class FormCard < Phlex::HTML
|
|
4
|
+
def initialize(form_class:, index:)
|
|
5
|
+
@form_class = form_class
|
|
6
|
+
@index = index
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template
|
|
10
|
+
h2 { a(href: "/forms/#{@index}") { @form_class.name_text } }
|
|
11
|
+
p { @form_class.description.html_safe }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Layout < Phlex::HTML
|
|
4
|
+
def initialize(title: "Superform")
|
|
5
|
+
@title = title
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def view_template(&)
|
|
9
|
+
doctype
|
|
10
|
+
html do
|
|
11
|
+
head do
|
|
12
|
+
title { @title }
|
|
13
|
+
link rel: "stylesheet", href: "/styles.css"
|
|
14
|
+
end
|
|
15
|
+
body do
|
|
16
|
+
header do
|
|
17
|
+
h1 { a(href: "/") { "Superform" } }
|
|
18
|
+
end
|
|
19
|
+
yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class FormsController < ActionController::Base
|
|
4
|
+
before_action { Rails.autoloaders.main.reload }
|
|
5
|
+
class IndexPage < Phlex::HTML
|
|
6
|
+
def view_template
|
|
7
|
+
render Layout.new do
|
|
8
|
+
render Breadcrumb.new do |b|
|
|
9
|
+
b.crumb { "Examples" }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
form_classes.each_with_index do |form_class, index|
|
|
13
|
+
render FormCard.new(form_class: form_class, index: index)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ShowPage < Phlex::HTML
|
|
20
|
+
def initialize(form_class:, action:)
|
|
21
|
+
@form_class = form_class
|
|
22
|
+
@form = form_class.new(Example.new, action: action)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def view_template
|
|
26
|
+
render Layout.new(title: "#{@form_class.name_text} - Superform") do
|
|
27
|
+
render Breadcrumb.new do |b|
|
|
28
|
+
b.crumb { a(href: "/") { "Examples" } }
|
|
29
|
+
b.crumb { @form_class.name_text }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
p { @form_class.description.html_safe }
|
|
33
|
+
render @form
|
|
34
|
+
|
|
35
|
+
hr
|
|
36
|
+
|
|
37
|
+
h3 { "Source Code" }
|
|
38
|
+
pre do
|
|
39
|
+
code { source_code }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def source_code
|
|
47
|
+
file = File.join(EXAMPLES_DIR, "#{underscore(@form_class.name)}.rb")
|
|
48
|
+
File.exist?(file) ? File.read(file) : "# Source not found"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def underscore(name)
|
|
52
|
+
name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class SubmitPage < Phlex::HTML
|
|
57
|
+
def initialize(form_class:, params:, form_path:)
|
|
58
|
+
@form_class = form_class
|
|
59
|
+
@params = params
|
|
60
|
+
@form_path = form_path
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def view_template
|
|
64
|
+
render Layout.new(title: "#{@form_class.name_text} - Submitted") do
|
|
65
|
+
render Breadcrumb.new do |b|
|
|
66
|
+
b.crumb { a(href: "/") { "Examples" } }
|
|
67
|
+
b.crumb { a(href: @form_path) { @form_class.name_text } }
|
|
68
|
+
b.crumb { "Submitted" }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
h3 { "Params" }
|
|
72
|
+
pre do
|
|
73
|
+
code { JSON.pretty_generate(@params.to_unsafe_h) }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def index
|
|
80
|
+
render IndexPage.new, layout: false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def show
|
|
84
|
+
index = params[:id].to_i
|
|
85
|
+
form_class = form_classes[index]
|
|
86
|
+
render ShowPage.new(form_class: form_class, action: request.path), layout: false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def create
|
|
90
|
+
index = params[:id].to_i
|
|
91
|
+
form_class = form_classes[index]
|
|
92
|
+
render SubmitPage.new(form_class: form_class, params: params, form_path: request.path), layout: false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Example
|
|
4
|
+
include ActiveModel::Model
|
|
5
|
+
include ActiveModel::Attributes
|
|
6
|
+
|
|
7
|
+
attribute :name, :string
|
|
8
|
+
attribute :email, :string
|
|
9
|
+
attribute :password, :string
|
|
10
|
+
attribute :age, :integer
|
|
11
|
+
attribute :title, :string
|
|
12
|
+
attribute :body, :string
|
|
13
|
+
attribute :country, :string
|
|
14
|
+
attribute :priority, :string
|
|
15
|
+
attribute :quantity, :integer
|
|
16
|
+
attribute :terms_accepted, :boolean
|
|
17
|
+
attribute :subscribe, :boolean
|
|
18
|
+
attribute :featured, :boolean
|
|
19
|
+
attribute :birth_date, :date
|
|
20
|
+
attribute :appointment_time, :time
|
|
21
|
+
attribute :event_datetime, :datetime
|
|
22
|
+
attribute :favorite_color, :string
|
|
23
|
+
attribute :volume, :integer
|
|
24
|
+
attribute :search_query, :string
|
|
25
|
+
attribute :avatar, :string
|
|
26
|
+
attribute :address, :string
|
|
27
|
+
|
|
28
|
+
def self.model_name
|
|
29
|
+
ActiveModel::Name.new(self, nil, "Example")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/* Superform Examples */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #ffffff;
|
|
5
|
+
--bg-secondary: #f5f5f7;
|
|
6
|
+
--fg: #1d1d1f;
|
|
7
|
+
--fg-secondary: #6e6e73;
|
|
8
|
+
--border: #c7c7cc;
|
|
9
|
+
--accent: #007aff;
|
|
10
|
+
--radius: 8px;
|
|
11
|
+
--font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", system-ui, sans-serif;
|
|
12
|
+
--font-mono: "SF Mono", SFMono-Regular, ui-monospace, Menlo, monospace;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--bg: #000000;
|
|
18
|
+
--bg-secondary: #1c1c1e;
|
|
19
|
+
--fg: #f5f5f7;
|
|
20
|
+
--fg-secondary: #86868b;
|
|
21
|
+
--border: #38383a;
|
|
22
|
+
--accent: #0a84ff;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
* {
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
html {
|
|
31
|
+
font-family: var(--font);
|
|
32
|
+
font-size: 17px;
|
|
33
|
+
line-height: 1.47059;
|
|
34
|
+
-webkit-font-smoothing: antialiased;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
body {
|
|
38
|
+
margin: 0;
|
|
39
|
+
padding: 48px 24px;
|
|
40
|
+
background: var(--bg);
|
|
41
|
+
color: var(--fg);
|
|
42
|
+
max-width: 600px;
|
|
43
|
+
margin-inline: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
h1, h2, h3 {
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
letter-spacing: -0.015em;
|
|
49
|
+
margin: 0 0 8px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
h1 { font-size: 21px; }
|
|
53
|
+
h2 { font-size: 19px; }
|
|
54
|
+
h3 { font-size: 14px; color: var(--fg-secondary); font-weight: 500; letter-spacing: 0; }
|
|
55
|
+
|
|
56
|
+
p {
|
|
57
|
+
margin: 0 0 16px;
|
|
58
|
+
color: var(--fg-secondary);
|
|
59
|
+
font-size: 15px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
a {
|
|
63
|
+
color: var(--accent);
|
|
64
|
+
text-decoration: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
a:hover {
|
|
68
|
+
text-decoration: underline;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
hr {
|
|
72
|
+
border: none;
|
|
73
|
+
border-top: 1px solid var(--border);
|
|
74
|
+
margin: 32px 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pre, code {
|
|
78
|
+
font-family: var(--font-mono);
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
code {
|
|
83
|
+
background: var(--bg-secondary);
|
|
84
|
+
padding: 2px 6px;
|
|
85
|
+
border-radius: 4px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pre {
|
|
89
|
+
background: var(--bg-secondary);
|
|
90
|
+
border-radius: var(--radius);
|
|
91
|
+
padding: 16px;
|
|
92
|
+
overflow-x: auto;
|
|
93
|
+
margin: 0 0 16px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pre code {
|
|
97
|
+
background: none;
|
|
98
|
+
padding: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Header */
|
|
102
|
+
header {
|
|
103
|
+
margin-bottom: 32px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
header h1 {
|
|
107
|
+
margin: 0;
|
|
108
|
+
font-size: 21px;
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
header h1 a {
|
|
113
|
+
color: var(--fg);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
header h1 a:hover {
|
|
117
|
+
text-decoration: none;
|
|
118
|
+
color: var(--accent);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Breadcrumb */
|
|
122
|
+
nav.breadcrumb {
|
|
123
|
+
margin-bottom: 24px;
|
|
124
|
+
font-size: 15px;
|
|
125
|
+
color: var(--fg-secondary);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
nav.breadcrumb .crumb + .crumb::before {
|
|
129
|
+
content: "›";
|
|
130
|
+
margin: 0 8px;
|
|
131
|
+
color: var(--fg-secondary);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Forms */
|
|
135
|
+
form {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: 16px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fieldset {
|
|
142
|
+
border: 1px solid var(--border);
|
|
143
|
+
border-radius: var(--radius);
|
|
144
|
+
padding: 16px;
|
|
145
|
+
margin: 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
legend {
|
|
149
|
+
padding: 0 8px;
|
|
150
|
+
font-size: 15px;
|
|
151
|
+
font-weight: 500;
|
|
152
|
+
color: var(--fg-secondary);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
label {
|
|
156
|
+
display: block;
|
|
157
|
+
font-size: 13px;
|
|
158
|
+
font-weight: 500;
|
|
159
|
+
margin-bottom: 4px;
|
|
160
|
+
color: var(--fg-secondary);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
label + input,
|
|
164
|
+
label + textarea,
|
|
165
|
+
label + select {
|
|
166
|
+
margin-top: 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
input[type="text"],
|
|
170
|
+
input[type="email"],
|
|
171
|
+
input[type="password"],
|
|
172
|
+
input[type="number"],
|
|
173
|
+
input[type="date"],
|
|
174
|
+
input[type="time"],
|
|
175
|
+
input[type="datetime-local"],
|
|
176
|
+
input[type="url"],
|
|
177
|
+
input[type="tel"],
|
|
178
|
+
input[type="search"],
|
|
179
|
+
input[type="color"],
|
|
180
|
+
input[type="file"],
|
|
181
|
+
textarea,
|
|
182
|
+
select {
|
|
183
|
+
display: block;
|
|
184
|
+
width: 100%;
|
|
185
|
+
padding: 7px 10px;
|
|
186
|
+
font-family: inherit;
|
|
187
|
+
font-size: 17px;
|
|
188
|
+
color: var(--fg);
|
|
189
|
+
background: var(--bg);
|
|
190
|
+
border: 1px solid var(--border);
|
|
191
|
+
border-radius: var(--radius);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
input:focus,
|
|
195
|
+
textarea:focus,
|
|
196
|
+
select:focus {
|
|
197
|
+
outline: none;
|
|
198
|
+
border-color: var(--accent);
|
|
199
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
input[type="checkbox"],
|
|
203
|
+
input[type="radio"] {
|
|
204
|
+
accent-color: var(--accent);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
input[type="range"] {
|
|
208
|
+
accent-color: var(--accent);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
input[type="color"] {
|
|
212
|
+
padding: 4px;
|
|
213
|
+
height: 38px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
input[type="file"] {
|
|
217
|
+
font-size: 15px;
|
|
218
|
+
padding: 6px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
textarea {
|
|
222
|
+
min-height: 80px;
|
|
223
|
+
resize: vertical;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
button,
|
|
227
|
+
input[type="submit"] {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
padding: 8px 16px;
|
|
232
|
+
font-family: inherit;
|
|
233
|
+
font-size: 15px;
|
|
234
|
+
font-weight: 500;
|
|
235
|
+
color: #fff;
|
|
236
|
+
background: var(--accent);
|
|
237
|
+
border: none;
|
|
238
|
+
border-radius: var(--radius);
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
button:hover,
|
|
243
|
+
input[type="submit"]:hover {
|
|
244
|
+
filter: brightness(1.1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* List */
|
|
248
|
+
article {
|
|
249
|
+
padding: 16px 0;
|
|
250
|
+
border-bottom: 1px solid var(--border);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
article:first-of-type {
|
|
254
|
+
padding-top: 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
article:last-of-type {
|
|
258
|
+
border-bottom: none;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
article h2 {
|
|
262
|
+
margin-bottom: 4px;
|
|
263
|
+
font-size: 17px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
article h2 a {
|
|
267
|
+
color: var(--fg);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
article h2 a:hover {
|
|
271
|
+
color: var(--accent);
|
|
272
|
+
text-decoration: none;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
article p {
|
|
276
|
+
margin: 0;
|
|
277
|
+
font-size: 15px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
article p code {
|
|
281
|
+
font-size: 12px;
|
|
282
|
+
}
|
data/server/rails.rb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
|
|
5
|
+
require "active_model"
|
|
6
|
+
require "action_controller/railtie"
|
|
7
|
+
require "phlex-rails"
|
|
8
|
+
|
|
9
|
+
# Load superform from this repo
|
|
10
|
+
require_relative "../lib/superform"
|
|
11
|
+
require_relative "../lib/superform/rails"
|
|
12
|
+
|
|
13
|
+
# Directories
|
|
14
|
+
SERVER_DIR = Pathname.new(__dir__)
|
|
15
|
+
EXAMPLES_DIR = SERVER_DIR.join("../examples").expand_path
|
|
16
|
+
|
|
17
|
+
# Minimal Rails app
|
|
18
|
+
class SuperformApp < Rails::Application
|
|
19
|
+
config.root = SERVER_DIR
|
|
20
|
+
config.eager_load = false
|
|
21
|
+
config.consider_all_requests_local = true
|
|
22
|
+
config.secret_key_base = "superform-demo-secret-key-base-for-development-only"
|
|
23
|
+
config.hosts.clear
|
|
24
|
+
config.public_file_server.enabled = true
|
|
25
|
+
|
|
26
|
+
config.autoload_paths << root.join("components")
|
|
27
|
+
config.autoload_paths << root.join("models")
|
|
28
|
+
config.autoload_paths << root.join("controllers")
|
|
29
|
+
|
|
30
|
+
config.autoload_paths << EXAMPLES_DIR
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Collect form classes dynamically
|
|
34
|
+
def form_classes
|
|
35
|
+
EXAMPLES_DIR.glob("*.rb").sort.filter_map do |path|
|
|
36
|
+
next if path.basename.to_s == "example_form.rb"
|
|
37
|
+
cpath = Rails.autoloaders.main.cpath_expected_at(path)
|
|
38
|
+
Object.const_get(cpath)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
SuperformApp.initialize!
|
|
43
|
+
|
|
44
|
+
SuperformApp.routes.draw do
|
|
45
|
+
root to: "forms#index"
|
|
46
|
+
get "/forms/:id" => "forms#show", as: :form
|
|
47
|
+
post "/forms/:id" => "forms#create"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.start(port: 3000)
|
|
51
|
+
require "rackup"
|
|
52
|
+
puts "Starting Superform Examples on http://localhost:#{port}"
|
|
53
|
+
puts "Press Ctrl+C to stop"
|
|
54
|
+
puts
|
|
55
|
+
Rackup::Handler::WEBrick.run(SuperformApp, Port: port)
|
|
56
|
+
end
|