1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-19 13:19:39 +02:00

Dashboard design fixes (#1898)
Some checks are pending
Publish Docker image / ci (push) Waiting to run
Publish Docker image / Build docker image (push) Blocked by required conditions

* Dashboard design fixes

* Update dashboard greeting

* Remove sidebar toggle from settings breadcrumbs

* Autofocus and outlines for category dropdowns

* Lint fixes
This commit is contained in:
Zach Gollwitzer 2025-02-25 17:28:40 -05:00 committed by GitHub
parent a4874815a6
commit c610b0ba4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 95 additions and 74 deletions

View file

@ -17,7 +17,7 @@ module FormsHelper
end end
end end
def period_select(form:, selected:, classes: "border border-tertiary shadow-xs rounded-lg text-sm pr-7 cursor-pointer text-primary focus:outline-hidden focus:ring-0") def period_select(form:, selected:, classes: "border border-secondary rounded-lg text-sm pr-7 cursor-pointer text-primary focus:outline-hidden focus:ring-0")
periods_for_select = Period.all.map { |period| [ period.label_short, period.key ] } periods_for_select = Period.all.map { |period| [ period.label_short, period.key ] }
form.select(:period, periods_for_select, { selected: selected.key }, class: classes, data: { "auto-submit-form-target": "auto" }) form.select(:period, periods_for_select, { selected: selected.key }, class: classes, data: { "auto-submit-form-target": "auto" })

View file

@ -4,6 +4,10 @@ import { Controller } from "@hotwired/stimulus";
export default class extends Controller { export default class extends Controller {
static targets = ["input", "list", "emptyMessage"]; static targets = ["input", "list", "emptyMessage"];
connect() {
this.inputTarget.focus();
}
filter() { filter() {
const filterValue = this.inputTarget.value.toLowerCase(); const filterValue = this.inputTarget.value.toLowerCase();
const items = this.listTarget.querySelectorAll(".filterable-item"); const items = this.listTarget.querySelectorAll(".filterable-item");

View file

@ -8,7 +8,7 @@ export default class extends Controller {
toggle() { toggle() {
this.panelTarget.classList.toggle("w-0"); this.panelTarget.classList.toggle("w-0");
this.panelTarget.classList.toggle("opacity-0"); this.panelTarget.classList.toggle("opacity-0");
this.panelTarget.classList.toggle("w-[260px]"); this.panelTarget.classList.toggle("w-80");
this.panelTarget.classList.toggle("opacity-100"); this.panelTarget.classList.toggle("opacity-100");
this.contentTarget.classList.toggle("max-w-4xl"); this.contentTarget.classList.toggle("max-w-4xl");
this.contentTarget.classList.toggle("max-w-5xl"); this.contentTarget.classList.toggle("max-w-5xl");

View file

@ -446,7 +446,7 @@ export default class extends Controller {
get _margin() { get _margin() {
if (this.useLabelsValue) { if (this.useLabelsValue) {
return { top: 20, right: 0, bottom: 30, left: 0 }; return { top: 20, right: 0, bottom: 10, left: 0 };
} }
return { top: 0, right: 0, bottom: 0, left: 0 }; return { top: 0, right: 0, bottom: 0, left: 0 };
} }

View file

@ -6,6 +6,6 @@
<%= tag.p @series.trend.percent_formatted, <%= tag.p @series.trend.percent_formatted,
style: "color: #{@series.trend.color}", style: "color: #{@series.trend.color}",
class: "text-right text-xs font-medium text-primary" %> class: "font-mono text-right text-xs font-medium text-primary" %>
</div> </div>
<% end %> <% end %>

View file

@ -8,21 +8,21 @@
data-tabs-inactive-class="text-secondary" data-tabs-inactive-class="text-secondary"
data-tabs-default-tab-value="assets-tab"> data-tabs-default-tab-value="assets-tab">
<div class="bg-surface-inset rounded-lg p-1 flex"> <div class="bg-surface-inset rounded-lg p-1 flex">
<button type="button" data-id="assets-tab" class="w-1/3 px-2 py-1 rounded-md text-sm text-secondary" data-tabs-target="btn" data-action="click->tabs#select"> <button type="button" data-id="assets-tab" class="w-1/3 px-2 py-1 rounded-md text-sm text-secondary font-medium" data-tabs-target="btn" data-action="click->tabs#select">
Assets Assets
</button> </button>
<button type="button" data-id="debts-tab" class="w-1/3 px-2 py-1 rounded-md text-secondary text-sm" data-tabs-target="btn" data-action="click->tabs#select"> <button type="button" data-id="debts-tab" class="w-1/3 px-2 py-1 rounded-md text-secondary text-sm font-medium" data-tabs-target="btn" data-action="click->tabs#select">
Debts Debts
</button> </button>
<button type="button" data-id="all-tab" class="w-1/3 px-2 py-1 rounded-md text-secondary text-sm" data-tabs-target="btn" data-action="click->tabs#select"> <button type="button" data-id="all-tab" class="w-1/3 px-2 py-1 rounded-md text-secondary text-sm font-medium" data-tabs-target="btn" data-action="click->tabs#select">
All All
</button> </button>
</div> </div>
<div data-tabs-target="tab" id="assets-tab"> <div data-tabs-target="tab" id="assets-tab">
<%= link_to new_account_path(step: "method_select"), <%= link_to new_account_path(step: "method_select", classification: "asset"),
class: "flex items-center gap-3 btn btn--ghost text-secondary mb-1", class: "flex items-center gap-3 btn btn--ghost text-secondary mb-1",
data: { turbo_frame: "modal" } do %> data: { turbo_frame: "modal" } do %>
<%= icon("plus") %> <%= icon("plus") %>
@ -37,7 +37,7 @@
</div> </div>
<div data-tabs-target="tab" id="debts-tab" class="hidden"> <div data-tabs-target="tab" id="debts-tab" class="hidden">
<%= link_to new_account_path(step: "method_select"), <%= link_to new_account_path(step: "method_select", classification: "liability"),
class: "flex items-center gap-3 btn btn--ghost text-secondary mb-1", class: "flex items-center gap-3 btn btn--ghost text-secondary mb-1",
data: { turbo_frame: "modal" } do %> data: { turbo_frame: "modal" } do %>
<%= icon("plus") %> <%= icon("plus") %>

View file

@ -19,12 +19,12 @@
<div class="space-y-1"> <div class="space-y-1">
<% account_group.accounts.each do |account| %> <% account_group.accounts.each do |account| %>
<%= link_to account_path(account), class: "block flex items-center gap-2 btn btn--ghost" do %> <%= link_to account_path(account), class: "block flex items-center gap-2 btn btn--ghost", title: account.name do %>
<%= render "accounts/logo", account: account, size: "sm", color: account_group.color %> <%= render "accounts/logo", account: account, size: "sm", color: account_group.color %>
<div> <div class="min-w-0 grow">
<%= tag.p account.name, class: "text-sm font-medium mb-0.5" %> <%= tag.p account.name, class: "text-sm font-medium mb-0.5 truncate" %>
<%= tag.p account.subtype&.humanize.presence || account_group.name, class: "text-sm text-secondary" %> <%= tag.p account.subtype&.humanize.presence || account_group.name, class: "text-sm text-secondary truncate" %>
</div> </div>
<div class="ml-auto text-right grow h-10"> <div class="ml-auto text-right grow h-10">

View file

@ -8,9 +8,9 @@
} %> } %>
<% if account.plaid_account_id? && account.institution_domain.present? %> <% if account.plaid_account_id? && account.institution_domain.present? %>
<%= image_tag "https://logo.synthfinance.com/#{account.institution_domain}", class: "rounded-full #{size_classes[size]}" %> <%= image_tag "https://logo.synthfinance.com/#{account.institution_domain}", class: "shrink-0 rounded-full #{size_classes[size]}" %>
<% elsif account.logo.attached? %> <% elsif account.logo.attached? %>
<%= image_tag account.logo, class: "rounded-full #{size_classes[size]}" %> <%= image_tag account.logo, class: "shrink-0 rounded-full #{size_classes[size]}" %>
<% else %> <% else %>
<%= circle_logo(account.name, hex: color || account.accountable.color, size: size) %> <%= circle_logo(account.name, hex: color || account.accountable.color, size: size) %>
<% end %> <% end %>

View file

@ -16,7 +16,7 @@
<%= tag.span period.comparison_label, class: "text-secondary" %> <%= tag.span period.comparison_label, class: "text-secondary" %>
</div> </div>
<div class="h-64"> <div class="h-64 pb-4">
<% if series.any? %> <% if series.any? %>
<div <div
id="lineChart" id="lineChart"

View file

@ -1,14 +1,25 @@
<%= render layout: "accounts/new/container", locals: { title: t(".title") } do %> <%= render layout: "accounts/new/container", locals: { title: t(".title") } do %>
<div class="text-sm"> <div class="text-sm">
<%= render "account_type", accountable: Depository.new %> <% unless params[:classification] == "liability" %>
<%= render "account_type", accountable: Investment.new %> <%= render "account_type", accountable: Depository.new %>
<%= render "account_type", accountable: Crypto.new %> <%= render "account_type", accountable: Investment.new %>
<%= render "account_type", accountable: Property.new %> <%= render "account_type", accountable: Crypto.new %>
<%= render "account_type", accountable: Vehicle.new %> <%= render "account_type", accountable: Property.new %>
<%= render "account_type", accountable: CreditCard.new %> <%= render "account_type", accountable: Vehicle.new %>
<%= render "account_type", accountable: Loan.new %> <% end %>
<%= render "account_type", accountable: OtherAsset.new %>
<%= render "account_type", accountable: OtherLiability.new %> <% unless params[:classification] == "asset" %>
<%= render "account_type", accountable: CreditCard.new %>
<%= render "account_type", accountable: Loan.new %>
<% end %>
<% unless params[:classification] == "liability" %>
<%= render "account_type", accountable: OtherAsset.new %>
<% end %>
<% unless params[:classification] == "asset" %>
<%= render "account_type", accountable: OtherLiability.new %>
<% end %>
<% unless params[:return_to].present? %> <% unless params[:return_to].present? %>
<%= button_to imports_path(import: { type: "AccountImport" }), <%= button_to imports_path(import: { type: "AccountImport" }),

View file

@ -6,6 +6,6 @@
<%= tag.p @account.sparkline_series.trend.percent_formatted, <%= tag.p @account.sparkline_series.trend.percent_formatted,
style: "color: #{@account.sparkline_series.trend.color}", style: "color: #{@account.sparkline_series.trend.color}",
class: "text-right text-xs font-medium text-primary" %> class: "font-mono text-right text-xs font-medium text-primary" %>
</div> </div>
<% end %> <% end %>

View file

@ -24,7 +24,7 @@
<% end %> <% end %>
<label class="relative"> <label class="relative">
<%= f.radio_button :color, "custom-color", class: "sr-only peer", data: { category_target: "colorPickerRadioBtn"} %> <%= f.radio_button :color, "custom-color", class: "sr-only peer", data: { category_target: "colorPickerRadioBtn"} %>
<div class="w-6 h-6 rounded-full cursor-pointer peer-checked:ring-2 peer-checked:ring-offset-2 peer-checked:ring-blue-500" data-category-target="pickerBtn" style="background: conic-gradient(red,orange,yellow,lime,green,teal,cyan,blue,indigo,purple,magenta,pink,red)" ></div> <div class="w-6 h-6 rounded-full cursor-pointer peer-checked:ring-2 peer-checked:ring-offset-2 peer-checked:ring-blue-500" data-category-target="pickerBtn" style="background: conic-gradient(red,orange,yellow,lime,green,teal,cyan,blue,indigo,purple,magenta,pink,red)"></div>
</label> </label>
</div> </div>
<div class="flex gap-2 items-center hidden flex-col" data-category-target="paletteSection"> <div class="flex gap-2 items-center hidden flex-col" data-category-target="paletteSection">
@ -46,7 +46,7 @@
<% Category.icon_codes.each do |icon| %> <% Category.icon_codes.each do |icon| %>
<label class="relative"> <label class="relative">
<%= f.radio_button :lucide_icon, icon, class: "sr-only peer", data: { action: "change->category#handleIconChange change->category#handleIconColorChange", category_target:"icon" } %> <%= f.radio_button :lucide_icon, icon, class: "sr-only peer", data: { action: "change->category#handleIconChange change->category#handleIconColorChange", category_target:"icon" } %>
<div class="w-7 h-7 flex m-0.5 items-center justify-center rounded-full cursor-pointer hover:bg-gray-100 peer-checked:bg-gray-100 border-1 border-transparent" > <div class="w-7 h-7 flex m-0.5 items-center justify-center rounded-full cursor-pointer hover:bg-gray-100 peer-checked:bg-gray-100 border-1 border-transparent">
<%= lucide_icon icon, class: "w-6 h-6 p-1" %> <%= lucide_icon icon, class: "w-6 h-6 p-1" %>
</div> </div>
</label> </label>

View file

@ -1,7 +1,10 @@
<%# locals: (category:) %> <%# locals: (category:) %>
<% is_selected = category.id === @selected_category&.id %> <% is_selected = category.id === @selected_category&.id %>
<%= content_tag :div, class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %> <%= content_tag :div,
class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full hover:bg-gray-25 focus-within:bg-gray-25",
{ "bg-gray-25": is_selected }],
data: { filter_name: category.name } do %>
<%= button_to account_transaction_category_path( <%= button_to account_transaction_category_path(
@transaction.entry, @transaction.entry,
account_entry: { account_entry: {
@ -10,7 +13,7 @@
} }
), ),
method: :patch, method: :patch,
class: "flex w-full items-center gap-1.5 cursor-pointer" do %> class: "flex w-full items-center gap-1.5 cursor-pointer focus:outline-none" do %>
<span class="w-5 h-5"> <span class="w-5 h-5">
<%= lucide_icon("check", class: "w-5 h-5 text-secondary") if is_selected %> <%= lucide_icon("check", class: "w-5 h-5 text-secondary") if is_selected %>

View file

@ -17,7 +17,7 @@
</li> </li>
<li> <li>
<%= render "layouts/sidebar/nav_item", name: "Budgets", path: budgets_path, icon_key: "layout-grid" %> <%= render "layouts/sidebar/nav_item", name: "Budgets", path: budgets_path, icon_key: "map" %>
</li> </li>
</ul> </ul>
@ -26,7 +26,7 @@
</div> </div>
</nav> </nav>
<%= tag.div class: class_names("py-4 shrink-0 h-full overflow-y-auto transition-all duration-300", Current.user.show_sidebar? ? "w-[260px]" : "w-0"), data: { sidebar_target: "panel" } do %> <%= tag.div class: class_names("py-4 shrink-0 h-full overflow-y-auto transition-all duration-300", Current.user.show_sidebar? ? "w-80" : "w-0"), data: { sidebar_target: "panel" } do %>
<% if content_for?(:sidebar) %> <% if content_for?(:sidebar) %>
<%= yield :sidebar %> <%= yield :sidebar %>
<% else %> <% else %>
@ -44,17 +44,16 @@
<% end %> <% end %>
<%= tag.div class: class_names("mx-auto w-full h-full", Current.user.show_sidebar? ? "max-w-4xl" : "max-w-5xl"), data: { sidebar_target: "content" } do %> <%= tag.div class: class_names("mx-auto w-full h-full", Current.user.show_sidebar? ? "max-w-4xl" : "max-w-5xl"), data: { sidebar_target: "content" } do %>
<% unless controller_path.start_with?('settings/') %> <% if content_for?(:breadcrumbs) %>
<% if content_for?(:breadcrumbs) %> <%= yield :breadcrumbs %>
<%= yield :breadcrumbs %> <% else %>
<% else %> <%= render "layouts/shared/breadcrumbs", breadcrumbs: @breadcrumbs %>
<%= render "layouts/shared/breadcrumbs", breadcrumbs: @breadcrumbs %>
<% end %>
<% if content_for?(:page_header) %>
<%= yield :page_header %>
<% end %>
<% end %> <% end %>
<% if content_for?(:page_header) %>
<%= yield :page_header %>
<% end %>
<%= yield %> <%= yield %>
<% end %> <% end %>
<% end %> <% end %>

View file

@ -1,6 +1,6 @@
<%= render "layouts/shared/htmldoc" do %> <%= render "layouts/shared/htmldoc" do %>
<div class="flex h-full bg-gray-25"> <div class="flex h-full bg-gray-25">
<div class="p-4 w-[260px] shrink-0 h-full overflow-y-auto"> <div class="p-4 w-96 shrink-0 h-full overflow-y-auto">
<%= render "settings/settings_nav" %> <%= render "settings/settings_nav" %>
</div> </div>
@ -10,7 +10,7 @@
<% if content_for?(:breadcrumbs) %> <% if content_for?(:breadcrumbs) %>
<%= yield :breadcrumbs %> <%= yield :breadcrumbs %>
<% else %> <% else %>
<%= render "layouts/shared/breadcrumbs", breadcrumbs: @breadcrumbs %> <%= render "layouts/shared/breadcrumbs", breadcrumbs: @breadcrumbs, sidebar_toggle_enabled: false %>
<% end %> <% end %>
<% if content_for?(:page_title) %> <% if content_for?(:page_title) %>

View file

@ -1,21 +1,25 @@
<%# locals: (breadcrumbs:) %> <%# locals: (breadcrumbs:, sidebar_toggle_enabled: true) %>
<nav class="flex items-center gap-2 mb-6"> <nav class="flex items-center gap-2 mb-6">
<button data-action="sidebar#toggle" class="w-9 h-9 inline-flex rounded-lg items-center justify-center hover:bg-gray-100 cursor-pointer"> <% if sidebar_toggle_enabled %>
<%= icon("panel-left", color: "gray") %> <button data-action="sidebar#toggle" class="p-2 inline-flex rounded-lg items-center justify-center hover:bg-gray-100 cursor-pointer">
</button> <%= icon("panel-left", color: "gray") %>
</button>
<% breadcrumbs.each_with_index do |(name, path), index| %>
<% if index > 0 %>
<%= icon("chevron-right", color: "gray", size: "sm") %>
<% end %>
<% if path.present? && index < breadcrumbs.size - 1 %>
<%= link_to name, path, class: "text-sm text-gray-500 font-medium" %>
<% elsif index == breadcrumbs.size - 1 %>
<span class="text-gray-900 font-medium text-sm"><%= name %></span>
<% else %>
<span class="text-sm text-gray-500 font-medium"><%= name %></span>
<% end %>
<% end %> <% end %>
<div class="py-2 flex items-center gap-2">
<% breadcrumbs.each_with_index do |(name, path), index| %>
<% if index > 0 %>
<%= icon("chevron-right", color: "gray", size: "sm") %>
<% end %>
<% if path.present? && index < breadcrumbs.size - 1 %>
<%= link_to name, path, class: "text-sm text-gray-500 font-medium" %>
<% elsif index == breadcrumbs.size - 1 %>
<span class="text-gray-900 font-medium text-sm"><%= name %></span>
<% else %>
<span class="text-sm text-gray-500 font-medium"><%= name %></span>
<% end %>
<% end %>
</div>
</nav> </nav>

View file

@ -1,7 +1,7 @@
<% content_for :page_header do %> <% content_for :page_header do %>
<div class="space-y-1 mb-6"> <div class="space-y-1 mb-6">
<h1 class="text-3xl font-medium text-gray-900">Welcome back, <%= Current.user.first_name %></h1> <h1 class="text-3xl font-medium text-gray-900">Welcome back, <%= Current.user.first_name %></h1>
<p class="text-gray-500">Here's what's happening with your money this week</p> <p class="text-gray-500">Here's what's happening with your finances</p>
</div> </div>
<% end %> <% end %>

View file

@ -17,7 +17,7 @@
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-2 text-sm">
<div class="h-2.5 w-2.5 rounded-full" style="background-color: <%= account_group.color %>;"></div> <div class="h-2.5 w-2.5 rounded-full" style="background-color: <%= account_group.color %>;"></div>
<p class="text-secondary"><%= account_group.name %></p> <p class="text-secondary"><%= account_group.name %></p>
<p class="text-black"><%= number_to_percentage(account_group.weight, precision: 0) %></p> <p class="text-black font-mono"><%= number_to_percentage(account_group.weight, precision: 0) %></p>
</div> </div>
<% end %> <% end %>
</div> </div>

View file

@ -1,10 +1,10 @@
<%# locals: (series:, period:) %> <%# locals: (series:, period:) %>
<div class="flex justify-between p-4"> <div class="flex justify-between gap-4 px-4">
<div class="space-y-2"> <div class="space-y-2">
<div class="space-y-2"> <div class="space-y-2">
<p class="text-sm text-secondary font-medium">Net Worth</p> <p class="text-sm text-secondary font-medium">Net Worth</p>
<p class="text-primary -space-x-0.5 text-xl font-medium"> <p class="text-primary -space-x-0.5 text-3xl font-medium">
<%= series.current.format %> <%= series.current.format %>
</p> </p>
<% if series.trend.nil? %> <% if series.trend.nil? %>

View file

@ -1,5 +1,5 @@
<div class="space-y-4"> <div class="space-y-4">
<div class="flex items-center gap-2 p-2"> <div class="flex items-center gap-2 p-1.5">
<%= link_to previous_path, class: "flex items-center gap-1 text-primary font-medium text-sm" do %> <%= link_to previous_path, class: "flex items-center gap-1 text-primary font-medium text-sm" do %>
<%= lucide_icon "chevron-left", class: "w-5 h-5 text-secondary" %> <%= lucide_icon "chevron-left", class: "w-5 h-5 text-secondary" %>
<span>Back</span> <span>Back</span>

View file

@ -4,11 +4,11 @@
<% if trend.direction.flat? %> <% if trend.direction.flat? %>
<span>No change</span> <span>No change</span>
<% else %> <% else %>
<span> <span class="font-mono">
<%= trend.value.is_a?(Money) ? format_money(trend.value) : trend.value.round(2) %> <%= trend.value.is_a?(Money) ? format_money(trend.value) : trend.value.round(2) %>
</span> </span>
<% unless trend.percent.infinite? %> <% unless trend.percent.infinite? %>
<span>(<%= lucide_icon(trend.icon, class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent_formatted %>)</span> <span class="font-mono">(<%= lucide_icon(trend.icon, class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent_formatted %>)</span>
<% end %> <% end %>
<% end %> <% end %>
</p> </p>