diff --git a/app/javascript/controllers/goals_filter_controller.js b/app/javascript/controllers/goals_filter_controller.js
index 5e68db6f8..ed98040b9 100644
--- a/app/javascript/controllers/goals_filter_controller.js
+++ b/app/javascript/controllers/goals_filter_controller.js
@@ -155,10 +155,7 @@ export default class extends Controller {
this.chipTargets.forEach((chip) => {
const active = chip.dataset.status === this.statusValue;
chip.setAttribute("aria-pressed", active);
- chip.classList.toggle("bg-container", active);
- chip.classList.toggle("shadow-border-xs", active);
- chip.classList.toggle("text-primary", active);
- chip.classList.toggle("text-secondary", !active);
+ chip.classList.toggle("segmented-control__segment--active", active);
});
}
}
diff --git a/app/javascript/controllers/providers_filter_controller.js b/app/javascript/controllers/providers_filter_controller.js
index a4d90e2a4..3b07a0f45 100644
--- a/app/javascript/controllers/providers_filter_controller.js
+++ b/app/javascript/controllers/providers_filter_controller.js
@@ -58,10 +58,7 @@ export default class extends Controller {
if (!this.hasChipTarget) return;
this.chipTargets.forEach((chip) => {
const active = chip.dataset.kind === this.kindValue;
- chip.classList.toggle("bg-container", active);
- chip.classList.toggle("shadow-border-xs", active);
- chip.classList.toggle("text-primary", active);
- chip.classList.toggle("text-secondary", !active);
+ chip.classList.toggle("segmented-control__segment--active", active);
chip.setAttribute("aria-pressed", active ? "true" : "false");
});
}
diff --git a/app/javascript/controllers/transaction_type_tabs_controller.js b/app/javascript/controllers/transaction_type_tabs_controller.js
index e347121de..ca16a0459 100644
--- a/app/javascript/controllers/transaction_type_tabs_controller.js
+++ b/app/javascript/controllers/transaction_type_tabs_controller.js
@@ -1,21 +1,26 @@
-import { Controller } from "@hotwired/stimulus"
-
-const ACTIVE_CLASSES = ["bg-container", "text-primary", "shadow-sm"]
-const INACTIVE_CLASSES = ["hover:bg-container", "text-subdued", "hover:text-primary", "hover:shadow-sm"]
+import { Controller } from "@hotwired/stimulus";
+// Expense / income tabs render as a DS::SegmentedControl. Switching is
+// client-side here: it updates the form's hidden nature field and flips the
+// active segment without navigating (the href is a progressive-enhancement
+// fallback). Transfer is a plain link to the transfer form.
export default class extends Controller {
- static targets = ["tab", "natureField"]
+ static targets = ["tab", "natureField"];
selectTab(event) {
- event.preventDefault()
+ event.preventDefault();
- const selectedTab = event.currentTarget
- this.natureFieldTarget.value = selectedTab.dataset.nature
+ const selectedTab = event.currentTarget;
+ this.natureFieldTarget.value = selectedTab.dataset.nature;
- this.tabTargets.forEach(tab => {
- const isActive = tab === selectedTab
- tab.classList.remove(...(isActive ? INACTIVE_CLASSES : ACTIVE_CLASSES))
- tab.classList.add(...(isActive ? ACTIVE_CLASSES : INACTIVE_CLASSES))
- })
+ this.tabTargets.forEach((tab) => {
+ const isActive = tab === selectedTab;
+ tab.classList.toggle("segmented-control__segment--active", isActive);
+ if (isActive) {
+ tab.setAttribute("aria-current", "true");
+ } else {
+ tab.removeAttribute("aria-current");
+ }
+ });
}
}
diff --git a/app/views/goals/index.html.erb b/app/views/goals/index.html.erb
index 8d927234b..d0bb68b10 100644
--- a/app/views/goals/index.html.erb
+++ b/app/views/goals/index.html.erb
@@ -125,19 +125,18 @@
horizontally when the column is too narrow for all six chips,
rather than wrapping "On track" mid-label or forcing the row
wider than the viewport. %>
-
+ <%= render DS::SegmentedControl.new(aria_label: t(".chips_aria", default: "Filter goals by status"), class: "overflow-x-auto max-w-full") do |sc| %>
<% %w[all on_track behind no_target_date paused completed].each do |status| %>
- <% active = status == "all" %>
-
+ <% sc.with_segment(t(".chips.#{status}"),
+ active: status == "all",
+ class: "shrink-0 whitespace-nowrap",
+ data: {
+ "goals-filter-target": "chip",
+ status: status,
+ action: "click->goals-filter#selectChip"
+ }) %>
<% end %>
-
+ <% end %>
<% end %>
diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb
index 8080b6b2e..a01f68cc4 100644
--- a/app/views/reports/index.html.erb
+++ b/app/views/reports/index.html.erb
@@ -30,38 +30,14 @@
<%# Period Navigation Tabs %>
-
- <%= render DS::Link.new(
- text: t("reports.index.periods.monthly"),
- variant: @period_type == :monthly ? "secondary" : "ghost",
- href: reports_path(period_type: :monthly),
- size: :sm
- ) %>
- <%= render DS::Link.new(
- text: t("reports.index.periods.quarterly"),
- variant: @period_type == :quarterly ? "secondary" : "ghost",
- href: reports_path(period_type: :quarterly),
- size: :sm
- ) %>
- <%= render DS::Link.new(
- text: t("reports.index.periods.ytd"),
- variant: @period_type == :ytd ? "secondary" : "ghost",
- href: reports_path(period_type: :ytd),
- size: :sm
- ) %>
- <%= render DS::Link.new(
- text: t("reports.index.periods.last_6_months"),
- variant: @period_type == :last_6_months ? "secondary" : "ghost",
- href: reports_path(period_type: :last_6_months),
- size: :sm
- ) %>
- <%= render DS::Link.new(
- text: t("reports.index.periods.custom"),
- variant: @period_type == :custom ? "secondary" : "ghost",
- href: reports_path(period_type: :custom),
- size: :sm
- ) %>
-
+ <%= render DS::SegmentedControl.new(aria_label: t("reports.index.period_aria", default: "Report period"), class: "overflow-x-auto no-scrollbar") do |sc| %>
+ <% %i[monthly quarterly ytd last_6_months custom].each do |ptype| %>
+ <% sc.with_segment(t("reports.index.periods.#{ptype}"),
+ active: @period_type == ptype,
+ href: reports_path(period_type: ptype),
+ class: "whitespace-nowrap") %>
+ <% end %>
+ <% end %>
<%# Custom Date Range Picker (only shown when custom is selected) %>
diff --git a/app/views/settings/providers/_search_filters.html.erb b/app/views/settings/providers/_search_filters.html.erb
index 30c36b172..54d28f33f 100644
--- a/app/views/settings/providers/_search_filters.html.erb
+++ b/app/views/settings/providers/_search_filters.html.erb
@@ -8,17 +8,15 @@
action: "input->providers-filter#filter"
}
) %>
-
+ <%= render DS::SegmentedControl.new(aria_label: t("settings.providers.search_filters.aria_label")) do |sc| %>
<% %w[all bank crypto investment].each do |kind| %>
- <% active = kind == "all" %>
-
+ <% sc.with_segment(t("settings.providers.search_filters.chips.#{kind}"),
+ active: kind == "all",
+ data: {
+ "providers-filter-target": "chip",
+ kind: kind,
+ action: "click->providers-filter#selectChip"
+ }) %>
<% end %>
-
+ <% end %>
diff --git a/app/views/shared/_transaction_type_tabs.html.erb b/app/views/shared/_transaction_type_tabs.html.erb
index dbb1f6b55..43b60a0da 100644
--- a/app/views/shared/_transaction_type_tabs.html.erb
+++ b/app/views/shared/_transaction_type_tabs.html.erb
@@ -1,30 +1,24 @@
<%# locals: (active_tab:, account_id: nil) %>
-
+ <% sc.with_segment(
+ safe_join([ icon("arrow-right-left"), tag.span(t("shared.transaction_tabs.transfer"), class: "truncate") ]),
+ active: active_tab == "transfer",
+ href: new_transfer_path(from_account_id: account_id),
+ class: "min-w-0 gap-2",
+ data: { turbo_frame: :modal }) %>
+<% end %>