diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css
index 00fb5d95..357724d9 100644
--- a/app/assets/stylesheets/application.tailwind.css
+++ b/app/assets/stylesheets/application.tailwind.css
@@ -15,16 +15,16 @@
@layer components {
.form-field {
- @apply relative rounded-md border bg-white border-alpha-black-100 shadow-xs;
+ @apply flex flex-col gap-1 relative px-3 py-2 rounded-md border bg-white border-alpha-black-100 shadow-xs w-full;
@apply focus-within:border-gray-900 focus-within:shadow-none focus-within:ring-4 focus-within:ring-gray-100;
}
.form-field__label {
- @apply block px-3 pt-2 pb-0 text-xs text-gray-500;
+ @apply block text-xs text-gray-500;
}
.form-field__input {
- @apply w-full border-none bg-transparent px-3 pt-1 pb-2 text-sm opacity-100;
+ @apply border-none bg-transparent text-sm opacity-100 w-full p-0;
@apply focus:opacity-100 focus:outline-none focus:ring-0;
@apply placeholder-shown:opacity-50;
@apply disabled:opacity-50;
@@ -58,6 +58,19 @@
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='111827' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
+ select[multiple="multiple"] {
+ @apply py-2 pr-2 space-y-0.5;
+ }
+
+ select[multiple="multiple"] option {
+ @apply p-2 rounded-md;
+ }
+
+ select[multiple="multiple"] option:checked {
+ @apply bg-gray-50;
+ @apply after:content-['\2713'] after:float-right after:text-gray-500;
+ }
+
.maybe-switch {
@apply block bg-gray-100 w-9 h-5 rounded-full cursor-pointer;
@apply after:content-[''] after:block after:absolute after:top-0.5 after:left-0.5 after:bg-white after:w-4 after:h-4 after:rounded-full after:transition-transform after:duration-300 after:ease-in-out;
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index e703dca4..9eaada46 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -19,6 +19,7 @@ class AccountsController < ApplicationController
end
def list
+ render layout: false
end
def new
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 500432af..84f8a8fe 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -2,8 +2,6 @@ class ApplicationController < ActionController::Base
include Authentication, Invitable, SelfHostable
include Pagy::Backend
- default_form_builder ApplicationFormBuilder
-
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
end
diff --git a/app/controllers/currencies_controller.rb b/app/controllers/currencies_controller.rb
index d12f835a..71e085b7 100644
--- a/app/controllers/currencies_controller.rb
+++ b/app/controllers/currencies_controller.rb
@@ -1,6 +1,6 @@
class CurrenciesController < ApplicationController
def show
- @currency = Money::Currency.all_instances.find { |currency| currency.iso_code == params[:id] }
- render json: { step: @currency.step, placeholder: Money.new(0, @currency).format }
+ currency = Money::Currency.all_instances.find { |currency| currency.iso_code == params[:id] }
+ render json: currency.as_json.merge({ step: currency.step })
end
end
diff --git a/app/helpers/application_form_builder.rb b/app/helpers/application_form_builder.rb
deleted file mode 100644
index 5c0fb8b7..00000000
--- a/app/helpers/application_form_builder.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-class ApplicationFormBuilder < ActionView::Helpers::FormBuilder
- def initialize(object_name, object, template, options)
- options[:html] ||= {}
- options[:html][:class] ||= "space-y-4"
-
- super(object_name, object, template, options)
- end
-
- (field_helpers - [ :label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field ]).each do |selector|
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{selector}(method, options = {})
- default_options = { class: "form-field__input" }
- merged_options = default_options.merge(options)
-
- return super(method, merged_options) unless options[:label]
-
- @template.form_field_tag do
- label(method, *label_args(options)) +
- super(method, merged_options.except(:label))
- end
- end
- RUBY_EVAL
- end
-
- # See `Monetizable` concern, which adds a _money suffix to the attribute name
- # For a monetized field, the setter will always be the attribute name without the _money suffix
- def money_field(method, options = {})
- money = @object && @object.respond_to?(method) ? @object.send(method) : nil
- raise ArgumentError, "The value of #{method} is not a Money object" unless money.is_a?(Money) || money.nil?
-
- money_amount_method = method.to_s.chomp("_money").to_sym
- money_currency_method = :currency
-
- readonly_currency = options[:readonly_currency] || false
-
- currency = money&.currency || Money::Currency.new(Current.family.currency) || Money.default_currency
- default_options = {
- class: "form-field__input",
- value: money&.amount,
- "data-money-field-target" => "amount",
- placeholder: Money.new(0, currency).format,
- min: -99999999999999,
- max: 99999999999999,
- step: currency.step
- }
-
- merged_options = default_options.merge(options)
-
- grouped_options = currency_options_for_select
- selected_currency = money&.currency&.iso_code || currency.iso_code
-
- @template.form_field_tag data: { controller: "money-field" } do
- (label(method, *label_args(options)).to_s if options[:label]) +
- @template.tag.div(class: "flex items-center") do
- number_field(money_amount_method, merged_options.except(:label)) +
- grouped_select(money_currency_method, grouped_options, { selected: selected_currency }, disabled: readonly_currency, class: "ml-auto form-field__input w-fit pr-8", data: { "money-field-target" => "currency", action: "change->money-field#handleCurrencyChange" })
- end
- end
- end
-
- def radio_button(method, tag_value, options = {})
- default_options = { class: "form-field__radio" }
- merged_options = default_options.merge(options)
- super(method, tag_value, merged_options)
- end
-
- def grouped_select(method, grouped_choices, options = {}, html_options = {})
- default_options = { class: "form-field__input" }
- merged_html_options = default_options.merge(html_options)
-
- label_html = label(method, *label_args(options)).to_s if options[:label]
- select_html = @template.grouped_collection_select(@object_name, method, grouped_choices, :last, :first, :last, :first, options, merged_html_options)
-
- @template.content_tag(:div, class: "flex items-center") do
- label_html.to_s.html_safe + select_html
- end
- end
-
- def currency_select(method, options = {}, html_options = {})
- default_options = { class: "form-field__input" }
- merged_options = default_options.merge(html_options)
-
- choices = currency_options_for_select
-
- return @template.grouped_collection_select(@object_name, method, choices, :last, :first, :last, :first, options, merged_options) unless options[:label]
-
- @template.form_field_tag do
- label(method, *label_args(options)) +
- @template.grouped_collection_select(@object_name, method, choices, :last, :first, :last, :first, options, merged_options.except(:label))
- end
- end
-
- def select(method, choices, options = {}, html_options = {})
- default_options = { class: "form-field__input" }
- merged_options = default_options.merge(html_options)
-
- return super(method, choices, options, merged_options) unless options[:label]
-
- @template.form_field_tag do
- label(method, *label_args(options)) +
- super(method, choices, options, merged_options.except(:label))
- end
- end
-
- def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
- default_options = { class: "form-field__input" }
- merged_options = default_options.merge(html_options)
-
- return super(method, collection, value_method, text_method, options, merged_options) unless options[:label]
-
- @template.form_field_tag do
- label(method, *label_args(options)) +
- super(method, collection, value_method, text_method, options, merged_options.except(:label))
- end
- end
-
- def submit(value = nil, options = {})
- value, options = nil, value if value.is_a?(Hash)
- default_options = { class: "form-field__submit" }
- merged_options = default_options.merge(options)
- super(value, merged_options)
- end
-
- private
-
- def currency_options_for_select
- popular_currencies = Money::Currency.popular.map { |currency| [ currency.iso_code, currency.iso_code ] }
- all_currencies = Money::Currency.all_instances.map { |currency| [ currency.iso_code, currency.iso_code ] }
- all_other_currencies = all_currencies.reject { |c| popular_currencies.map(&:last).include?(c.last) }.sort_by(&:last)
-
- {
- I18n.t("accounts.new.currency.popular") => popular_currencies,
- I18n.t("accounts.new.currency.all_others") => all_other_currencies
- }
- end
-
- def label_args(options)
- case options[:label]
- when Array
- options[:label]
- when String
- [ options[:label], { class: "form-field__label" } ]
- when Hash
- [ nil, options[:label] ]
- else
- [ nil, { class: "form-field__label" } ]
- end
- end
-end
diff --git a/app/helpers/forms_helper.rb b/app/helpers/forms_helper.rb
index 86968e02..356f928c 100644
--- a/app/helpers/forms_helper.rb
+++ b/app/helpers/forms_helper.rb
@@ -1,7 +1,12 @@
module FormsHelper
+ def styled_form_with(**options, &block)
+ options[:builder] = StyledFormBuilder
+ form_with(**options, &block)
+ end
+
def form_field_tag(options = {}, &block)
options[:class] = [ "form-field", options[:class] ].compact.join(" ")
- tag.div **options, &block
+ tag.div(**options, &block)
end
def radio_tab_tag(form:, name:, value:, label:, icon:, checked: false, disabled: false)
@@ -11,23 +16,59 @@ module FormsHelper
end
end
- def selectable_categories
- Current.family.categories.alphabetically
+ def period_select(form:, selected:, classes: "border border-alpha-black-100 shadow-xs rounded-lg text-sm pr-7 cursor-pointer text-gray-900 focus:outline-none focus:ring-0")
+ periods_for_select = [ [ "7D", "last_7_days" ], [ "1M", "last_30_days" ], [ "1Y", "last_365_days" ], [ "All", "all" ] ]
+ form.select(:period, periods_for_select, { selected: selected }, class: classes, data: { "auto-submit-form-target": "auto" })
end
- def selectable_merchants
- Current.family.merchants.alphabetically
+ def money_with_currency_field(form, money_method, options = {})
+ render partial: "shared/money_field", locals: {
+ form: form,
+ money_method: money_method,
+ disable_currency: options[:disable_currency] || false,
+ hide_currency: options[:hide_currency] || false,
+ label: options[:label] || "Amount"
+ }
end
- def selectable_accounts
- Current.family.accounts.alphabetically
+ def money_field(form, method, options = {})
+ value = form.object.send(method)
+
+ currency = value&.currency || Money::Currency.new(options[:default_currency] || "USD")
+
+ # See "Monetizable" concern
+ money_amount_method = method.to_s.chomp("_money").to_sym
+
+ money_options = {
+ value: value&.amount,
+ placeholder: 100,
+ min: -99999999999999,
+ max: 99999999999999,
+ step: currency.step
+ }
+
+ merged_options = options.merge(money_options)
+
+ form.number_field money_amount_method, merged_options
end
- def selectable_tags
- Current.family.tags.alphabetically.pluck(:name, :id)
+ def currency_select_full(form, method, options = {}, html_options = {}, &block)
+ choices = currencies_for_select.map { |currency| [ "#{currency.name} (#{currency.iso_code})", currency.iso_code ] }
+ form.select method, choices, options, html_options, &block
+ end
+
+ def currency_select(form, method, options = {}, html_options = {}, &block)
+ choices = currencies_for_select.map(&:iso_code)
+ form.select method, choices, options, html_options, &block
end
private
+
+ def currencies_for_select
+ Money::Currency.all_instances
+ .sort_by(&:priority)
+ end
+
def radio_tab_contents(label:, icon:)
tag.div(class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-gray-400 group-has-[:checked]:bg-white group-has-[:checked]:text-gray-800 group-has-[:checked]:shadow-sm") do
concat lucide_icon(icon, class: "w-5 h-5")
diff --git a/app/helpers/styled_form_builder.rb b/app/helpers/styled_form_builder.rb
new file mode 100644
index 00000000..01bf22e0
--- /dev/null
+++ b/app/helpers/styled_form_builder.rb
@@ -0,0 +1,55 @@
+class StyledFormBuilder < ActionView::Helpers::FormBuilder
+ # Fields that visually inherit from "text field"
+ class_attribute :text_field_helpers, default: field_helpers - [ :label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field ]
+
+ # Wraps "text" inputs with custom structure + base styles
+ text_field_helpers.each do |selector|
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{selector}(method, options = {})
+ input_html = label_html(method, options) + super(method, merged_options(options))
+ input_html = apply_form_field_wrapper(input_html) unless options[:inline]
+ input_html
+ end
+ RUBY_EVAL
+ end
+
+ def radio_button(method, tag_value, options = {})
+ super(method, tag_value, merged_options(options, "form-field__radio"))
+ end
+
+ def select(method, choices, options = {}, html_options = {})
+ input_html = label_html(method, options) + super(method, choices, options, merged_options(html_options))
+ input_html = apply_form_field_wrapper(input_html, class: "pr-0") unless options[:inline]
+ input_html
+ end
+
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
+ input_html = label_html(method, options) + super(method, collection, value_method, text_method, options, merged_options(html_options))
+ input_html = apply_form_field_wrapper(input_html, class: "pr-0") unless options[:inline]
+ input_html
+ end
+
+ def submit(value = nil, options = {})
+ value, options = nil, value if value.is_a?(Hash)
+ super(value, merged_options(options, "form-field__submit"))
+ end
+
+ private
+
+ def apply_form_field_wrapper(input_html, **options)
+ @template.form_field_tag(**options) do
+ input_html
+ end
+ end
+
+ def merged_options(options, default_class = "form-field__input")
+ combined_classes = options.fetch(:class, "") + " #{default_class}"
+ style_options = { class: combined_classes }
+ non_custom_options = options.except(:class, :label, :inline)
+ style_options.merge(non_custom_options)
+ end
+
+ def label_html(method, options)
+ options[:label] ? label(method, options[:label], class: "form-field__label") : "".html_safe
+ end
+end
diff --git a/app/javascript/controllers/money_field_controller.js b/app/javascript/controllers/money_field_controller.js
index 09b6b67a..26219681 100644
--- a/app/javascript/controllers/money_field_controller.js
+++ b/app/javascript/controllers/money_field_controller.js
@@ -4,17 +4,23 @@ import { CurrenciesService } from "services/currencies_service";
// Connects to data-controller="money-field"
// when currency select change, update the input value with the correct placeholder and step
export default class extends Controller {
- static targets = ["amount", "currency"];
+ static targets = ["amount", "currency", "symbol"];
- handleCurrencyChange() {
- const selectedCurrency = event.target.value;
+ handleCurrencyChange(e) {
+ const selectedCurrency = e.target.value;
this.updateAmount(selectedCurrency);
}
updateAmount(currency) {
- (new CurrenciesService).get(currency).then((data) => {
- this.amountTarget.placeholder = data.placeholder;
- this.amountTarget.step = data.step;
+ (new CurrenciesService).get(currency).then((currency) => {
+ console.log(currency)
+ this.amountTarget.step = currency.step;
+
+ if (isFinite(this.amountTarget.value)) {
+ this.amountTarget.value = parseFloat(this.amountTarget.value).toFixed(currency.default_precision)
+ }
+
+ this.symbolTarget.innerText = currency.symbol;
});
}
}
\ No newline at end of file
diff --git a/app/javascript/controllers/select_controller.js b/app/javascript/controllers/select_controller.js
deleted file mode 100644
index 4d8bb1a8..00000000
--- a/app/javascript/controllers/select_controller.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import { Controller } from "@hotwired/stimulus";
-
-/**
- * A custom "select" element that follows accessibility patterns of a native select element.
- *
- * - If you need to display arbitrary content including non-clickable items, links, buttons, and forms, use the "popover" controller instead.
- */
-export default class extends Controller {
- static classes = ["active"];
- static targets = ["option", "button", "list", "input", "buttonText"];
- static values = { selected: String };
-
- initialize() {
- this.show = false;
-
- const selectedElement = this.optionTargets.find(
- (option) => option.dataset.value === this.selectedValue
- );
- if (selectedElement) {
- this.updateAriaAttributesAndClasses(selectedElement);
- this.syncButtonTextWithInput();
- }
- }
-
- connect() {
- this.syncButtonTextWithInput();
- if (this.hasButtonTarget) {
- this.buttonTarget.addEventListener("click", this.toggleList);
- }
- this.element.addEventListener("keydown", this.handleKeydown);
- document.addEventListener("click", this.handleOutsideClick);
- this.element.addEventListener("turbo:load", this.handleTurboLoad);
- }
-
- disconnect() {
- this.element.removeEventListener("keydown", this.handleKeydown);
- document.removeEventListener("click", this.handleOutsideClick);
- this.element.removeEventListener("turbo:load", this.handleTurboLoad);
-
- if (this.hasButtonTarget) {
- this.buttonTarget.removeEventListener("click", this.toggleList);
- }
- }
-
- selectedValueChanged() {
- this.syncButtonTextWithInput();
- }
-
- handleOutsideClick = (event) => {
- if (this.show && !this.element.contains(event.target)) {
- this.close();
- }
- };
-
- handleTurboLoad = () => {
- this.close();
- this.syncButtonTextWithInput();
- };
-
- handleKeydown = (event) => {
- switch (event.key) {
- case " ":
- case "Enter":
- event.preventDefault(); // Prevent the default action to avoid scrolling
- if (
- this.hasButtonTarget &&
- document.activeElement === this.buttonTarget
- ) {
- this.toggleList();
- } else {
- this.selectOption(event);
- }
- break;
- case "ArrowDown":
- event.preventDefault(); // Prevent the default action to avoid scrolling
- this.focusNextOption();
- break;
- case "ArrowUp":
- event.preventDefault(); // Prevent the default action to avoid scrolling
- this.focusPreviousOption();
- break;
- case "Escape":
- this.close();
- if (this.hasButtonTarget) {
- this.buttonTarget.focus(); // Bring focus back to the button
- }
- break;
- case "Tab":
- this.close();
- break;
- }
- };
-
- focusNextOption() {
- this.focusOptionInDirection(1);
- }
-
- focusPreviousOption() {
- this.focusOptionInDirection(-1);
- }
-
- focusOptionInDirection(direction) {
- const currentFocusedIndex = this.optionTargets.findIndex(
- (option) => option === document.activeElement
- );
- const optionsCount = this.optionTargets.length;
- const nextIndex =
- (currentFocusedIndex + direction + optionsCount) % optionsCount;
- this.optionTargets[nextIndex].focus();
- }
-
- toggleList = () => {
- if (!this.hasButtonTarget) return; // Ensure button target is present before toggling
-
- this.show = !this.show;
- this.listTarget.classList.toggle("hidden", !this.show);
- this.buttonTarget.setAttribute("aria-expanded", this.show.toString());
-
- if (this.show) {
- // Focus the first option or the selected option when the list is shown
- const selectedOption = this.optionTargets.find(
- (option) => option.getAttribute("aria-selected") === "true"
- );
- (selectedOption || this.optionTargets[0]).focus();
- }
- };
-
- close() {
- if (this.hasButtonTarget) {
- this.show = false;
- this.listTarget.classList.add("hidden");
- this.buttonTarget.setAttribute("aria-expanded", "false");
- }
- }
-
- selectOption(event) {
- const selectedOption =
- event.type === "keydown" ? document.activeElement : event.currentTarget;
- this.updateAriaAttributesAndClasses(selectedOption);
- if (this.inputTarget.value !== selectedOption.getAttribute("data-value")) {
- this.updateInputValueAndEmitEvent(selectedOption);
- }
- this.close(); // Close the list after selection
- }
-
- updateAriaAttributesAndClasses(selectedOption) {
- this.optionTargets.forEach((option) => {
- option.setAttribute("aria-selected", "false");
- option.setAttribute("tabindex", "-1");
- option.classList.remove(...this.activeClasses);
- });
- selectedOption.classList.add(...this.activeClasses);
- selectedOption.setAttribute("aria-selected", "true");
- selectedOption.focus();
- }
-
- updateInputValueAndEmitEvent(selectedOption) {
- // Update the hidden input's value
- const selectedValue = selectedOption.getAttribute("data-value");
- this.inputTarget.value = selectedValue;
- this.syncButtonTextWithInput();
-
- // Emit an input event for auto-submit functionality
- const inputEvent = new Event("input", {
- bubbles: true,
- cancelable: true,
- });
- this.inputTarget.dispatchEvent(inputEvent);
- }
-
- syncButtonTextWithInput() {
- const matchingOption = this.optionTargets.find(
- (option) => option.getAttribute("data-value") === this.inputTarget.value
- );
- if (matchingOption && this.hasButtonTextTarget) {
- this.buttonTextTarget.textContent = matchingOption.textContent.trim();
- }
- }
-}
diff --git a/app/views/account/entries/_selection_bar.html.erb b/app/views/account/entries/_selection_bar.html.erb
index e3003813..3f72eaab 100644
--- a/app/views/account/entries/_selection_bar.html.erb
+++ b/app/views/account/entries/_selection_bar.html.erb
@@ -9,7 +9,6 @@
<%= turbo_frame_tag "bulk_transaction_edit_drawer" %>
<%= form_with url: mark_transfers_transactions_path,
- builder: ActionView::Helpers::FormBuilder,
scope: "bulk_update",
data: {
turbo_frame: "_top",
@@ -36,7 +35,7 @@
<%= lucide_icon "pencil-line", class: "w-5 group-hover:text-white" %>
<% end %>
- <%= form_with url: bulk_delete_transactions_path, builder: ActionView::Helpers::FormBuilder, data: { turbo_confirm: true, turbo_frame: "_top" } do %>
+ <%= form_with url: bulk_delete_transactions_path, data: { turbo_confirm: true, turbo_frame: "_top" } do %>
diff --git a/app/views/account/entries/entryables/transaction/_show.html.erb b/app/views/account/entries/entryables/transaction/_show.html.erb
index 1ff9d7ba..ebf603ba 100644
--- a/app/views/account/entries/entryables/transaction/_show.html.erb
+++ b/app/views/account/entries/entryables/transaction/_show.html.erb
@@ -27,30 +27,28 @@
- <%= form_with model: [account, entry], url: account_entry_path(account, entry), html: { data: { controller: "auto-submit-form" } } do |f| %>
-
- <%= f.text_field :name, label: t(".name_label"), "data-auto-submit-form-target": "auto" %>
- <% unless entry.marked_as_transfer? %>
-
-
- <%= f.select :nature, [["Expense", "expense"], ["Income", "income"]], { label: t(".nature"), selected: entry.amount.negative? ? "income" : "expense" }, "data-auto-submit-form-target": "auto" %>
-
-
- <%= f.number_field :amount, value: entry.amount.abs, label: t(".amount"), step: "0.01", "data-auto-submit-form-target": "auto", "data-autosubmit-trigger-event": "change" %>
-
+ <%= styled_form_with model: [account, entry], url: account_entry_path(account, entry), class: "space-y-2", data: { controller: "auto-submit-form" } do |f| %>
+ <%= f.text_field :name, label: t(".name_label"), "data-auto-submit-form-target": "auto" %>
+ <% unless entry.marked_as_transfer? %>
+
+
+ <%= f.select :nature, [["Expense", "expense"], ["Income", "income"]], { label: t(".nature"), selected: entry.amount.negative? ? "income" : "expense" }, "data-auto-submit-form-target": "auto" %>
- <% end %>
- <%= f.date_field :date, label: t(".date_label"), max: Date.current, "data-auto-submit-form-target": "auto" %>
+
+ <%= f.number_field :amount, value: entry.amount.abs, label: t(".amount"), step: "0.01", "data-auto-submit-form-target": "auto", "data-autosubmit-trigger-event": "change" %>
+
+
+ <% end %>
+ <%= f.date_field :date, label: t(".date_label"), max: Date.current, "data-auto-submit-form-target": "auto" %>
- <%= f.fields_for :entryable do |ef| %>
- <% unless entry.marked_as_transfer? %>
- <%= ef.collection_select :category_id, selectable_categories, :id, :name, { prompt: t(".category_placeholder"), label: t(".category_label"), class: "text-gray-400" }, "data-auto-submit-form-target": "auto" %>
- <%= ef.collection_select :merchant_id, selectable_merchants, :id, :name, { prompt: t(".merchant_placeholder"), label: t(".merchant_label"), class: "text-gray-400" }, "data-auto-submit-form-target": "auto" %>
- <% end %>
+ <%= f.fields_for :entryable do |ef| %>
+ <% unless entry.marked_as_transfer? %>
+ <%= ef.collection_select :category_id, Current.family.categories.alphabetically, :id, :name, { prompt: t(".category_placeholder"), label: t(".category_label"), class: "text-gray-400" }, "data-auto-submit-form-target": "auto" %>
+ <%= ef.collection_select :merchant_id, Current.family.merchants.alphabetically, :id, :name, { prompt: t(".merchant_placeholder"), label: t(".merchant_label"), class: "text-gray-400" }, "data-auto-submit-form-target": "auto" %>
<% end %>
+ <% end %>
- <%= f.collection_select :account_id, selectable_accounts, :id, :name, { prompt: t(".account_placeholder"), label: t(".account_label"), class: "text-gray-500" }, { class: "form-field__input cursor-not-allowed text-gray-400", disabled: "disabled" } %>
-
+ <%= f.collection_select :account_id, Current.family.accounts.alphabetically, :id, :name, { prompt: t(".account_placeholder"), label: t(".account_label"), class: "text-gray-500" }, { class: "form-field__input cursor-not-allowed text-gray-400", disabled: "disabled" } %>
<% end %>
@@ -61,12 +59,12 @@
<%= lucide_icon "chevron-down", class: "group-open:transform group-open:rotate-180 text-gray-500 w-5" %>
-
- <%= form_with model: [account, entry], url: account_entry_path(account, entry), html: { data: { controller: "auto-submit-form" } } do |f| %>
+
+ <%= styled_form_with model: [account, entry], url: account_entry_path(account, entry), class: "space-y-2", data: { controller: "auto-submit-form" } do |f| %>
<%= f.fields_for :entryable do |ef| %>
<%= ef.select :tag_ids,
- options_for_select(selectable_tags, transaction.tag_ids),
+ options_for_select(Current.family.tags.alphabetically.pluck(:name, :id), transaction.tag_ids),
{
multiple: true,
label: t(".tags_label"),
@@ -86,7 +84,7 @@
- <%= form_with model: [account, entry], url: account_entry_path(account, entry), html: { class: "p-3 space-y-3", data: { controller: "auto-submit-form" } } do |f| %>
+ <%= styled_form_with model: [account, entry], url: account_entry_path(account, entry), class: "p-3 space-y-3", data: { controller: "auto-submit-form" } do |f| %>
<%= f.fields_for :entryable do |ef| %>
diff --git a/app/views/account/entries/entryables/transaction/_transaction.html.erb b/app/views/account/entries/entryables/transaction/_transaction.html.erb
index 0c87eac2..851af438 100644
--- a/app/views/account/entries/entryables/transaction/_transaction.html.erb
+++ b/app/views/account/entries/entryables/transaction/_transaction.html.erb
@@ -31,7 +31,7 @@
<% if unconfirmed_transfer?(entry) %>
<% if editable %>
- <%= form_with url: unmark_transfers_transactions_path, builder: ActionView::Helpers::FormBuilder, class: "flex items-center", data: {
+ <%= form_with url: unmark_transfers_transactions_path, class: "flex items-center", data: {
turbo_confirm: {
title: t(".remove_transfer"),
body: t(".remove_transfer_body"),
diff --git a/app/views/account/entries/entryables/valuation/_form.html.erb b/app/views/account/entries/entryables/valuation/_form.html.erb
index 0123e61d..c7b3abff 100644
--- a/app/views/account/entries/entryables/valuation/_form.html.erb
+++ b/app/views/account/entries/entryables/valuation/_form.html.erb
@@ -1,8 +1,7 @@
<%# locals: (entry:) %>
<%= form_with model: [entry.account, entry],
data: { turbo_frame: "_top" },
- url: entry.new_record? ? account_entries_path(entry.account) : account_entry_path(entry.account, entry),
- builder: ActionView::Helpers::FormBuilder do |f| %>
+ url: entry.new_record? ? account_entries_path(entry.account) : account_entry_path(entry.account, entry) do |f| %>
diff --git a/app/views/account/transfers/_form.html.erb b/app/views/account/transfers/_form.html.erb
index ddd89c79..2f58b08e 100644
--- a/app/views/account/transfers/_form.html.erb
+++ b/app/views/account/transfers/_form.html.erb
@@ -1,4 +1,4 @@
-<%= form_with model: transfer, data: { turbo_frame: "_top" } do |f| %>
+<%= styled_form_with model: transfer, class: "space-y-4", data: { turbo_frame: "_top" } do |f| %>
diff --git a/app/views/accounts/_account.html.erb b/app/views/accounts/_account.html.erb
index 715570c7..8a343360 100644
--- a/app/views/accounts/_account.html.erb
+++ b/app/views/accounts/_account.html.erb
@@ -17,7 +17,6 @@
<%= form_with model: account,
namespace: account.id,
- builder: ActionView::Helpers::FormBuilder,
data: { controller: "auto-submit-form", turbo_frame: "_top" } do |form| %>
<%= form.check_box :is_active, { class: "sr-only peer", data: { "auto-submit-form-target": "auto" } } %>
diff --git a/app/views/accounts/edit.html.erb b/app/views/accounts/edit.html.erb
index 3660aa3f..0b14157e 100644
--- a/app/views/accounts/edit.html.erb
+++ b/app/views/accounts/edit.html.erb
@@ -5,9 +5,9 @@
<%= lucide_icon "x", class: "w-5 h-5 text-gray-500", data: { action: "click->modal#close" } %>
- <%= form_with model: @account, data: { turbo_frame: "_top" } do |f| %>
+ <%= styled_form_with model: @account, class: "space-y-4", data: { turbo_frame: "_top" } do |f| %>
<%= f.text_field :name, label: t(".name") %>
- <%= f.money_field :balance_money, label: t(".balance"), readonly_currency: true %>
+ <%= money_with_currency_field f, :balance_money, label: t(".balance"), disable_currency: true %>
<%= f.collection_select :institution_id, Current.family.institutions.alphabetically, :id, :name, { include_blank: t(".ungrouped"), label: t(".institution") } %>
diff --git a/app/views/accounts/list.html.erb b/app/views/accounts/list.html.erb
index fe2a7d33..6075fd14 100644
--- a/app/views/accounts/list.html.erb
+++ b/app/views/accounts/list.html.erb
@@ -1,5 +1,5 @@
-
+<%= turbo_frame_tag "account-list" do %>
<% account_groups(period: @period).each do |group| %>
<%= render "accounts/account_list", group: group %>
<% end %>
-
+<% end %>
diff --git a/app/views/accounts/new.html.erb b/app/views/accounts/new.html.erb
index 1e42a5d6..04590414 100644
--- a/app/views/accounts/new.html.erb
+++ b/app/views/accounts/new.html.erb
@@ -73,13 +73,13 @@
<% end %>
Add <%= @account.accountable.model_name.human.downcase %>
- <%= form_with model: @account, url: accounts_path, scope: :account, html: { class: "m-5 mt-1 flex flex-col justify-between grow", data: { turbo: false } } do |f| %>
+ <%= styled_form_with model: @account, url: accounts_path, scope: :account, class: "m-5 mt-1 flex flex-col justify-between grow", data: { turbo: false } do |f| %>
<%= f.hidden_field :accountable_type %>
<%= f.text_field :name, placeholder: t(".name.placeholder"), required: "required", label: t(".name.label"), autofocus: true %>
<%= f.collection_select :institution_id, Current.family.institutions.alphabetically, :id, :name, { include_blank: t(".ungrouped"), label: t(".institution") } %>
<%= render "accounts/accountables/#{permitted_accountable_partial(@account.accountable_type)}", f: f %>
- <%= f.money_field :balance_money, label: t(".balance"), required: "required" %>
+ <%= money_with_currency_field f, :balance_money, label: t(".balance"), required: "required" %>
<%= check_box_tag :add_start_values, class: "maybe-checkbox maybe-checkbox--light peer mb-1" %>
diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb
index e1712d53..de2a57e1 100644
--- a/app/views/accounts/show.html.erb
+++ b/app/views/accounts/show.html.erb
@@ -69,8 +69,8 @@
<%= tag.span period_label(@period), class: "text-gray-500" %>
- <%= form_with url: account_path(@account), method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %>
- <%= render partial: "shared/period_select", locals: { value: @period.name } %>
+ <%= form_with url: account_path(@account), method: :get, data: { controller: "auto-submit-form" } do |form| %>
+ <%= period_select form: form, selected: @period.name %>
<% end %>
diff --git a/app/views/accounts/summary.html.erb b/app/views/accounts/summary.html.erb
index e8603f8d..4dc83eaf 100644
--- a/app/views/accounts/summary.html.erb
+++ b/app/views/accounts/summary.html.erb
@@ -10,10 +10,10 @@
<%= render partial: "shared/value_heading", locals: {
label: "Assets",
- period: @period,
- value: Current.family.assets,
- trend: @asset_series.trend
- } %>
+ period: @period,
+ value: Current.family.assets,
+ trend: @asset_series.trend
+ } %>
<%= render partial: "shared/value_heading", locals: {
label: "Liabilities",
- period: @period,
- size: "md",
- value: Current.family.liabilities,
- trend: @liability_series.trend
- } %>
+ period: @period,
+ size: "md",
+ value: Current.family.liabilities,
+ trend: @liability_series.trend
+ } %>
<%= t(".new") %>
<% end %>
- <%= form_with url: summary_accounts_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %>
- <%= render partial: "shared/period_select", locals: { value: @period.name } %>
+ <%= form_with url: summary_accounts_path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
+ <%= period_select form: form, selected: @period.name %>
<% end %>
@@ -64,8 +64,8 @@
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-500") %>
<%= t(".new") %>
<% end %>
- <%= form_with url: summary_accounts_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %>
- <%= render partial: "shared/period_select", locals: { value: @period.name } %>
+ <%= form_with url: summary_accounts_path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
+ <%= period_select form: form, selected: @period.name %>
<% end %>
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
index b5fba349..0321b2bd 100644
--- a/app/views/categories/_form.html.erb
+++ b/app/views/categories/_form.html.erb
@@ -1,4 +1,4 @@
-<%= form_with model: category, data: { turbo: false } do |form| %>
+<%= styled_form_with model: category, data: { turbo: false } do |form| %>
- <%= form_with url: category_deletions_path(@category),
- data: {
- turbo: false,
- controller: "deletion",
- deletion_dangerous_action_class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
- deletion_safe_action_class: "form-field__submit border border-transparent",
- deletion_submit_text_when_not_replacing_value: t(".delete_and_leave_uncategorized", category_name: @category.name),
- deletion_submit_text_when_replacing_value: t(".delete_and_recategorize", category_name: @category.name) } do |f| %>
+ <%= styled_form_with url: category_deletions_path(@category),
+ class: "space-y-4",
+ data: {
+ turbo: false,
+ controller: "deletion",
+ deletion_dangerous_action_class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
+ deletion_safe_action_class: "form-field__submit border border-transparent",
+ deletion_submit_text_when_not_replacing_value: t(".delete_and_leave_uncategorized", category_name: @category.name),
+ deletion_submit_text_when_replacing_value: t(".delete_and_recategorize", category_name: @category.name) } do |f| %>
<%= f.collection_select :replacement_category_id,
Current.family.categories.alphabetically.without(@category),
- :id, :name,
- { prompt: t(".replacement_category_prompt"), label: t(".category") },
+ :id, :name,
+ { prompt: t(".replacement_category_prompt"), label: t(".category") },
{ data: { deletion_target: "replacementField", action: "deletion#updateSubmitButton" } } %>
<%= f.submit t(".delete_and_leave_uncategorized", category_name: @category.name),
- class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
+ class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
data: { deletion_target: "submitButton" } %>
<% end %>
diff --git a/app/views/imports/_csv_paste.html.erb b/app/views/imports/_csv_paste.html.erb
index 6006e874..59257173 100644
--- a/app/views/imports/_csv_paste.html.erb
+++ b/app/views/imports/_csv_paste.html.erb
@@ -1,11 +1,9 @@
-<%= form_with model: @import, url: load_import_path(@import) do |form| %>
-
+<%= styled_form_with model: @import, url: load_import_path(@import), class: "space-y-4" do |form| %>
<%= form.text_area :raw_csv_str,
rows: 10,
required: true,
placeholder: "Paste your CSV file contents here",
- class: "rounded-md w-full border text-sm border-alpha-black-100 bg-white placeholder:text-gray-400 p-4" %>
-
+ class: "rounded-md w-full border text-sm border-alpha-black-100 bg-white placeholder:text-gray-400" %>
<%= form.submit t(".next"), class: "px-4 py-2 mb-4 block w-full rounded-lg bg-gray-900 text-white text-sm font-medium", data: { turbo_confirm: (@import.raw_csv_str? ? { title: t(".confirm_title"), body: t(".confirm_body"), accept: t(".confirm_accept") } : nil) } %>
<% end %>
diff --git a/app/views/imports/_csv_upload.html.erb b/app/views/imports/_csv_upload.html.erb
index 69d598ef..ec23db31 100644
--- a/app/views/imports/_csv_upload.html.erb
+++ b/app/views/imports/_csv_upload.html.erb
@@ -1,4 +1,4 @@
-<%= form_with model: @import, url: upload_import_path(@import), class: "dropzone", data: { controller: "csv-upload" }, method: :patch, multipart: true do |form| %>
+<%= styled_form_with model: @import, url: upload_import_path(@import), class: "dropzone space-y-4", data: { controller: "csv-upload" }, method: :patch, multipart: true do |form| %>
diff --git a/app/views/merchants/_form.html.erb b/app/views/merchants/_form.html.erb
index aebeef4e..9e78369c 100644
--- a/app/views/merchants/_form.html.erb
+++ b/app/views/merchants/_form.html.erb
@@ -1,6 +1,6 @@
<% is_editing = @merchant.id.present? %>
- <%= form_with model: @merchant, url: is_editing ? merchant_path(@merchant) : merchants_path, method: is_editing ? :patch : :post, scope: :merchant, data: { turbo: false } do |f| %>
+ <%= styled_form_with model: @merchant, url: is_editing ? merchant_path(@merchant) : merchants_path, method: is_editing ? :patch : :post, scope: :merchant, class: "space-y-4", data: { turbo: false } do |f| %>
<%= render partial: "merchants/avatar", locals: { merchant: } %>
diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb
index 66b24c9d..27bf0c57 100644
--- a/app/views/pages/dashboard.html.erb
+++ b/app/views/pages/dashboard.html.erb
@@ -26,8 +26,8 @@
trend: @net_worth_series.trend
} %>
- <%= form_with url: root_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do %>
- <%= render partial: "shared/period_select", locals: { value: @period.name } %>
+ <%= form_with url: root_path, method: :get, class: "flex items-center gap-4", data: { controller: "auto-submit-form" } do |form| %>
+ <%= period_select form: form, selected: @period.name %>
<% end %>
<%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @net_worth_series } %>
diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb
index 1a4b6260..42c87ffe 100644
--- a/app/views/password_resets/edit.html.erb
+++ b/app/views/password_resets/edit.html.erb
@@ -2,7 +2,7 @@
header_title t(".title")
%>
-<%= form_with model: @user, url: password_reset_path(token: params[:token]), method: :patch, html: {class: "space-y-6"} do |form| %>
+<%= styled_form_with model: @user, url: password_reset_path(token: params[:token]), method: :patch, class: "space-y-4" do |form| %>
<%= auth_messages form %>
diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb
index 2974c80e..f2b455a0 100644
--- a/app/views/password_resets/new.html.erb
+++ b/app/views/password_resets/new.html.erb
@@ -2,7 +2,7 @@
header_title t(".title")
%>
-<%= form_with url: password_reset_path do |form| %>
+<%= styled_form_with url: password_reset_path, class: "space-y-4" do |form| %>
<%= auth_messages form %>
<%= form.email_field :email, label: true, autofocus: false, autocomplete: "email", required: "required", placeholder: "you@example.com" %>
diff --git a/app/views/passwords/edit.html.erb b/app/views/passwords/edit.html.erb
index 0bbfa827..a89cabde 100644
--- a/app/views/passwords/edit.html.erb
+++ b/app/views/passwords/edit.html.erb
@@ -1,6 +1,6 @@
<% t(".title") %>
-<%= form_with model: Current.user, url: password_path do |form| %>
+<%= styled_form_with model: Current.user, url: password_path, class: "space-y-4" do |form| %>
<%= auth_messages form %>
diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb
index bfd7289f..a5714243 100644
--- a/app/views/registrations/new.html.erb
+++ b/app/views/registrations/new.html.erb
@@ -1,7 +1,7 @@
<%
header_title t(".title")
%>
-<%= form_with model: @user, url: registration_path do |form| %>
+<%= styled_form_with model: @user, url: registration_path, class: "space-y-4" do |form| %>
<%= auth_messages form %>
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: "required", placeholder: "you@example.com", label: true %>
<%= form.password_field :password, autocomplete: "new-password", required: "required", label: true %>
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
index da0a3264..67a9d7ed 100644
--- a/app/views/sessions/new.html.erb
+++ b/app/views/sessions/new.html.erb
@@ -2,12 +2,12 @@
header_title t(".title")
%>
-<%= form_with url: session_path do |form| %>
+<%= styled_form_with url: session_path, class: "space-y-4" do |form| %>
<%= auth_messages form %>
<%= form.email_field :email, label: t(".email"), autofocus: false, autocomplete: "email", required: "required", placeholder: t(".email_placeholder") %>
- <%= form.password_field :password, label: true, required: "required" %>
+ <%= form.password_field :password, label: t(".password"), required: "required" %>
<%= form.submit t(".submit") %>
<% end %>
diff --git a/app/views/settings/hostings/show.html.erb b/app/views/settings/hostings/show.html.erb
index 37c1f3f6..4b9dff76 100644
--- a/app/views/settings/hostings/show.html.erb
+++ b/app/views/settings/hostings/show.html.erb
@@ -4,7 +4,7 @@
<%= t(".page_title") %>
<%= settings_section title: t(".general_settings_title") do %>
- <%= form_with model: Setting.new, url: settings_hosting_path, method: :patch, local: true, html: { class: "space-y-6", data: { controller: "auto-submit-form", "auto-submit-form-trigger-event-value" => "blur" } } do |form| %>
+ <%= styled_form_with model: Setting.new, url: settings_hosting_path, method: :patch, local: true, class: "space-y-6", data: { controller: "auto-submit-form", "auto-submit-form-trigger-event-value" => "blur" } do |form| %>
<% if ENV["HOSTING_PLATFORM"] == "render" %>
diff --git a/app/views/settings/preferences/show.html.erb b/app/views/settings/preferences/show.html.erb
index 734190e7..0a6609ca 100644
--- a/app/views/settings/preferences/show.html.erb
+++ b/app/views/settings/preferences/show.html.erb
@@ -5,16 +5,16 @@
<%= t(".page_title") %>
<%= settings_section title: t(".general_title"), subtitle: t(".general_subtitle") do %>
- <%= form_with model: Current.user, url: settings_preferences_path, html: { class: "space-y-4", data: { controller: "auto-submit-form" } } do |form| %>
- <%= form.fields_for :family_attributes do |family_fields| %>
- <%= family_fields.currency_select :currency, { selected: Current.family.currency, label: "Currency" }, { data: { auto_submit_form_target: "auto" } } %>
+ <%= styled_form_with model: Current.user, url: settings_preferences_path, class: "space-y-4", data: { controller: "auto-submit-form" } do |form| %>
+ <%= form.fields_for :family_attributes do |family_form| %>
+ <%= currency_select_full family_form, :currency, { label: "Currency", selected: Current.family.currency }, { data: { auto_submit_form_target: "auto" } } %>
<% end %>
<% end %>
<% end %>
<%= settings_section title: t(".theme_title"), subtitle: t(".theme_subtitle") do %>
- <%= form_with model: Current.user, url: settings_preferences_path, local: true, html: { class: "flex justify-between items-center" } do |form| %>
+ <%= styled_form_with model: Current.user, url: settings_preferences_path, local: true, class: "flex justify-between items-center" do |form| %>
<%= image_tag("light-mode-preview.png", alt: "Light Theme Preview", class: "h-44 mb-4") %>
diff --git a/app/views/settings/profiles/show.html.erb b/app/views/settings/profiles/show.html.erb
index e829761b..edcab295 100644
--- a/app/views/settings/profiles/show.html.erb
+++ b/app/views/settings/profiles/show.html.erb
@@ -5,7 +5,7 @@
<%= t(".page_title") %>
<%= settings_section title: t(".profile_title"), subtitle: t(".profile_subtitle") do %>
- <%= form_with model: Current.user, url: settings_profile_path, html: {data: { controller: "profile-image-preview" }} do |form| %>
+ <%= styled_form_with model: Current.user, url: settings_profile_path, class: "space-y-4", data: { controller: "profile-image-preview" } do |form| %>
@@ -43,7 +43,7 @@
<% end %>
<%= settings_section title: t(".household_title"), subtitle: t(".household_subtitle") do %>
- <%= form_with model: Current.user, url: settings_profile_path, html: { class: "space-y-4", data: { controller: "auto-submit-form", "auto-submit-form-trigger-event-value": "blur" } } do |form| %>
+ <%= styled_form_with model: Current.user, url: settings_profile_path, class: "space-y-4", data: { controller: "auto-submit-form", "auto-submit-form-trigger-event-value": "blur" } do |form| %>
<%= form.fields_for :family_attributes do |family_fields| %>
<%= family_fields.text_field :name, placeholder: t(".household_form_input_placeholder"), value: Current.family.name, label: t(".household_form_label"), disabled: !Current.user.admin?, "data-auto-submit-form-target": "auto" %>
<% end %>
diff --git a/app/views/shared/_money_field.html.erb b/app/views/shared/_money_field.html.erb
new file mode 100644
index 00000000..7b3288b0
--- /dev/null
+++ b/app/views/shared/_money_field.html.erb
@@ -0,0 +1,24 @@
+<%# locals: (form:, money_method:, default_currency: "USD", disable_currency: false, hide_currency: false, label: nil) %>
+<% fallback_label = t(".money-label") %>
+
diff --git a/app/views/shared/_period_select.html.erb b/app/views/shared/_period_select.html.erb
deleted file mode 100644
index 8b935a72..00000000
--- a/app/views/shared/_period_select.html.erb
+++ /dev/null
@@ -1,22 +0,0 @@
-<%# locals: (value: 'last_30_days', button_class: '') -%>
-<% options = [["7D", "last_7_days"], ["1M", "last_30_days"], ["1Y", "last_365_days"], ["All", "all"]] %>
-
- <%=
- tag.button(
- type: "button",
- data: { "select-target": "button" },
- class: button_class.presence || "flex items-center gap-1 w-full border border-alpha-black-100 shadow-xs rounded-lg text-sm p-2 cursor-pointer text-gray-900 text-sm"
- ) do
- %>
-
<%= options.find { |option| option[1] == value }[0] %>
- <%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %>
- <% end %>
-
-
- <% options.each do |label, value| %>
- -
- <%= label %>
-
- <% end %>
-
-
diff --git a/app/views/tag/deletions/new.html.erb b/app/views/tag/deletions/new.html.erb
index 240df1f2..48c3562d 100644
--- a/app/views/tag/deletions/new.html.erb
+++ b/app/views/tag/deletions/new.html.erb
@@ -11,14 +11,15 @@
- <%= form_with url: tag_deletions_path(@tag),
- data: {
- turbo: false,
- controller: "deletion",
- deletion_dangerous_action_class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
- deletion_safe_action_class: "form-field__submit border border-transparent",
- deletion_submit_text_when_not_replacing_value: t(".delete_and_leave_uncategorized", tag_name: @tag.name),
- deletion_submit_text_when_replacing_value: t(".delete_and_recategorize", tag_name: @tag.name) } do |f| %>
+ <%= styled_form_with url: tag_deletions_path(@tag),
+ class: "space-y-4",
+ data: {
+ turbo: false,
+ controller: "deletion",
+ deletion_dangerous_action_class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
+ deletion_safe_action_class: "form-field__submit border border-transparent",
+ deletion_submit_text_when_not_replacing_value: t(".delete_and_leave_uncategorized", tag_name: @tag.name),
+ deletion_submit_text_when_replacing_value: t(".delete_and_recategorize", tag_name: @tag.name) } do |f| %>
<%= f.collection_select :replacement_tag_id,
Current.family.tags.alphabetically.without(@tag),
:id, :name,
diff --git a/app/views/tags/_form.html.erb b/app/views/tags/_form.html.erb
index 4965b305..4ee675d5 100644
--- a/app/views/tags/_form.html.erb
+++ b/app/views/tags/_form.html.erb
@@ -1,4 +1,4 @@
-<%= form_with model: tag, data: { turbo: false } do |form| %>
+<%= styled_form_with model: tag, data: { turbo: false } do |form| %>
<%= form_with url: transactions_path,
- builder: ActionView::Helpers::FormBuilder,
method: :get,
class: "flex items-center gap-4",
data: { controller: "auto-submit-form" } do |f| %>
diff --git a/app/views/transactions/bulk_edit.html.erb b/app/views/transactions/bulk_edit.html.erb
index 07c21e1e..dc4ec2a1 100644
--- a/app/views/transactions/bulk_edit.html.erb
+++ b/app/views/transactions/bulk_edit.html.erb
@@ -2,7 +2,7 @@