diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index f12188eb..7d28ea69 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -2,34 +2,44 @@ @tailwind components; @tailwind utilities; -.prose table { - @apply divide-y divide-gray-300; -} +@layer components { + .prose { + table { + @apply divide-y divide-gray-300; + } -.prose tr { - @apply divide-x divide-gray-100; -} + tr { + @apply divide-x divide-gray-100; + } -.prose th { - @apply whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900; -} + th { + @apply whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900; + } -.prose tbody { - @apply divide-y divide-gray-200; -} + tbody { + @apply divide-y divide-gray-200; + } -.prose td { - @apply px-2 py-2 text-sm text-gray-500 whitespace-nowrap; -} + td { + @apply px-2 py-2 text-sm text-gray-500 whitespace-nowrap; + } + } -.input-wrapper { - @apply relative p-4 bg-gray-100 border border-gray-200 rounded-2xl focus-within:bg-white focus-within:drop-shadow-form focus-within:opacity-100; -} + .form-field { + @apply relative border bg-white rounded-xl shadow-sm; + @apply focus-within:shadow-none focus-within:border-gray-900 focus-within:ring-4 focus-within:ring-gray-100; + } -.input-label { - @apply block text-sm font-medium text-gray-500; -} + .form-field__label { + @apply p-3 pb-0 block text-sm font-medium opacity-50; + } -.input-field { - @apply p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100; + .form-field__input { + @apply p-3 pt-1 w-full bg-transparent border-none opacity-50; + @apply focus:outline-none focus:ring-0 focus:opacity-100; + } + + .form-field__submit { + @apply w-full p-3 text-center text-white bg-black rounded-lg hover:bg-gray-700; + } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4d0fae2e..9d2e1869 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,8 @@ class ApplicationController < ActionController::Base include Authentication + 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 diff --git a/app/helpers/application_form_builder.rb b/app/helpers/application_form_builder.rb new file mode 100644 index 00000000..1fad76b3 --- /dev/null +++ b/app/helpers/application_form_builder.rb @@ -0,0 +1,58 @@ +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 + + 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 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 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 new file mode 100644 index 00000000..ac138976 --- /dev/null +++ b/app/helpers/forms_helper.rb @@ -0,0 +1,5 @@ +module FormsHelper + def form_field_tag(&) + tag.div class: "form-field", & + end +end diff --git a/app/views/accounts/account/_depository.html.erb b/app/views/accounts/account/_depository.html.erb index a6f1670c..d524cc2d 100644 --- a/app/views/accounts/account/_depository.html.erb +++ b/app/views/accounts/account/_depository.html.erb @@ -1,4 +1 @@ -
- - <%= f.select :subtype, options_for_select([["Checking", "checking"], ["Savings", "savings"]], selected: ""), {}, class: "block w-full p-0 mt-1 bg-transparent border-none focus:outline-none focus:ring-0" %> -
+<%= f.select :subtype, options_for_select([["Checking", "checking"], ["Savings", "savings"]], selected: ""), { label: "Type" } %> diff --git a/app/views/accounts/account/_investment.html.erb b/app/views/accounts/account/_investment.html.erb index 41b46a78..d770f824 100644 --- a/app/views/accounts/account/_investment.html.erb +++ b/app/views/accounts/account/_investment.html.erb @@ -1,4 +1 @@ -
- - <%= f.select :subtype, options_for_select(Account::Investment::SUBTYPES, selected: ""), {}, class: "block w-full p-0 mt-1 bg-transparent border-none focus:outline-none focus:ring-0" %> -
+<%= f.select :subtype, options_for_select(Account::Investment::SUBTYPES, selected: ""), { label: true } %> diff --git a/app/views/accounts/new.html.erb b/app/views/accounts/new.html.erb index b30c96f3..1cbac1ea 100644 --- a/app/views/accounts/new.html.erb +++ b/app/views/accounts/new.html.erb @@ -70,25 +70,13 @@ <%= form_with model: @account, url: accounts_path, scope: :account, html: { class: "space-y-4 m-5 mt-1", data: { turbo: false } } do |f| %> <%= f.hidden_field :accountable_type %> -
- <%= f.label :name, 'Account name', class: 'block text-sm font-medium opacity-50 focus-within:opacity-100' %> - <%= f.text_field :name, placeholder: 'Example account name', required: 'required', class: "p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100" %> -
+ <%= f.text_field :name, placeholder: 'Example account name', required: 'required', label: 'Account name' %> <%= render "accounts/#{permitted_accountable_partial(@account.accountable_type)}", f: f %> -
- <%= f.label :balance, class: 'block text-sm font-medium opacity-50 focus-within:opacity-100' %> -
- <%= f.number_field :balance, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', class: "p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full" %> -
-
+ <%= f.number_field :balance, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', label: true %> -
- -
+ <%= f.submit "Add #{@account.accountable.model_name.human.downcase}" %> <% end %> <% end %> <% end %> diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb index 21140123..7ebf336b 100644 --- a/app/views/password_resets/new.html.erb +++ b/app/views/password_resets/new.html.erb @@ -2,15 +2,10 @@ header_title t('.title') %> -<%= form_with url: password_reset_path, html: {class: 'space-y-6'} do |form| %> +<%= form_with url: password_reset_path do |form| %> <%= auth_messages form %> -
- <%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> - <%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> -
+ <%= form.email_field :email, label: true, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com' %> -
- <%= form.submit t('.submit'), class: 'flex justify-center w-full px-4 py-3 text-sm font-medium text-white bg-black rounded-xl hover:bg-black focus:outline-none focus:ring-2 focus:ring-gray-200 shadow' %> -
+ <%= form.submit t('.submit') %> <% end %> diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb index 534b44ca..da0a8d32 100644 --- a/app/views/registrations/new.html.erb +++ b/app/views/registrations/new.html.erb @@ -2,32 +2,18 @@ header_title t('.title') %> -<%= form_with model: @user, url: registration_path, html: {class: 'space-y-6'} do |form| %> +<%= form_with model: @user, url: registration_path do |form| %> <%= auth_messages form %> -
- <%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> - <%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> -
+ <%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', label: true %> -
- <%= form.label :password, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> - <%= form.password_field :password, autocomplete: "new-password", required: 'required', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> -
+ <%= form.password_field :password, autocomplete: "new-password", required: 'required', label: true %> -
- <%= form.label :password_confirmation, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> - <%= form.password_field :password_confirmation, autocomplete: "new-password", required: 'required', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> -
+ <%= form.password_field :password_confirmation, autocomplete: "new-password", required: 'required', label: true %> <% if hosted_app? %> -
- <%= form.label :invite_code, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> - <%= form.password_field :invite_code, required: 'required', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> -
+ <%= form.password_field :invite_code, required: 'required', label: true %> <% end %> -
- <%= form.submit class: 'cursor-pointer flex justify-center w-full px-4 py-3 text-sm font-medium text-white bg-black rounded-xl hover:bg-black focus:outline-none focus:ring-2 focus:ring-gray-200 shadow' %> -
+ <%= form.submit %> <% end %> diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 4c66dd16..91fabf19 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -2,22 +2,14 @@ header_title t('.title') %> -<%= form_with url: session_path, html: {class: 'space-y-6'} do |form| %> +<%= form_with url: session_path do |form| %> <%= auth_messages form %> -
- <%= form.label :email, t('.email'), class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> - <%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: t('.email_placeholder'), class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> -
+ <%= form.email_field :email, label: t('.email'), autofocus: false, autocomplete: "email", required: 'required', placeholder: t('.email_placeholder') %> -
- <%= form.label :password, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> - <%= form.password_field :password, required: 'required', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> -
+ <%= form.password_field :password, label: true, required: 'required' %> -
- <%= form.submit t('.submit'), class: 'cursor-pointer flex justify-center w-full px-4 py-3 text-sm font-medium text-white bg-black rounded-xl hover:bg-black focus:outline-none focus:ring-2 focus:ring-gray-200 shadow' %> -
+ <%= form.submit t('.submit') %> <% end %>
diff --git a/app/views/settings/edit.html.erb b/app/views/settings/edit.html.erb index 14a5e9d4..dd485ba0 100644 --- a/app/views/settings/edit.html.erb +++ b/app/views/settings/edit.html.erb @@ -2,39 +2,20 @@ <%= form_with model: Current.user, url: settings_path, html: { class: "space-y-4" } do |form| %> <%= form.fields_for :family_attributes do |family_fields| %> -
- <%= family_fields.label :name, "Family name", class: "block text-sm font-medium opacity-75 focus-within:opacity-100" %> - <%= family_fields.text_field :name, placeholder: "Family name", value: Current.family.name, class: "p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100" %> -
+ <%= family_fields.text_field :name, placeholder: "Family name", value: Current.family.name, label: "Family name" %> <% end %> + <%= form.text_field :first_name, placeholder: "First name", value: Current.user.first_name, label: true %> -
- <%= form.label :first_name, class: "block text-sm font-medium opacity-75 focus-within:opacity-100" %> - <%= form.text_field :first_name, placeholder: "First name", value: Current.user.first_name, class: "w-full p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100" %> -
+ <%= form.text_field :last_name, placeholder: "Last name", value: Current.user.last_name, label: true %> + + <%= form.email_field :email, placeholder: "Email", value: Current.user.email, label: true %> -
- <%= form.label :last_name, class: "block text-sm font-medium opacity-75 focus-within:opacity-100" %> - <%= form.text_field :last_name, placeholder: "Last name", value: Current.user.last_name, class: "w-full p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100" %> -
+ <%= form.password_field :password, label: true %> -
- <%= form.label :email, class: "block text-sm font-medium opacity-75 focus-within:opacity-100" %> - <%= form.email_field :email, placeholder: "Email", value: Current.user.email, class: "w-full p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100" %> -
+ <%= form.password_field :password_confirmation, label: true %> -
- <%= form.label :password, class: "block text-sm font-medium opacity-75 focus-within:opacity-100" %> - <%= form.password_field :password, class: "w-full p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100" %> -
- -
- <%= form.label :password_confirmation, class: "block text-sm font-medium opacity-75 focus-within:opacity-100" %> - <%= form.password_field :password_confirmation, class: "w-full p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100" %> -
- -
+