CONTENT-WP-0002-T01 adapts the pinned OnCo visual system under ui/ without the corpus, mark, or kind routes. Assistant: grok Assistant-Session: 01a1346c-fdff-7741-b1af-b7283661554b
100 lines
2.6 KiB
TypeScript
100 lines
2.6 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import type { SortState } from "@/lib/types";
|
|
|
|
/*
|
|
* Reduced from OnCo src/components/filters/ResultsTable.tsx
|
|
* https://github.com/judegomila/OnCo @ b594d7433bf4e408b44215e25e5304696d6f840e
|
|
* Copyright (c) 2026 OnCo. MIT License. See ui/NOTICE.
|
|
*
|
|
* The parent owns sorting. This table does not filter, resize, translate, or page.
|
|
*/
|
|
|
|
export type Column<T> = {
|
|
key: string;
|
|
label: string;
|
|
render: (row: T) => ReactNode;
|
|
sortable?: boolean;
|
|
};
|
|
|
|
export function ResultsTable<T>({
|
|
columns,
|
|
rows,
|
|
rowKey,
|
|
sort,
|
|
onSort,
|
|
empty = "No records.",
|
|
}: {
|
|
columns: Column<T>[];
|
|
rows: readonly T[];
|
|
rowKey: (row: T) => string;
|
|
sort?: SortState;
|
|
onSort?: (key: string) => void;
|
|
empty?: string;
|
|
}) {
|
|
return (
|
|
<div className="card results-table">
|
|
<div className="overflow-x-auto lg:overflow-x-visible">
|
|
<table className="records">
|
|
<thead>
|
|
<tr>
|
|
{columns.map((column) => {
|
|
const sorted = sort?.key === column.key;
|
|
const direction = sort?.dir === -1 ? "descending" : "ascending";
|
|
return (
|
|
<th
|
|
key={column.key}
|
|
scope="col"
|
|
aria-sort={sorted ? direction : undefined}
|
|
>
|
|
<ColumnHead column={column} sort={sort} onSort={onSort} />
|
|
</th>
|
|
);
|
|
})}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={columns.length}>{empty}</td>
|
|
</tr>
|
|
) : (
|
|
rows.map((row) => (
|
|
<tr key={rowKey(row)}>
|
|
{columns.map((column) => (
|
|
<td key={column.key}>{column.render(row)}</td>
|
|
))}
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ColumnHead<T>({
|
|
column,
|
|
sort,
|
|
onSort,
|
|
}: {
|
|
column: Column<T>;
|
|
sort?: SortState;
|
|
onSort?: (key: string) => void;
|
|
}) {
|
|
if (!column.sortable || !onSort) return <>{column.label}</>;
|
|
const sorted = sort?.key === column.key;
|
|
const title = sorted
|
|
? sort?.dir === -1
|
|
? `Sorted descending by ${column.label}`
|
|
: `Sorted ascending by ${column.label}`
|
|
: `Sort by ${column.label}`;
|
|
return (
|
|
<button type="button" onClick={() => onSort(column.key)} title={title}>
|
|
{column.label}
|
|
<span aria-hidden="true" className={sorted ? "text-accent" : "text-muted/40"}>
|
|
{sorted ? (sort?.dir === -1 ? " ↓" : " ↑") : " ↕"}
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|