feat(managers): shared sort across the table and card layouts
Cards have no column headers to sort by, so lift the sort out of DataTable into one shared state that BOTH views drive and reflect. New lib/columnSort.ts holds the comparator + click-cycle (one source of truth); DataTable gains an optional controlled sort (sort + onSortChange) and falls back to its internal persistKey sort when they're omitted, so every other table is untouched. The subscribed sort lives in ChannelsProvider, discovery keeps its own (its columns differ); a new ColumnSortControl dropdown (options derived from the columns' sortable metadata, feed-style + asc/desc toggle) drives it in cards mode. Per-account, persisted, per-tab.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// Shared column-sort primitives, lifted out of DataTable so a NON-table renderer (the channel
|
||||
// manager's card layout) can sort the same rows the same way — one comparator, one source of truth.
|
||||
// Kept structural (no `Column` import) so it has zero coupling to DataTable.
|
||||
|
||||
export type SortState = { key: string; dir: "asc" | "desc" } | null;
|
||||
|
||||
interface Sortable<T> {
|
||||
key: string;
|
||||
sortValue?: (row: T) => string | number;
|
||||
}
|
||||
|
||||
/** Sort a copy of `rows` by the sortable column named in `sort` (numeric diff or locale compare).
|
||||
* Returns `rows` unchanged when there's no sort or the column has no `sortValue`. */
|
||||
export function sortRows<T>(rows: T[], columns: Sortable<T>[], sort: SortState): T[] {
|
||||
if (!sort) return rows;
|
||||
const get = columns.find((c) => c.key === sort.key)?.sortValue;
|
||||
if (!get) return rows;
|
||||
return [...rows].sort((a, b) => {
|
||||
const av = get(a);
|
||||
const bv = get(b);
|
||||
const cmp =
|
||||
typeof av === "number" && typeof bv === "number"
|
||||
? av - bv
|
||||
: String(av).localeCompare(String(bv));
|
||||
return sort.dir === "asc" ? cmp : -cmp;
|
||||
});
|
||||
}
|
||||
|
||||
/** The header-click cycle: unsorted → ascending → descending → unsorted. */
|
||||
export function cycleSort(current: SortState, key: string): SortState {
|
||||
if (!current || current.key !== key) return { key, dir: "asc" };
|
||||
if (current.dir === "asc") return { key, dir: "desc" };
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user