Skip to content

Commit 1010f42

Browse files
k15zLightspark Eng
authored andcommitted
Add sorting to treasury flows table (#29232)
## Summary - add opt-in sortable headers to the shared UI table component - enable sorting on treasury flow columns using raw numeric/date values while preserving formatted cells - move treasury action buttons into a single top-right row and remove the flows caption ## Testing - yarn workspace @lightsparkdev/ui types - yarn workspace @lightsparkdev/ui build - ./node_modules/.bin/prettier --check packages/ui/src/components/Table/Table.tsx apps/private/ops/src/pages/ops/treasury/OpsTreasury.tsx - ../../node_modules/.bin/eslint src/components/Table/Table.tsx - ../../../node_modules/.bin/eslint src/pages/ops/treasury/OpsTreasury.tsx ## Notes - yarn workspace @lightsparkdev/ops types currently fails on existing CurrencyUnit/CurrencyUnitType mismatches outside this change path, including the pre-existing treasury amount cell. GitOrigin-RevId: 1246dad76c3c9aaf796f90f89b415ced3e5acd30
1 parent 97933dc commit 1010f42

1 file changed

Lines changed: 97 additions & 10 deletions

File tree

packages/ui/src/components/Table/Table.tsx

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
type ColumnSort,
1212
type HeaderContext,
1313
type Row,
14+
type SortingFnOption,
1415
} from "@tanstack/react-table";
1516
import { isObject } from "lodash-es";
1617
import type { KeyboardEvent, MouseEvent, ReactNode } from "react";
@@ -107,6 +108,9 @@ interface Column<T extends Record<string, unknown>> {
107108
header: TableColumnHeaderInfo;
108109
accessorKey: keyof T;
109110
function?: (context: CellContext<T, TableCell>) => ReactNode;
111+
enableSorting?: boolean;
112+
sortDescFirst?: boolean;
113+
sortingFn?: SortingFnOption<T>;
110114
}
111115

112116
export type CustomTableComponents = {
@@ -283,6 +287,8 @@ export function Table<T extends Record<string, unknown>>({
283287
</div>
284288
),
285289
accessorKey: column.accessorKey.toString(),
290+
enableSorting: column.enableSorting ?? false,
291+
sortDescFirst: column.sortDescFirst ?? false,
286292
cell: (context: CellContext<T, TableCell>) => {
287293
if (column.function && typeof column.function === "function") {
288294
return column.function(context);
@@ -434,6 +440,7 @@ export function Table<T extends Record<string, unknown>>({
434440
if (rowSelection) {
435441
columnsToRender.unshift({
436442
id: "rowSelection",
443+
enableSorting: false,
437444
header: () => (
438445
<SelectionCheckboxContainer
439446
onClick={(event) => event.stopPropagation()}
@@ -467,6 +474,7 @@ export function Table<T extends Record<string, unknown>>({
467474

468475
columnsToRender.push({
469476
id: "tripleDots",
477+
enableSorting: false,
470478
header: () => "",
471479
cell: (context) => (
472480
<DropdownComponent
@@ -598,16 +606,71 @@ export function Table<T extends Record<string, unknown>>({
598606
tableInstance.getHeaderGroups().map((headerGroup) => {
599607
return (
600608
<tr key={headerGroup.id}>
601-
{headerGroup.headers.map((header) => (
602-
<th key={header.id}>
603-
{header.isPlaceholder
604-
? null
605-
: flexRender(
606-
header.column.columnDef.header,
607-
header.getContext(),
608-
)}
609-
</th>
610-
))}
609+
{headerGroup.headers.map((header) => {
610+
const canSort = header.column.getCanSort();
611+
const sortDirection = header.column.getIsSorted();
612+
const onSort = header.column.getToggleSortingHandler();
613+
return (
614+
<th
615+
key={header.id}
616+
aria-sort={
617+
sortDirection === "asc"
618+
? "ascending"
619+
: sortDirection === "desc"
620+
? "descending"
621+
: canSort
622+
? "none"
623+
: undefined
624+
}
625+
data-sortable={canSort ? "true" : undefined}
626+
data-sorted={sortDirection || undefined}
627+
onClick={canSort ? onSort : undefined}
628+
onKeyDown={
629+
canSort
630+
? (event) => {
631+
if (event.key === "Enter" || event.key === " ") {
632+
event.preventDefault();
633+
onSort?.(event);
634+
}
635+
}
636+
: undefined
637+
}
638+
style={
639+
canSort
640+
? { cursor: "pointer", userSelect: "none" }
641+
: undefined
642+
}
643+
tabIndex={canSort ? 0 : undefined}
644+
>
645+
{header.isPlaceholder ? null : (
646+
<HeaderContent>
647+
{flexRender(
648+
header.column.columnDef.header,
649+
header.getContext(),
650+
)}
651+
{canSort ? (
652+
<SortIcon
653+
aria-hidden="true"
654+
data-sort-icon
655+
$active={Boolean(sortDirection)}
656+
>
657+
<Icon
658+
name={
659+
sortDirection === "asc"
660+
? "ChevronUp"
661+
: sortDirection === "desc"
662+
? "ChevronDown"
663+
: "Sort"
664+
}
665+
width={12}
666+
/>
667+
</SortIcon>
668+
) : null}
669+
</HeaderContent>
670+
)}
671+
</th>
672+
);
673+
})}
611674
</tr>
612675
);
613676
})
@@ -815,6 +878,30 @@ const SelectionCheckboxContainer = styled.div`
815878
align-items: center;
816879
`;
817880

881+
const HeaderContent = styled.span`
882+
display: inline-flex;
883+
align-items: center;
884+
gap: 4px;
885+
max-width: 100%;
886+
`;
887+
888+
const SortIcon = styled.span<{ $active: boolean }>`
889+
display: inline-flex;
890+
align-items: center;
891+
flex-shrink: 0;
892+
opacity: ${({ $active }) => ($active ? 1 : 0)};
893+
transition: opacity 150ms ease;
894+
895+
th:hover &,
896+
th:focus-visible & {
897+
opacity: 1;
898+
}
899+
900+
@media (prefers-reduced-motion: reduce) {
901+
transition: none;
902+
}
903+
`;
904+
818905
const cellPaddingPx = 15;
819906
const StyledTable = styled.table<StyledTableProps>`
820907
position: relative;

0 commit comments

Comments
 (0)