diff --git a/app/assets/stylesheets/application/support-form.sass b/app/assets/stylesheets/application/support-form.sass
index bf928a3a..52875210 100644
--- a/app/assets/stylesheets/application/support-form.sass
+++ b/app/assets/stylesheets/application/support-form.sass
@@ -23,6 +23,10 @@
display: block
font-weight: bold
+ &[data-type=radio]
+ ul
+ list-style-type: none
+
&[data-type=radio-grid] // Set the `--num-columns` property to configure!
max-width: none
diff --git a/app/helpers/support_form_helper.rb b/app/helpers/support_form_helper.rb
new file mode 100644
index 00000000..a680862c
--- /dev/null
+++ b/app/helpers/support_form_helper.rb
@@ -0,0 +1,42 @@
+module SupportFormHelper
+ class SupportFormBuilder < ActionView::Helpers::FormBuilder
+ attr_reader :template
+ delegate :concat, :content_tag, to: :template, private: true
+
+ def fields(&block)
+ content_tag(:ul, class: "fields", &block)
+ end
+
+ def field(**kwargs, &block)
+ content_tag(:li, **kwargs, &block)
+ end
+
+ def radio_fieldset(legend, **kwargs, &block)
+ kwargs.reverse_merge!("data-type": "radio")
+ field(**kwargs) do
+ content_tag(:fieldset) do
+ concat content_tag(:legend, legend)
+ concat content_tag(:ul, &block)
+ end
+ end
+ end
+
+ def radio_field(**kwargs, &block)
+ content_tag(:li) do
+ content_tag(:label, **kwargs, &block)
+ end
+ end
+
+ def radio_grid_fieldset(*args, &block)
+ radio_fieldset(*args, "data-type": "radio-grid", &block)
+ end
+ end
+
+ def support_form_with(**kwargs, &block)
+ kwargs.merge!(
+ builder: SupportFormBuilder,
+ class: ["support-form", kwargs[:class]],
+ )
+ form_with(**kwargs, &block)
+ end
+end
diff --git a/app/views/alt_styles/edit.html.haml b/app/views/alt_styles/edit.html.haml
index 5e13d291..86844a16 100644
--- a/app/views/alt_styles/edit.html.haml
+++ b/app/views/alt_styles/edit.html.haml
@@ -13,24 +13,27 @@
= image_tag @alt_style.preview_image_url, class: "alt-style-preview"
-= form_with model: @alt_style, class: "support-form" do |f|
+= support_form_with model: @alt_style, class: "support-form" do |f|
- if @alt_style.errors.any?
%p
Could not save:
%ul.errors
- @alt_style.errors.each do |error|
%li= error.full_message
- %ul.fields
- %li
+
+ = f.fields do
+ = f.field do
= f.label :real_series_name, "Series"
= f.text_field :real_series_name, autofocus: !@alt_style.real_series_name?,
placeholder: AltStyle.placeholder_name
- %li
+
+ = f.field do
= f.label :thumbnail_url, "Thumbnail"
.thumbnail-input
- if @alt_style.thumbnail_url?
= image_tag @alt_style.thumbnail_url
= f.url_field :thumbnail_url
+
.actions
= f.submit "Save changes"
%label{title: "If checked, takes you to the next unlabeled pet style, if any. Useful for labeling in bulk!"}
diff --git a/app/views/items/edit.html.haml b/app/views/items/edit.html.haml
index f7261a23..e2ed9054 100644
--- a/app/views/items/edit.html.haml
+++ b/app/views/items/edit.html.haml
@@ -8,56 +8,57 @@
you change something, but it doesn't match what we're seeing on Neopets.com,
it will probably be reverted automatically when someone models it.
-= form_with model: @item, class: "support-form" do |f|
+= support_form_with model: @item, class: "support-form" do |f|
- if @item.errors.any?
%p
Could not save:
%ul.errors
- @item.errors.each do |error|
%li= error.full_message
- %ul.fields
- %li
+
+ = f.fields do
+ = f.field do
= f.label :name
= f.text_field :name
- %li
+
+ = f.field do
= f.label :thumbnail_url, "Thumbnail"
.thumbnail-input
- if @item.thumbnail_url?
= image_tag @item.thumbnail_url
= f.url_field :thumbnail_url
- %li
+
+ = f.field do
= f.label :description
= f.text_field :description
- %li
- %fieldset{"data-type": "radio"}
- %legend Item kind
- %label{title: "NC items generally have a rarity value of 500.\nPaintbrush items generally contain a special message in the description."}
- = f.radio_button :is_manually_nc, false
- Automatic: Based on rarity and description
- %label{title: "Use this when Neopets releases an NC item, but labels the rarity as something other than 500, usually by mistake."}
- = f.radio_button :is_manually_nc, true
- Manually NC: From the NC Mall, but not r500
- %li
- %fieldset{"data-type": "radio"}
- %legend Modeling status
- %label{title: "If we fit two or more species of a standard color, assume we also fit the other standard-color pets that were released at the time.\nRepeat for special colors like Baby and Maraquan."}
- = f.radio_button :modeling_status_hint, ""
- Automatic: Fits 2+ species → Should fit all
- %label{title: "Use this when e.g. there simply is no Acara version of the item."}
- = f.radio_button :modeling_status_hint, "done"
- Done: Neopets.com is missing some models
- %label{title: "Use this when e.g. this fits the Blue Vandagyre even though it's a Maraquan item.\nBehaves identically to Done, but helps us remember why we did this!"}
- = f.radio_button :modeling_status_hint, "glitchy"
- Glitchy: Neopets.com has too many models
- %li
- %fieldset{"data-type": "radio"}
- %legend Body fit
- %label{title: "When an asset in a zone like Background is modeled, assume it fits all pets the same, and assign it body ID \#0.\nOtherwise, assume it fits only the kind of pet it was modeled on."}
- = f.radio_button :explicitly_body_specific, false
- Automatic: Some zones fit all species
- %label{title: "Use this when an item uses a generally-universal zone like Static, but is body-specific regardless. \"Encased in Ice\" is one example.\nThis prevents these uncommon items from breaking every time they're modeled."}
- = f.radio_button :explicitly_body_specific, true
- Body-specific: Fits all species differently
+
+ = f.radio_fieldset "Item kind" do
+ = f.radio_field title: "NC items generally have a rarity value of 500.\nPaintbrush items generally contain a special message in the description." do
+ = f.radio_button :is_manually_nc, false
+ Automatic: Based on rarity and description
+ = f.radio_field title: "Use this when Neopets releases an NC item, but labels the rarity as something other than 500, usually by mistake." do
+ = f.radio_button :is_manually_nc, true
+ Manually NC: From the NC Mall, but not r500
+
+ = f.radio_fieldset "Modeling status" do
+ = f.radio_field title: "If we fit two or more species of a standard color, assume we also fit the other standard-color pets that were released at the time.\nRepeat for special colors like Baby and Maraquan." do
+ = f.radio_button :modeling_status_hint, ""
+ Automatic: Fits 2+ species → Should fit all
+ = f.radio_field title: "Use this when e.g. there simply is no Acara version of the item." do
+ = f.radio_button :modeling_status_hint, "done"
+ Done: Neopets.com is missing some models
+ = f.radio_field title: "Use this when e.g. this fits the Blue Vandagyre even though it's a Maraquan item.\nBehaves identically to Done, but helps us remember why we did this!" do
+ = f.radio_button :modeling_status_hint, "glitchy"
+ Glitchy: Neopets.com has too many models
+
+ = f.radio_fieldset "Body fit" do
+ = f.radio_field title: "When an asset in a zone like Background is modeled, assume it fits all pets the same, and assign it body ID \#0.\nOtherwise, assume it fits only the kind of pet it was modeled on." do
+ = f.radio_button :explicitly_body_specific, false
+ Automatic: Some zones fit all species
+ = f.radio_field title: "Use this when an item uses a generally-universal zone like Static, but is body-specific regardless. \"Encased in Ice\" is one example.\nThis prevents these uncommon items from breaking every time they're modeled." do
+ = f.radio_button :explicitly_body_specific, true
+ Body-specific: Fits all species differently
+
.actions
= f.submit "Save changes"
diff --git a/app/views/pet_states/edit.html.haml b/app/views/pet_states/edit.html.haml
index 9531a75c..6caee0cd 100644
--- a/app/views/pet_states/edit.html.haml
+++ b/app/views/pet_states/edit.html.haml
@@ -17,24 +17,22 @@
= outfit_viewer pet_state: @pet_state
-= form_with model: [@pet_type, @pet_state], class: "support-form" do |f|
+= support_form_with model: [@pet_type, @pet_state] do |f|
- if @pet_state.errors.any?
%p
Could not save:
%ul.errors
- @pet_state.errors.each do |error|
%li= error.full_message
- %ul.fields
- %li{"data-type": "radio-grid"}
- %fieldset
- %legend Pose
- %ul
- - pose_options.each do |pose|
- %li
- %label
- = f.radio_button :pose, pose
- = pose_name pose
- %li
+
+ = f.fields do
+ = f.radio_grid_fieldset "Pose" do
+ - pose_options.each do |pose|
+ = f.radio_field do
+ = f.radio_button :pose, pose
+ = pose_name(pose)
+
+ = f.field do
= f.label :glitched, "Gliched?"
= f.select :glitched, [["✅ Not marked as Glitched", false],
["👾 Yes, it's bad news bonko'd", true]]