Prettier 3 (pinned), printWidth 100, double quotes — chosen to match the existing hand-formatting and minimise reflow. This commit is ONLY the mechanical format pass (`prettier --write`) plus the config/ignore/scripts, isolated so it never pollutes a feature diff. Verified behaviour-neutral: typecheck, eslint (0 errors), and vitest all green before and after. See .git-blame-ignore-revs — `git blame` skips this sha.
69 lines
2.7 KiB
TypeScript
69 lines
2.7 KiB
TypeScript
import { ArrowDown, ArrowUp } from "lucide-react";
|
|
import { type Column } from "./DataTable";
|
|
import { type SortState } from "../lib/columnSort";
|
|
|
|
// A sort control for views without clickable column headers (the channel manager's card layout):
|
|
// a dropdown of the sortable columns + an asc/desc toggle, mirroring the feed toolbar's sort. It's
|
|
// driven by the SAME `SortState` the DataTable header uses, so table and cards stay in sync. The
|
|
// dropdown's options come straight from the columns' `sortable`/`sortValue` metadata — no new list.
|
|
export default function ColumnSortControl<T>({
|
|
columns,
|
|
sort,
|
|
onChange,
|
|
label,
|
|
defaultLabel,
|
|
ascLabel,
|
|
descLabel,
|
|
}: {
|
|
columns: Column<T>[];
|
|
sort: SortState;
|
|
onChange: (next: SortState) => void;
|
|
/** Accessible name for the dropdown (e.g. "Sort"). */
|
|
label: string;
|
|
/** The "no sort / original order" option's text. */
|
|
defaultLabel: string;
|
|
ascLabel: string;
|
|
descLabel: string;
|
|
}) {
|
|
const sortable = columns.filter((c) => c.sortable && c.sortValue);
|
|
// A stored sort can name a column this table no longer offers (renamed, or `sortable` dropped).
|
|
// `sortRows` already leaves the rows unsorted then, so treat it as no sort here too — otherwise the
|
|
// select falls back to showing "default" while a direction arrow claims a sort that isn't applied.
|
|
const active = sortable.some((c) => c.key === sort?.key) ? sort : null;
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
<select
|
|
value={active?.key ?? ""}
|
|
onChange={(e) => {
|
|
const key = e.target.value;
|
|
// Keep the direction when re-picking the same column; a new column starts ascending.
|
|
onChange(key ? { key, dir: active?.key === key ? active.dir : "asc" } : null);
|
|
}}
|
|
aria-label={label}
|
|
className="bg-card border border-border rounded-lg px-2 py-1.5 text-sm outline-none focus:border-accent"
|
|
>
|
|
<option value="">{defaultLabel}</option>
|
|
{sortable.map((c) => (
|
|
<option key={c.key} value={c.key}>
|
|
{c.header}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{active && (
|
|
<button
|
|
onClick={() => onChange({ key: active.key, dir: active.dir === "asc" ? "desc" : "asc" })}
|
|
title={active.dir === "asc" ? ascLabel : descLabel}
|
|
aria-label={active.dir === "asc" ? ascLabel : descLabel}
|
|
className="shrink-0 p-1.5 rounded-lg border border-border bg-card text-fg hover:border-accent hover:text-accent active:translate-y-px transition"
|
|
>
|
|
{active.dir === "asc" ? (
|
|
<ArrowUp className="w-4 h-4" />
|
|
) : (
|
|
<ArrowDown className="w-4 h-4" />
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|