mirror of
https://github.com/we-promise/sure.git
synced 2026-05-23 20:44:55 +00:00
feat(settings/providers): replace Add another provider CTA with a search + kind filter
Per the design review, the "Add another provider · Browse providers" card was a redirect to content one scroll-tick away. A search input plus kind chips lets users self-segment the catalog and is the right tool once it grows beyond the four to twelve providers we ship today. - New providers_filter Stimulus controller — case-insensitive free text search across name/region/kind, plus a chip group with All / Banks / Crypto / Investment that toggle visibility via Tailwind's `hidden` class. - _search_filters partial: search box (count-pluralized placeholder) + chip group, ARIA-labelled and aria-pressed for the chips. - ProviderCard exposes filter_data (target + name/region/kind data attrs) so the controller can match without re-rendering. - Lunchflow's `kind` was "Lunch" — switched to "Bank" so it falls under the Banks chip alongside its actual offering (it aggregates banks). - Drops the add_provider_cta partial and its locale entries; adds search_filters.* and an empty_filter message.
This commit is contained in:
committed by
Guillem Arias
parent
bf73e3a1e3
commit
d037412b8d
54
app/javascript/controllers/providers_filter_controller.js
Normal file
54
app/javascript/controllers/providers_filter_controller.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
// Connects to data-controller="providers-filter"
|
||||
// Filters provider cards by free-text query and a chip-selected kind.
|
||||
export default class extends Controller {
|
||||
static targets = ["input", "chip", "card", "empty"];
|
||||
static values = { kind: { type: String, default: "all" } };
|
||||
|
||||
connect() {
|
||||
this.syncChipState();
|
||||
}
|
||||
|
||||
filter() {
|
||||
const query = this.hasInputTarget
|
||||
? this.inputTarget.value.toLocaleLowerCase().trim()
|
||||
: "";
|
||||
const activeKind = this.kindValue;
|
||||
let visibleCount = 0;
|
||||
|
||||
this.cardTargets.forEach((card) => {
|
||||
const name = card.dataset.providerName ?? "";
|
||||
const region = card.dataset.providerRegion ?? "";
|
||||
const kind = card.dataset.providerKind ?? "";
|
||||
const haystack = `${name} ${region} ${kind}`;
|
||||
const matchesQuery = !query || haystack.includes(query);
|
||||
const matchesKind = activeKind === "all" || kind === activeKind;
|
||||
const visible = matchesQuery && matchesKind;
|
||||
card.classList.toggle("hidden", !visible);
|
||||
if (visible) visibleCount++;
|
||||
});
|
||||
|
||||
if (this.hasEmptyTarget) {
|
||||
this.emptyTarget.classList.toggle("hidden", visibleCount > 0);
|
||||
}
|
||||
}
|
||||
|
||||
selectChip(event) {
|
||||
this.kindValue = event.currentTarget.dataset.kind ?? "all";
|
||||
this.syncChipState();
|
||||
this.filter();
|
||||
}
|
||||
|
||||
syncChipState() {
|
||||
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.setAttribute("aria-pressed", active ? "true" : "false");
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user