From fb7107d614c09190bbff060fd9ead023b829f059 Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Wed, 7 May 2025 09:26:06 -0400 Subject: [PATCH] feature(dark mode): misc design fixes (#2215) * Fix category dark mode styles * Fix sidebar account tab states * Fix dashboard balance sheet group styles * Fix budget dark mode styles * Fix chart gradient split * Fix prose styles in dark mode * Add back chat nav id for tests --- app/assets/tailwind/application.css | 12 +- app/assets/tailwind/maybe-design-system.css | 9 ++ .../controllers/category_controller.js | 103 +++++++++++------- .../controllers/donut_chart_controller.js | 4 +- .../controllers/sidebar_tabs_controller.js | 16 +++ .../controllers/theme_controller.js | 15 +-- .../time_series_chart_controller.js | 26 +++-- app/models/budget.rb | 4 +- app/models/budget_category.rb | 6 +- .../accounts/_account_sidebar_tabs.html.erb | 68 ++++++------ .../accounts/_accountable_group.html.erb | 30 +++-- app/views/budgets/_budgeted_summary.html.erb | 12 +- app/views/budgets/show.html.erb | 39 +++---- app/views/categories/_badge.html.erb | 6 +- app/views/categories/_form.html.erb | 8 +- app/views/category/dropdowns/_row.html.erb | 4 +- app/views/category/dropdowns/show.html.erb | 16 ++- app/views/chats/_ai_greeting.html.erb | 4 +- app/views/chats/_chat_nav.html.erb | 9 +- app/views/chats/index.html.erb | 9 +- app/views/chats/new.html.erb | 2 +- app/views/chats/show.html.erb | 2 +- app/views/layouts/shared/_nav_item.html.erb | 2 +- .../pages/dashboard/_balance_sheet.html.erb | 8 +- .../pages/dashboard/_group_weight.html.erb | 4 +- app/views/transactions/_summary.html.erb | 2 +- .../transactions/_transfer_match.html.erb | 8 +- 27 files changed, 254 insertions(+), 174 deletions(-) create mode 100644 app/javascript/controllers/sidebar_tabs_controller.js diff --git a/app/assets/tailwind/application.css b/app/assets/tailwind/application.css index f97f63a5..4af14181 100644 --- a/app/assets/tailwind/application.css +++ b/app/assets/tailwind/application.css @@ -71,18 +71,22 @@ /* Typography */ .prose { - @apply max-w-none; + @apply max-w-none text-primary; + + a { + @apply text-link; + } h2 { - @apply text-xl font-medium; + @apply text-xl font-medium text-primary; } h3 { - @apply text-lg font-medium; + @apply text-lg font-medium text-primary; } li { - @apply m-0; + @apply m-0 text-primary; } details { diff --git a/app/assets/tailwind/maybe-design-system.css b/app/assets/tailwind/maybe-design-system.css index e03ccb1b..c8a583b8 100644 --- a/app/assets/tailwind/maybe-design-system.css +++ b/app/assets/tailwind/maybe-design-system.css @@ -26,6 +26,11 @@ --color-destructive: var(--color-red-600); --color-shadow: --alpha(var(--color-black) / 6%); + /* Colors used in Stimulus controllers with SVGs (easier to define light/dark mode here than toggle within the controllers) */ + /* See @layer base block below for dark mode overrides */ + --budget-unused-fill: var(--color-gray-200); + --budget-unallocated-fill: var(--color-gray-50); + /* Gray scale */ --color-gray-25: #FAFAFA; --color-gray-50: #F7F7F7; @@ -250,6 +255,10 @@ --color-destructive: var(--color-red-400); --color-shadow: --alpha(var(--color-white) / 8%); + /* Dark mode overrides for colors used in Stimulus controllers with SVGs */ + --budget-unused-fill: var(--color-gray-500); + --budget-unallocated-fill: var(--color-gray-700); + --shadow-xs: 0px 1px 2px 0px --alpha(var(--color-white) / 8%); --shadow-sm: 0px 1px 6px 0px --alpha(var(--color-white) / 8%); --shadow-md: 0px 4px 8px -2px --alpha(var(--color-white) / 8%); diff --git a/app/javascript/controllers/category_controller.js b/app/javascript/controllers/category_controller.js index dfbcd029..fdd47729 100644 --- a/app/javascript/controllers/category_controller.js +++ b/app/javascript/controllers/category_controller.js @@ -1,22 +1,36 @@ -import { Controller } from "@hotwired/stimulus" -import Pickr from '@simonwep/pickr' +import { Controller } from "@hotwired/stimulus"; +import Pickr from "@simonwep/pickr"; export default class extends Controller { - static targets = ["pickerBtn", "colorInput", "colorsSection", "paletteSection", "pickerSection", "colorPreview", "avatar", "details", "icon","validationMessage","selection","colorPickerRadioBtn"]; + static targets = [ + "pickerBtn", + "colorInput", + "colorsSection", + "paletteSection", + "pickerSection", + "colorPreview", + "avatar", + "details", + "icon", + "validationMessage", + "selection", + "colorPickerRadioBtn", + ]; + static values = { presetColors: Array, }; initialize() { - this.pickerBtnTarget.addEventListener('click', () => { + this.pickerBtnTarget.addEventListener("click", () => { this.showPaletteSection(); }); - this.colorInputTarget.addEventListener('input', (e) => { + this.colorInputTarget.addEventListener("input", (e) => { this.picker.setColor(e.target.value); }); - this.detailsTarget.addEventListener('toggle', (e) => { + this.detailsTarget.addEventListener("toggle", (e) => { if (!this.colorInputTarget.checkValidity()) { e.preventDefault(); this.colorInputTarget.reportValidity(); @@ -38,7 +52,7 @@ export default class extends Controller { this.picker = Pickr.create({ el: this.pickerBtnTarget, - theme: 'monolith', + theme: "monolith", container: ".pickerContainer", useAsButton: true, showAlways: true, @@ -48,7 +62,7 @@ export default class extends Controller { }, }); - this.picker.on('change', (color) => { + this.picker.on("change", (color) => { const hexColor = color.toHEXA().toString(); const rgbacolor = color.toRGBA(); @@ -74,23 +88,26 @@ export default class extends Controller { handleIconColorChange(e) { const selectedIcon = e.target; this.selectedIcon = selectedIcon; - + const currentColor = this.colorInputTarget.value; - - this.iconTargets.forEach(icon => { + + this.iconTargets.forEach((icon) => { const iconWrapper = icon.nextElementSibling; - iconWrapper.style.removeProperty("background-color") - iconWrapper.style.color = "black"; + iconWrapper.style.removeProperty("background-color"); + iconWrapper.style.removeProperty("color"); }); this.updateSelectedIconColor(currentColor); } handleIconChange(e) { - const iconSVG = e.currentTarget.closest('label').querySelector('svg').cloneNode(true); - this.avatarTarget.innerHTML = ''; - iconSVG.style.padding = "0px" - iconSVG.classList.add("w-8","h-8") + const iconSVG = e.currentTarget + .closest("label") + .querySelector("svg") + .cloneNode(true); + this.avatarTarget.innerHTML = ""; + iconSVG.style.padding = "0px"; + iconSVG.classList.add("w-8", "h-8"); this.avatarTarget.appendChild(iconSVG); } @@ -112,7 +129,9 @@ export default class extends Controller { handleContrastValidation(contrastRatio) { if (contrastRatio < 4.5) { - this.colorInputTarget.setCustomValidity("Poor contrast, choose darker color or auto-adjust."); + this.colorInputTarget.setCustomValidity( + "Poor contrast, choose darker color or auto-adjust.", + ); this.validationMessageTarget.classList.remove("hidden"); } else { @@ -121,7 +140,7 @@ export default class extends Controller { } } - autoAdjust(e){ + autoAdjust(e) { const currentRGBA = this.picker.getColor(); const adjustedRGBA = this.darkenColor(currentRGBA).toString(); this.picker.setColor(adjustedRGBA); @@ -129,22 +148,29 @@ export default class extends Controller { handleParentChange(e) { const parent = e.currentTarget.value; - const display = typeof parent === "string" && parent !== "" ? "none" : "flex"; + const display = + typeof parent === "string" && parent !== "" ? "none" : "flex"; this.selectionTarget.style.display = display; } - backgroundColor([r,g,b,a], percentage) { - const mixedR = Math.round((r * (percentage / 100)) + (255 * (1 - percentage / 100))); - const mixedG = Math.round((g * (percentage / 100)) + (255 * (1 - percentage / 100))); - const mixedB = Math.round((b * (percentage / 100)) + (255 * (1 - percentage / 100))); + backgroundColor([r, g, b, a], percentage) { + const mixedR = Math.round( + r * (percentage / 100) + 255 * (1 - percentage / 100), + ); + const mixedG = Math.round( + g * (percentage / 100) + 255 * (1 - percentage / 100), + ); + const mixedB = Math.round( + b * (percentage / 100) + 255 * (1 - percentage / 100), + ); return [mixedR, mixedG, mixedB]; } - luminance([r,g,b]) { - const toLinear = c => { + luminance([r, g, b]) { + const toLinear = (c) => { const scaled = c / 255; - return scaled <= 0.04045 - ? scaled / 12.92 + return scaled <= 0.04045 + ? scaled / 12.92 : ((scaled + 0.055) / 1.055) ** 2.4; }; return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b); @@ -162,12 +188,15 @@ export default class extends Controller { const backgroundColor = this.backgroundColor(darkened, 10); let contrastRatio = this.contrast(darkened, backgroundColor); - while (contrastRatio < 4.5 && (darkened[0] > 0 || darkened[1] > 0 || darkened[2] > 0)) { + while ( + contrastRatio < 4.5 && + (darkened[0] > 0 || darkened[1] > 0 || darkened[2] > 0) + ) { darkened = [ Math.max(0, darkened[0] - 10), Math.max(0, darkened[1] - 10), Math.max(0, darkened[2] - 10), - darkened[3] + darkened[3], ]; contrastRatio = this.contrast(darkened, backgroundColor); } @@ -177,23 +206,23 @@ export default class extends Controller { showPaletteSection() { this.initPicker(); - this.colorsSectionTarget.classList.add('hidden'); - this.paletteSectionTarget.classList.remove('hidden'); - this.pickerSectionTarget.classList.remove('hidden'); + this.colorsSectionTarget.classList.add("hidden"); + this.paletteSectionTarget.classList.remove("hidden"); + this.pickerSectionTarget.classList.remove("hidden"); this.picker.show(); } showColorsSection() { - this.colorsSectionTarget.classList.remove('hidden'); - this.paletteSectionTarget.classList.add('hidden'); - this.pickerSectionTarget.classList.add('hidden'); + this.colorsSectionTarget.classList.remove("hidden"); + this.paletteSectionTarget.classList.add("hidden"); + this.pickerSectionTarget.classList.add("hidden"); if (this.picker) { this.picker.destroyAndRemove(); } } toggleSections() { - if (this.colorsSectionTarget.classList.contains('hidden')) { + if (this.colorsSectionTarget.classList.contains("hidden")) { this.showColorsSection(); } else { this.showPaletteSection(); diff --git a/app/javascript/controllers/donut_chart_controller.js b/app/javascript/controllers/donut_chart_controller.js index 55c7cbb3..0c52104b 100644 --- a/app/javascript/controllers/donut_chart_controller.js +++ b/app/javascript/controllers/donut_chart_controller.js @@ -136,13 +136,13 @@ export default class extends Controller { .attr("fill", function () { if (this.dataset.segmentId === segmentId) { if (this.dataset.segmentId === unusedSegmentId) { - return "#A3A3A3"; + return "var(--budget-unused-fill)"; } return this.dataset.originalColor; } - return "#F0F0F0"; + return "var(--budget-unallocated-fill)"; }); this.defaultContentTarget.classList.add("hidden"); diff --git a/app/javascript/controllers/sidebar_tabs_controller.js b/app/javascript/controllers/sidebar_tabs_controller.js new file mode 100644 index 00000000..f88a70f7 --- /dev/null +++ b/app/javascript/controllers/sidebar_tabs_controller.js @@ -0,0 +1,16 @@ +import { Controller } from "@hotwired/stimulus"; + +// Connects to data-controller="sidebar-tabs" +export default class extends Controller { + static targets = ["account"]; + + select(event) { + this.accountTargets.forEach((account) => { + if (account.contains(event.target)) { + account.classList.add("bg-container"); + } else { + account.classList.remove("bg-container"); + } + }); + } +} diff --git a/app/javascript/controllers/theme_controller.js b/app/javascript/controllers/theme_controller.js index 4a7a9f48..10aa7b9a 100644 --- a/app/javascript/controllers/theme_controller.js +++ b/app/javascript/controllers/theme_controller.js @@ -4,7 +4,6 @@ export default class extends Controller { static values = { userPreference: String }; connect() { - this.applyTheme(); this.startSystemThemeListener(); } @@ -45,7 +44,7 @@ export default class extends Controller { if (isDark) { document.documentElement.setAttribute("data-theme", "dark"); } else { - document.documentElement.removeAttribute("data-theme"); + document.documentElement.setAttribute("data-theme", "light"); } } @@ -60,20 +59,12 @@ export default class extends Controller { } }; - toDark() { - this.setTheme(true); - } - - toLight() { - this.setTheme(false); - } - toggle() { const currentTheme = document.documentElement.getAttribute("data-theme"); if (currentTheme === "dark") { - this.toLight(); + this.setTheme(false); } else { - this.toDark(); + this.setTheme(true); } } diff --git a/app/javascript/controllers/time_series_chart_controller.js b/app/javascript/controllers/time_series_chart_controller.js index dc60e8cc..3de33c57 100644 --- a/app/javascript/controllers/time_series_chart_controller.js +++ b/app/javascript/controllers/time_series_chart_controller.js @@ -138,36 +138,48 @@ export default class extends Controller { .attr("x1", this._d3XScale.range()[0]) .attr("x2", this._d3XScale.range()[1]); + // First stop - solid trend color gradient .append("stop") .attr("class", "start-color") .attr("offset", "0%") .attr("stop-color", this.dataValue.trend.color); + // Second stop - trend color right before split gradient .append("stop") - .attr("class", "middle-color") + .attr("class", "split-before") .attr("offset", "100%") .attr("stop-color", this.dataValue.trend.color); + // Third stop - gray color right after split + gradient + .append("stop") + .attr("class", "split-after") + .attr("offset", "100%") + .attr("stop-color", "var(--color-gray-400)"); + + // Fourth stop - solid gray to end gradient .append("stop") .attr("class", "end-color") .attr("offset", "100%") - .attr("class", "fg-subdued") - .attr("stop-color", "currentColor"); + .attr("stop-color", "var(--color-gray-400)"); } _setTrendlineSplitAt(percent) { + const position = percent * 100; + + // Update both stops at the split point this._d3Svg .select(`#${this.element.id}-split-gradient`) - .select(".middle-color") - .attr("offset", `${percent * 100}%`); + .select(".split-before") + .attr("offset", `${position}%`); this._d3Svg .select(`#${this.element.id}-split-gradient`) - .select(".end-color") - .attr("offset", `${percent * 100}%`); + .select(".split-after") + .attr("offset", `${position}%`); this._d3Svg .select(`#${this.element.id}-trendline-gradient-rect`) diff --git a/app/models/budget.rb b/app/models/budget.rb index c2393da4..e53b6f09 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -131,14 +131,14 @@ class Budget < ApplicationRecord unused_segment_id = "unused" # Continuous gray segment for empty budgets - return [ { color: "#F0F0F0", amount: 1, id: unused_segment_id } ] unless allocations_valid? + return [ { color: "var(--budget-unallocated-fill)", amount: 1, id: unused_segment_id } ] unless allocations_valid? segments = budget_categories.map do |bc| { color: bc.category.color, amount: budget_category_actual_spending(bc), id: bc.id } end if available_to_spend.positive? - segments.push({ color: "#F0F0F0", amount: available_to_spend, id: unused_segment_id }) + segments.push({ color: "var(--budget-unallocated-fill)", amount: available_to_spend, id: unused_segment_id }) end segments diff --git a/app/models/budget_category.rb b/app/models/budget_category.rb index e909a7e8..a2a84850 100644 --- a/app/models/budget_category.rb +++ b/app/models/budget_category.rb @@ -79,14 +79,14 @@ class BudgetCategory < ApplicationRecord unused_segment_id = "unused" overage_segment_id = "overage" - return [ { color: "#F0F0F0", amount: 1, id: unused_segment_id } ] unless actual_spending > 0 + return [ { color: "var(--budget-unallocated-fill)", amount: 1, id: unused_segment_id } ] unless actual_spending > 0 segments = [ { color: category.color, amount: actual_spending, id: id } ] if available_to_spend.negative? - segments.push({ color: "#EF4444", amount: available_to_spend.abs, id: overage_segment_id }) + segments.push({ color: "var(--color-destructive)", amount: available_to_spend.abs, id: overage_segment_id }) else - segments.push({ color: "#F0F0F0", amount: available_to_spend, id: unused_segment_id }) + segments.push({ color: "var(--budget-unallocated-fill)", amount: available_to_spend, id: unused_segment_id }) end segments diff --git a/app/views/accounts/_account_sidebar_tabs.html.erb b/app/views/accounts/_account_sidebar_tabs.html.erb index 1ed98300..9ca2781c 100644 --- a/app/views/accounts/_account_sidebar_tabs.html.erb +++ b/app/views/accounts/_account_sidebar_tabs.html.erb @@ -21,16 +21,17 @@ <% end %> - <%= render TabsComponent.new(active_tab: active_account_group_tab, url_param_key: "account_group_tab", testid: "account-sidebar-tabs") do |tabs| %> - <% tabs.with_nav do |nav| %> - <% nav.with_btn(id: "assets", label: "Assets") %> - <% nav.with_btn(id: "debts", label: "Debts") %> - <% nav.with_btn(id: "all", label: "All") %> - <% end %> +
+ <%= render TabsComponent.new(active_tab: active_account_group_tab, url_param_key: "account_group_tab", testid: "account-sidebar-tabs") do |tabs| %> + <% tabs.with_nav do |nav| %> + <% nav.with_btn(id: "assets", label: "Assets") %> + <% nav.with_btn(id: "debts", label: "Debts") %> + <% nav.with_btn(id: "all", label: "All") %> + <% end %> - <% tabs.with_panel(tab_id: "assets") do %> -
- <%= render LinkComponent.new( + <% tabs.with_panel(tab_id: "assets") do %> +
+ <%= render LinkComponent.new( text: "New asset", variant: "ghost", href: new_account_path(step: "method_select", classification: "asset"), @@ -40,17 +41,17 @@ class: "justify-start" ) %> -
- <% family.balance_sheet.account_groups("asset").each do |group| %> - <%= render "accounts/accountable_group", account_group: group %> - <% end %> +
+ <% family.balance_sheet.account_groups("asset").each do |group| %> + <%= render "accounts/accountable_group", account_group: group %> + <% end %> +
-
- <% end %> + <% end %> - <% tabs.with_panel(tab_id: "debts") do %> -
- <%= render LinkComponent.new( + <% tabs.with_panel(tab_id: "debts") do %> +
+ <%= render LinkComponent.new( text: "New debt", variant: "ghost", href: new_account_path(step: "method_select", classification: "liability"), @@ -60,17 +61,17 @@ class: "justify-start" ) %> -
- <% family.balance_sheet.account_groups("liability").each do |group| %> - <%= render "accounts/accountable_group", account_group: group %> - <% end %> +
+ <% family.balance_sheet.account_groups("liability").each do |group| %> + <%= render "accounts/accountable_group", account_group: group %> + <% end %> +
-
- <% end %> + <% end %> - <% tabs.with_panel(tab_id: "all") do %> -
- <%= render LinkComponent.new( + <% tabs.with_panel(tab_id: "all") do %> +
+ <%= render LinkComponent.new( text: "New account", variant: "ghost", full_width: true, @@ -80,12 +81,13 @@ class: "justify-start" ) %> -
- <% family.balance_sheet.account_groups.each do |group| %> - <%= render "accounts/accountable_group", account_group: group %> - <% end %> +
+ <% family.balance_sheet.account_groups.each do |group| %> + <%= render "accounts/accountable_group", account_group: group %> + <% end %> +
-
+ <% end %> <% end %> - <% end %> +
diff --git a/app/views/accounts/_accountable_group.html.erb b/app/views/accounts/_accountable_group.html.erb index a3939727..a1ab2e3a 100644 --- a/app/views/accounts/_accountable_group.html.erb +++ b/app/views/accounts/_accountable_group.html.erb @@ -1,6 +1,6 @@ <%# locals: (account_group:) %> -<%= render DisclosureComponent.new(title: account_group.name, align: :left) do |disclosure| %> +<%= render DisclosureComponent.new(title: account_group.name, align: :left, open: account_group.accounts.any? { |account| page_active?(account_path(account)) }) do |disclosure| %> <% disclosure.with_summary_content do %>
<%= tag.p format_money(account_group.total_money), class: "text-sm font-medium text-primary" %> @@ -15,7 +15,13 @@
<% account_group.accounts.each do |account| %> - <%= link_to account_path(account), class: "block flex items-center gap-2 px-3 py-2 hover:bg-surface-hover", title: account.name do %> + <%= link_to account_path(account), + class: class_names( + "block flex items-center gap-2 px-3 py-2 rounded-lg", + page_active?(account_path(account)) ? "bg-container" : "hover:bg-surface-hover" + ), + data: { sidebar_tabs_target: "account", action: "click->sidebar-tabs#select" }, + title: account.name do %> <%= render "accounts/logo", account: account, size: "sm", color: account_group.color %>
@@ -36,13 +42,15 @@ <% end %>
- <%= render LinkComponent.new( - href: new_polymorphic_path(account_group.key, step: "method_select"), - text: "New #{account_group.name.downcase.singularize}", - icon: "plus", - full_width: true, - variant: "ghost", - frame: :modal, - class: "justify-start" - ) %> +
+ <%= render LinkComponent.new( + href: new_polymorphic_path(account_group.key, step: "method_select"), + text: "New #{account_group.name.downcase.singularize}", + icon: "plus", + full_width: true, + variant: "ghost", + frame: :modal, + class: "justify-start" + ) %> +
<% end %> diff --git a/app/views/budgets/_budgeted_summary.html.erb b/app/views/budgets/_budgeted_summary.html.erb index 41400860..37e225e7 100644 --- a/app/views/budgets/_budgeted_summary.html.erb +++ b/app/views/budgets/_budgeted_summary.html.erb @@ -15,7 +15,7 @@
<% else %>
-
+
<% end %>
@@ -41,18 +41,18 @@
<% if budget.available_to_spend.negative? %> -
-
+
+
<% else %> -
-
+
+
<% end %>

<%= format_money(budget.actual_spending_money) %> spent

<% if budget.available_to_spend.negative? %> - <%= format_money(budget.available_to_spend_money.abs) %> over + <%= format_money(budget.available_to_spend_money.abs) %> over <% else %> <%= format_money(budget.available_to_spend_money) %> left <% end %> diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb index 5d92a2f2..ff19bab1 100644 --- a/app/views/budgets/show.html.erb +++ b/app/views/budgets/show.html.erb @@ -16,31 +16,26 @@

+ <% if @budget.initialized? && @budget.available_to_allocate.positive? %> -
- <% base_classes = "rounded-md px-2 py-1 flex-1 text-center" %> - <% selected_tab = params[:tab].presence || "budgeted" %> + <%= render TabsComponent.new(active_tab: params[:tab].presence || "budgeted") do |tabs| %> + <% tabs.with_nav do |nav| %> + <% nav.with_btn(id: "budgeted", label: "Budgeted") %> + <% nav.with_btn(id: "actuals", label: "Actual") %> + <% end %> - <%= link_to "Budgeted", - budget_path(@budget, tab: "budgeted"), - class: class_names( - base_classes, - "bg-container shadow-xs text-primary": selected_tab == "budgeted", - "text-secondary": selected_tab != "budgeted" - ) %> + <% tabs.with_panel(tab_id: "budgeted") do %> +
+ <%= render "budgets/budgeted_summary", budget: @budget %> +
+ <% end %> - <%= link_to "Actual", - budget_path(@budget, tab: "actuals"), - class: class_names( - base_classes, - "bg-container shadow-xs text-primary": selected_tab == "actuals", - "text-secondary": selected_tab != "actuals" - ) %> -
- -
- <%= render selected_tab == "budgeted" ? "budgets/budgeted_summary" : "budgets/actuals_summary", budget: @budget %> -
+ <% tabs.with_panel(tab_id: "actuals") do %> +
+ <%= render "budgets/actuals_summary", budget: @budget %> +
+ <% end %> + <% end %> <% else %>
<%= render "budgets/actuals_summary", budget: @budget %> diff --git a/app/views/categories/_badge.html.erb b/app/views/categories/_badge.html.erb index 744ce94f..10135c25 100644 --- a/app/views/categories/_badge.html.erb +++ b/app/views/categories/_badge.html.erb @@ -2,10 +2,10 @@ <% category ||= Category.uncategorized %>
- <% if category.lucide_icon.present? %> <%= icon category.lucide_icon, size: "sm", color: "current" %> diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb index bb15e650..2c62d739 100644 --- a/app/views/categories/_form.html.erb +++ b/app/views/categories/_form.html.erb @@ -7,7 +7,7 @@ <%= render partial: "color_avatar", locals: { category: category } %>
- + <%= icon("pen", size: "sm") %> @@ -19,7 +19,7 @@ <% Category::COLORS.each do |color| %> <% end %>
-

Icon

+

Icon

<% Category.icon_codes.each do |icon| %>