From a700a92171cb16a4d08af7775554c5e501d7228a Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 9 Jun 2026 17:33:40 -0400 Subject: [PATCH] feat(bar-app): inline spend chart bars/line toggle in the Spend header Move the bars/line choice out of Settings and into the Spend section header's blank space as a small inline toggle, so the user flips the chart style right where the chart is. Persistence unchanged. --- .../Sources/CCSBarApp/BarAnalyticsView.swift | 22 ++++++++++++++++++- macos-bar/Sources/CCSBarApp/BarMenuView.swift | 13 +++++++---- .../CCSBarApp/BarPreferencesView.swift | 9 ++------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/macos-bar/Sources/CCSBarApp/BarAnalyticsView.swift b/macos-bar/Sources/CCSBarApp/BarAnalyticsView.swift index 225abc81..4162a4af 100644 --- a/macos-bar/Sources/CCSBarApp/BarAnalyticsView.swift +++ b/macos-bar/Sources/CCSBarApp/BarAnalyticsView.swift @@ -22,6 +22,10 @@ struct BarAnalyticsView: View { /// Controls whether the spend sparkline renders as bars or a line graph. /// Passed in by BarMenuView so it reflects the user's persisted choice live. var spendChartStyle: SpendChartStyle = .bars + /// Inline flip for the spend chart style, surfaced as a small toggle in the + /// Spend header (using the otherwise-blank space) so the user switches + /// bars/line in place rather than digging into Settings. nil for `.breakdown`. + var onToggleSpendStyle: (() -> Void)? = nil private var lastActive: String? { BarFormatting.lastActiveLabel( @@ -71,7 +75,23 @@ struct BarAnalyticsView: View { /// line, and a thin inline 30-day sparkline when there is real spend. private var spendStrip: some View { VStack(alignment: .leading, spacing: 5) { - SectionLabel("Spend") + HStack(spacing: 6) { + SectionLabel("Spend") + Spacer() + // Inline bars/line toggle in the header's blank space — no Settings trip. + if let toggle = onToggleSpendStyle, analytics.hasRecentData, !sparklineIsEmpty { + Button(action: toggle) { + Image( + systemName: spendChartStyle == .bars + ? "chart.line.uptrend.xyaxis" : "chart.bar.fill" + ) + .font(.system(size: 10)) + } + .buttonStyle(.borderless) + .foregroundStyle(.tertiary) + .help("Spend graph: switch to \(spendChartStyle == .bars ? "line" : "bars")") + } + } if analytics.hasRecentData { Text(spendCaption) .font(.caption2) diff --git a/macos-bar/Sources/CCSBarApp/BarMenuView.swift b/macos-bar/Sources/CCSBarApp/BarMenuView.swift index 625c3bd7..b5d29124 100644 --- a/macos-bar/Sources/CCSBarApp/BarMenuView.swift +++ b/macos-bar/Sources/CCSBarApp/BarMenuView.swift @@ -50,12 +50,17 @@ struct BarMenuView: View { accountsSection // (3) SPEND — demoted to a thin informational strip below the cockpit. - // spendChartStyle is threaded from the viewModel so a live change in - // Settings immediately updates the chart without a refresh. + // spendChartStyle is threaded from the viewModel and toggled inline + // from the Spend header, so a change updates the chart immediately. if let analytics = viewModel.analytics { Divider() - BarAnalyticsView(analytics: analytics, section: .spend, - spendChartStyle: viewModel.spendChartStyle) + BarAnalyticsView( + analytics: analytics, section: .spend, + spendChartStyle: viewModel.spendChartStyle, + onToggleSpendStyle: { + viewModel.spendChartStyle = + viewModel.spendChartStyle == .bars ? .line : .bars + }) } // (4) POOL ACCOUNTS — compact generic rows, subordinate. diff --git a/macos-bar/Sources/CCSBarApp/BarPreferencesView.swift b/macos-bar/Sources/CCSBarApp/BarPreferencesView.swift index 59f493af..6e9a39c2 100644 --- a/macos-bar/Sources/CCSBarApp/BarPreferencesView.swift +++ b/macos-bar/Sources/CCSBarApp/BarPreferencesView.swift @@ -62,13 +62,8 @@ struct BarPreferencesView: View { Text("Dark").tag(BarAppearance.dark) } .pickerStyle(.segmented) - // Changing this live-updates the spend chart in the open dropdown because - // viewModel.spendChartStyle is @Published and SpendChartStyleStore persists it. - Picker("Spend graph", selection: $viewModel.spendChartStyle) { - Text("Bars").tag(SpendChartStyle.bars) - Text("Line").tag(SpendChartStyle.line) - } - .pickerStyle(.segmented) + // Spend graph bars/line is toggled inline in the dropdown's Spend header, + // not here — kept out of Settings so the choice lives where the chart is. } }