superform 0.6.1 → 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 -31
- 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 +91 -44
- 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 +26 -4
- data/lib/superform/rails/option_mapper.rb +0 -36
|
@@ -12,8 +12,8 @@ module Superform
|
|
|
12
12
|
# end
|
|
13
13
|
#
|
|
14
14
|
# class Field < Field
|
|
15
|
-
# def label(
|
|
16
|
-
# MyLabel.new(self,
|
|
15
|
+
# def label(**attributes, &)
|
|
16
|
+
# MyLabel.new(self, **attributes, &)
|
|
17
17
|
# end
|
|
18
18
|
#
|
|
19
19
|
# def input(class: nil, **)
|
|
@@ -26,27 +26,53 @@ module Superform
|
|
|
26
26
|
# Now all calls to `label` will have the `text-bold` class applied to it.
|
|
27
27
|
class Field < Superform::Field
|
|
28
28
|
def button(**attributes)
|
|
29
|
-
Components::Button.new(field, attributes
|
|
29
|
+
Components::Button.new(field, **attributes)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def input(**attributes)
|
|
33
|
-
Components::Input.new(field, attributes
|
|
33
|
+
Components::Input.new(field, **attributes)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def checkbox(**attributes)
|
|
37
|
-
Components::Checkbox.new(field, attributes
|
|
36
|
+
def checkbox(index: nil, **attributes)
|
|
37
|
+
Components::Checkbox.new(field, index:, **attributes)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def label(**attributes, &)
|
|
41
|
-
Components::Label.new(field, attributes
|
|
41
|
+
Components::Label.new(field, **attributes, &)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def textarea(**attributes)
|
|
45
|
-
Components::Textarea.new(field, attributes
|
|
45
|
+
Components::Textarea.new(field, **attributes)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def select(*
|
|
49
|
-
Components::Select.new(
|
|
48
|
+
def select(*options, multiple: false, **attributes, &)
|
|
49
|
+
Components::Select.new(
|
|
50
|
+
field,
|
|
51
|
+
options:,
|
|
52
|
+
multiple:,
|
|
53
|
+
**attributes,
|
|
54
|
+
&
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def datalist(*options, **attributes, &block)
|
|
59
|
+
Components::Datalist.new(field, options:, **attributes, &block)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def errors
|
|
63
|
+
object.errors[key]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def invalid?
|
|
67
|
+
errors.any?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def valid?
|
|
71
|
+
not invalid?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def human_attribute_name
|
|
75
|
+
object.class.human_attribute_name key
|
|
50
76
|
end
|
|
51
77
|
|
|
52
78
|
# HTML5 input type convenience methods - clean API without _field suffix
|
|
@@ -56,73 +82,83 @@ module Superform
|
|
|
56
82
|
# field(:birthday).date
|
|
57
83
|
# field(:secret).hidden(value: "token123")
|
|
58
84
|
# field(:gender).radio("male", id: "user_gender_male")
|
|
59
|
-
def text(*,
|
|
60
|
-
input(*, **, type: :text
|
|
85
|
+
def text(*, **)
|
|
86
|
+
input(*, **, type: :text)
|
|
61
87
|
end
|
|
62
88
|
|
|
63
|
-
def hidden(*,
|
|
64
|
-
input(*, **, type: :hidden
|
|
89
|
+
def hidden(*, **)
|
|
90
|
+
input(*, **, type: :hidden)
|
|
65
91
|
end
|
|
66
92
|
|
|
67
|
-
def password(*,
|
|
68
|
-
input(*, **, type: :password
|
|
93
|
+
def password(*, **)
|
|
94
|
+
input(*, **, type: :password)
|
|
69
95
|
end
|
|
70
96
|
|
|
71
|
-
def email(*,
|
|
72
|
-
input(*, **, type: :email
|
|
97
|
+
def email(*, **)
|
|
98
|
+
input(*, **, type: :email)
|
|
73
99
|
end
|
|
74
100
|
|
|
75
|
-
def url(*,
|
|
76
|
-
input(*, **, type: :url
|
|
101
|
+
def url(*, **)
|
|
102
|
+
input(*, **, type: :url)
|
|
77
103
|
end
|
|
78
104
|
|
|
79
|
-
def tel(*,
|
|
80
|
-
input(*, **, type: :tel
|
|
105
|
+
def tel(*, **)
|
|
106
|
+
input(*, **, type: :tel)
|
|
81
107
|
end
|
|
82
108
|
alias_method :phone, :tel
|
|
83
109
|
|
|
84
|
-
def number(*,
|
|
85
|
-
input(*, **, type: :number
|
|
110
|
+
def number(*, **)
|
|
111
|
+
input(*, **, type: :number)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def range(*, **)
|
|
115
|
+
input(*, **, type: :range)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def date(*, **)
|
|
119
|
+
input(*, **, type: :date)
|
|
86
120
|
end
|
|
87
121
|
|
|
88
|
-
def
|
|
89
|
-
input(*, **, type: :
|
|
122
|
+
def time(*, **)
|
|
123
|
+
input(*, **, type: :time)
|
|
90
124
|
end
|
|
91
125
|
|
|
92
|
-
def
|
|
93
|
-
input(*, **, type: :
|
|
126
|
+
def datetime(*, **)
|
|
127
|
+
input(*, **, type: :"datetime-local")
|
|
94
128
|
end
|
|
95
129
|
|
|
96
|
-
def
|
|
97
|
-
input(*, **, type: :
|
|
130
|
+
def month(*, **)
|
|
131
|
+
input(*, **, type: :month)
|
|
98
132
|
end
|
|
99
133
|
|
|
100
|
-
def
|
|
101
|
-
input(*, **, type: :
|
|
134
|
+
def week(*, **)
|
|
135
|
+
input(*, **, type: :week)
|
|
102
136
|
end
|
|
103
137
|
|
|
104
|
-
def
|
|
105
|
-
input(*, **, type: :
|
|
138
|
+
def color(*, **)
|
|
139
|
+
input(*, **, type: :color)
|
|
106
140
|
end
|
|
107
141
|
|
|
108
|
-
def
|
|
109
|
-
input(*, **, type: :
|
|
142
|
+
def search(*, **)
|
|
143
|
+
input(*, **, type: :search)
|
|
110
144
|
end
|
|
111
145
|
|
|
112
|
-
def
|
|
113
|
-
input(*, **, type: :
|
|
146
|
+
def file(*, **)
|
|
147
|
+
input(*, **, type: :file)
|
|
114
148
|
end
|
|
115
149
|
|
|
116
|
-
def
|
|
117
|
-
|
|
150
|
+
def radio(value, index: value, **attributes)
|
|
151
|
+
Components::Radio.new(field, value:, index:, **attributes)
|
|
118
152
|
end
|
|
119
153
|
|
|
120
|
-
def
|
|
121
|
-
|
|
154
|
+
def radios(*options, **attributes, &block)
|
|
155
|
+
options = enum_options if options.empty?
|
|
156
|
+
Components::Radios.new(field, options:, **attributes, &block)
|
|
122
157
|
end
|
|
123
158
|
|
|
124
|
-
def
|
|
125
|
-
|
|
159
|
+
def checkboxes(*options, **attributes, &block)
|
|
160
|
+
options = enum_options if options.empty?
|
|
161
|
+
Components::Checkboxes.new(field, options:, **attributes, &block)
|
|
126
162
|
end
|
|
127
163
|
|
|
128
164
|
# Rails compatibility aliases
|
|
@@ -132,6 +168,17 @@ module Superform
|
|
|
132
168
|
def title
|
|
133
169
|
key.to_s.titleize
|
|
134
170
|
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
def enum_options
|
|
175
|
+
return [] unless object
|
|
176
|
+
enums = object.class.try(:defined_enums)
|
|
177
|
+
return [] unless enums
|
|
178
|
+
enum = enums[key.to_s]
|
|
179
|
+
return [] unless enum
|
|
180
|
+
enum.keys.map { |k| [k, k.humanize] }
|
|
181
|
+
end
|
|
135
182
|
end
|
|
136
183
|
end
|
|
137
184
|
end
|
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
|
+
}
|