diff --git a/.changeset/table-sort-icon.md b/.changeset/table-sort-icon.md new file mode 100644 index 0000000000..69c033620b --- /dev/null +++ b/.changeset/table-sort-icon.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/kumo": minor +--- + +Add `Table.SortIcon` sub-component with `direction: false | "asc" | "desc"` prop diff --git a/packages/kumo-docs-astro/package.json b/packages/kumo-docs-astro/package.json index 43ce487fc2..f7c2701f8f 100644 --- a/packages/kumo-docs-astro/package.json +++ b/packages/kumo-docs-astro/package.json @@ -24,6 +24,7 @@ "@cloudflare/kumo": "workspace:*", "@phosphor-icons/react": "catalog:", "@tailwindcss/typography": "^0.5.19", + "@tanstack/react-table": "^8.21.3", "@types/turndown": "5.0.6", "astro": "^5.16.7", "clsx": "catalog:", diff --git a/packages/kumo-docs-astro/src/components/demos/TableDemo.tsx b/packages/kumo-docs-astro/src/components/demos/TableDemo.tsx index b4fd3eed78..b35ac84568 100644 --- a/packages/kumo-docs-astro/src/components/demos/TableDemo.tsx +++ b/packages/kumo-docs-astro/src/components/demos/TableDemo.tsx @@ -13,6 +13,14 @@ import { PencilSimple, Trash, } from "@phosphor-icons/react"; +import { + type ColumnDef, + type SortingState, + flexRender, + getCoreRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; // Sample data for demos const emailData = [ @@ -264,6 +272,218 @@ export function TableFixedLayoutDemo() { ); } +// --------------------------------------------------------------------------- +// TanStack Table integration data + demo +// --------------------------------------------------------------------------- + +type Worker = { + id: string; + name: string; + requests: number; + errors: number; + cpuMs: number; + status: "Active" | "Inactive" | "Degraded"; +}; + +const workerData: Worker[] = [ + { + id: "1", + name: "api-gateway", + requests: 142_830, + errors: 12, + cpuMs: 4.2, + status: "Active", + }, + { + id: "2", + name: "auth-service", + requests: 98_210, + errors: 0, + cpuMs: 2.1, + status: "Active", + }, + { + id: "3", + name: "image-resizer", + requests: 34_560, + errors: 87, + cpuMs: 18.9, + status: "Degraded", + }, + { + id: "4", + name: "cache-purger", + requests: 6_120, + errors: 0, + cpuMs: 0.8, + status: "Active", + }, + { + id: "5", + name: "log-drain", + requests: 0, + errors: 0, + cpuMs: 0, + status: "Inactive", + }, + { + id: "6", + name: "edge-router", + requests: 215_400, + errors: 3, + cpuMs: 1.5, + status: "Active", + }, +]; + +const workerColumns: ColumnDef[] = [ + { + accessorKey: "name", + header: "Worker", + size: 200, + minSize: 120, + }, + { + accessorKey: "requests", + header: "Requests", + size: 130, + minSize: 90, + cell: ({ getValue }) => (getValue() as number).toLocaleString(), + }, + { + accessorKey: "errors", + header: "Errors", + size: 100, + minSize: 70, + }, + { + accessorKey: "cpuMs", + header: "CPU (ms)", + size: 110, + minSize: 80, + cell: ({ getValue }) => `${getValue() as number} ms`, + }, + { + accessorKey: "status", + header: "Status", + size: 110, + minSize: 80, + cell: ({ getValue }) => { + const status = getValue() as Worker["status"]; + const variantMap: Record< + Worker["status"], + "secondary" | "destructive" | "success" + > = { + Active: "success", + Degraded: "destructive", + Inactive: "secondary", + }; + return {status}; + }, + }, +]; + +/** TanStack Table with sortable columns and resizable column widths using Table.ResizeHandle */ +export function TableTanStackSortableResizableDemo() { + const [sorting, setSorting] = useState([]); + + const table = useReactTable({ + data: workerData, + columns: workerColumns, + state: { sorting }, + onSortingChange: setSorting, + columnResizeMode: "onChange", + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + + return ( + + + + + {table.getAllColumns().map((col) => ( + + ))} + {/* Filler column — absorbs remaining space so fixed columns don't stretch */} + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const isSorted = header.column.getIsSorted(); + const canSort = header.column.getCanSort(); + return ( + +
+ {header.isPlaceholder ? null : ( + <> + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + {canSort && } + + )} +
+ { + e.stopPropagation(); + }} + onMouseDown={(e) => { + header.getResizeHandler()(e); + }} + onTouchStart={(e) => { + header.getResizeHandler()(e); + }} + /> +
+ ); + })} + {/* Filler header cell matching the filler col */} + +
+ ))} +
+ + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + {/* Filler cell matching the filler col */} + + + ))} + +
+
+
+ ); +} + export function TableFullDemo() { const [selectedIds, setSelectedIds] = useState>(new Set(["2"])); diff --git a/packages/kumo-docs-astro/src/pages/components/table.mdx b/packages/kumo-docs-astro/src/pages/components/table.mdx index 0fe78aba0f..cb564e0db5 100644 --- a/packages/kumo-docs-astro/src/pages/components/table.mdx +++ b/packages/kumo-docs-astro/src/pages/components/table.mdx @@ -17,6 +17,7 @@ import { TableSelectedRowDemo, TableFixedLayoutDemo, TableFullDemo, + TableTanStackSortableResizableDemo, } from "~/components/demos/TableDemo"; {/* Demo */} @@ -243,6 +244,178 @@ export default function Example() { > + + TanStack Table — Sortable & Resizable +

+ Combine @tanstack/react-table with + Kumo's Table for sortable columns + and draggable column resizing via Table.ResizeHandle. + Click any column header to sort; drag the resize handle on the right edge to change column width. +

+ [] = [ + { accessorKey: "name", header: "Worker", size: 200, minSize: 120 }, + { + accessorKey: "requests", + header: "Requests", + size: 130, + minSize: 90, + cell: ({ getValue }) => (getValue() as number).toLocaleString(), + }, + { accessorKey: "errors", header: "Errors", size: 100, minSize: 70 }, + { + accessorKey: "cpuMs", + header: "CPU (ms)", + size: 110, + minSize: 80, + cell: ({ getValue }) => \`\${getValue() as number} ms\`, + }, + { + accessorKey: "status", + header: "Status", + size: 110, + minSize: 80, + cell: ({ getValue }) => { + const status = getValue() as Worker["status"]; + return {status}; + }, + }, +]; + +export function DataTable() { + const [sorting, setSorting] = useState([]); + + const table = useReactTable({ + data: workerData, + columns, + state: { sorting }, + onSortingChange: setSorting, + columnResizeMode: "onChange", + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + + return ( + + + {table.getAllColumns().map((col) => ( + + ))} + {/* Filler column — absorbs remaining space so fixed columns don't stretch */} + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const isSorted = header.column.getIsSorted(); + const canSort = header.column.getCanSort(); + return ( + +
+ {header.isPlaceholder ? null : ( + <> + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + {canSort && } + + )} +
+ { + e.stopPropagation(); + }} + onMouseDown={(e) => { + header.getResizeHandler()(e); + }} + onTouchStart={(e) => { + header.getResizeHandler()(e); + }} + /> +
+ ); + })} + {/* Filler header cell matching the filler col */} + +
+ ))} +
+ + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + {/* Filler cell matching the filler col */} + + + ))} + +
+ ); +} + +type Worker = { + id: string; + name: string; + requests: number; + errors: number; + cpuMs: number; + status: "Active" | "Inactive" | "Degraded"; +}; + +const workerData: Worker[] = [ + { id: "1", name: "api-gateway", requests: 142_830, errors: 12, cpuMs: 4.2, status: "Active" }, + { id: "2", name: "auth-service", requests: 98_210, errors: 0, cpuMs: 2.1, status: "Active" }, + { id: "3", name: "image-resizer", requests: 34_560, errors: 87, cpuMs: 18.9, status: "Degraded" }, + { id: "4", name: "cache-purger", requests: 6_120, errors: 0, cpuMs: 0.8, status: "Active" }, + { id: "5", name: "log-drain", requests: 0, errors: 0, cpuMs: 0, status: "Inactive" }, + { id: "6", name: "edge-router", requests: 215_400, errors: 3, cpuMs: 1.5, status: "Active" }, +]; + +const variantMap: Record = { + Active: "success", + Degraded: "destructive", + Inactive: "secondary", +}; +`} + > + +
{/* API Reference */} @@ -293,70 +466,6 @@ Body cell. Renders ``.

-{/* TanStack Table Integration */} - - - TanStack Table Integration -

- For advanced features like sorting, filtering, and resizable columns, - integrate with TanStack Table. The Table component is designed to work seamlessly with TanStack's - headless API. -

- - - {table.getAllColumns().map((column) => ( - - ))} - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {flexRender(header.column.columnDef.header, header.getContext())} - - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ))} - - -); }`} lang="tsx" /> - -
- {/* Accessibility */} diff --git a/packages/kumo/ai/component-registry.json b/packages/kumo/ai/component-registry.json index ecde5a51d5..48476ce1cb 100644 --- a/packages/kumo/ai/component-registry.json +++ b/packages/kumo/ai/component-registry.json @@ -1938,7 +1938,13 @@ "description": "Additional CSS classes merged via `cn()`." } }, - "examples": [], + "examples": [ + "", + "", + "", + "", + "" + ], "colors": [ "bg-kumo-base", "border-kumo-fill", @@ -3381,9 +3387,9 @@ } }, "examples": [ - ",\n id: \"bold\",\n tooltip: \"Bold\",\n onClick: () => setActive(active === \"bold\" ? undefined : \"bold\"),\n },\n {\n icon: ,\n id: \"italic\",\n tooltip: \"Italic\",\n onClick: () => setActive(active === \"italic\" ? undefined : \"italic\"),\n },\n ]}\n />", - ",\n id: \"bold\",\n tooltip: \"Bold\",\n onClick: () => setActive(active === \"bold\" ? undefined : \"bold\"),\n },\n {\n icon: ,\n id: \"italic\",\n tooltip: \"Italic\",\n onClick: () => setActive(active === \"italic\" ? undefined : \"italic\"),\n },\n ]}\n />", - ",\n id: \"bold\",\n tooltip: \"Bold\",\n onClick: () => setActive(active === \"bold\" ? undefined : \"bold\"),\n },\n {\n icon: ,\n id: \"italic\",\n tooltip: \"Italic\",\n onClick: () => setActive(active === \"italic\" ? undefined : \"italic\"),\n },\n ]}\n />" + ",\n id: \"bold\",\n tooltip: \"Bold\",\n onClick: () => {},\n },\n {\n icon: ,\n id: \"italic\",\n tooltip: \"Italic\",\n onClick: () => {},\n },\n ]}\n />", + ",\n id: \"bold\",\n tooltip: \"Bold\",\n onClick: () => {},\n },\n {\n icon: ,\n id: \"italic\",\n tooltip: \"Italic\",\n onClick: () => {},\n },\n ]}\n />", + ",\n id: \"bold\",\n tooltip: \"Bold\",\n onClick: () => {},\n },\n {\n icon: ,\n id: \"italic\",\n tooltip: \"Italic\",\n onClick: () => {},\n },\n ]}\n />" ], "colors": [ "bg-kumo-base", @@ -3455,7 +3461,7 @@ "", "", "", - "" + "" ], "colors": [ "bg-kumo-fill", @@ -4356,7 +4362,7 @@ "Table": { "name": "Table", "type": "component", - "description": "Table — semantic HTML table with styled rows, cells, and selection support. Compound component: `Table` (Root), `.Header`, `.Head`, `.Body`, `.Row`, `.Cell`, `.Footer`, `.CheckCell`, `.CheckHead`, `.ResizeHandle`.", + "description": "Table — semantic HTML table with styled rows, cells, and selection support. Compound component: `Table` (Root), `.Header`, `.Head`, `.Body`, `.Row`, `.Cell`, `.Footer`, `.CheckCell`, `.CheckHead`, `.ResizeHandle`, `.SortIcon`.", "importPath": "@cloudflare/kumo", "category": "Other", "props": { @@ -4399,6 +4405,7 @@ "\n \n \n \n \n Subject\n From\n Date\n \n \n \n {emailData.slice(0, 3).map((row) => (\n \n {row.subject}\n {row.from}\n {row.date}\n \n ))}\n \n
\n
\n
", "\n \n \n \n \n 0 && selectedIds.size < rows.length\n }\n onValueChange={toggleAll}\n aria-label=\"Select all rows\"\n />\n Subject\n From\n Date\n \n \n \n {rows.map((row) => (\n \n toggleRow(row.id)}\n aria-label={`Select ${row.subject}`}\n />\n {row.subject}\n {row.from}\n {row.date}\n \n ))}\n \n
\n
\n
", "\n \n \n \n \n \n \n \n \n \n Subject\n From\n Date\n \n \n \n {emailData.map((row) => (\n \n {row.subject}\n {row.from}\n {row.date}\n \n ))}\n \n
\n
\n
", + "\n \n \n \n {table.getAllColumns().map((col) => (\n \n ))}\n {/* Filler column — absorbs remaining space so fixed columns don't stretch */}\n \n \n \n {table.getHeaderGroups().map((headerGroup) => (\n \n {headerGroup.headers.map((header) => {\n const isSorted = header.column.getIsSorted();\n const canSort = header.column.getCanSort();\n return (\n \n
\n {header.isPlaceholder ? null : (\n <>\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n {canSort && }\n \n )}\n
\n \n \n );\n })}\n {/* Filler header cell matching the filler col */}\n \n
\n ))}\n
\n \n {table.getRowModel().rows.map((row) => (\n \n {row.getVisibleCells().map((cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n {/* Filler cell matching the filler col */}\n \n \n ))}\n \n
\n
\n
", "\n \n \n \n {\" \"}\n {/* Checkbox column - width handled by Table.CheckHead/CheckCell */}\n \n \n \n \n \n \n \n 0 && selectedIds.size < emailData.length\n }\n onValueChange={toggleAll}\n aria-label=\"Select all rows\"\n />\n Subject\n From\n Date\n \n \n \n \n {emailData.map((row) => (\n \n toggleRow(row.id)}\n aria-label={`Select ${row.subject}`}\n />\n \n
\n \n {row.subject}\n {row.tags && (\n
\n {row.tags.map((tag) => (\n {tag}\n ))}\n
\n )}\n
\n
\n \n {row.from}\n \n \n {row.date}\n \n \n \n \n \n \n }\n />\n \n View\n \n Edit\n \n \n \n Delete\n \n \n \n \n \n ))}\n
\n
\n
\n
" ], "colors": [ @@ -4455,6 +4462,11 @@ "name": "ResizeHandle", "description": "ResizeHandle sub-component", "props": {} + }, + "SortIcon": { + "name": "SortIcon", + "description": "SortIcon sub-component", + "props": {} } } }, diff --git a/packages/kumo/ai/component-registry.md b/packages/kumo/ai/component-registry.md index 7ad20a8937..2ea9427ed7 100644 --- a/packages/kumo/ai/component-registry.md +++ b/packages/kumo/ai/component-registry.md @@ -1077,6 +1077,27 @@ Props: - `lang`: CodeLang +**Examples:** + +```tsx + +``` + +```tsx + +``` + + --- ### Collapsible @@ -3330,20 +3351,20 @@ MenuBar — horizontal icon-button toolbar with keyboard arrow-key navigation. ```tsx , id: "bold", tooltip: "Bold", - onClick: () => setActive(active === "bold" ? undefined : "bold"), + onClick: () => {}, }, { icon: , id: "italic", tooltip: "Italic", - onClick: () => setActive(active === "italic" ? undefined : "italic"), + onClick: () => {}, }, ]} /> @@ -3403,7 +3424,7 @@ Progress bar showing a measured value within a known range (e.g. quota usage). ``` @@ -4630,7 +4651,7 @@ Props: ### Table -Table — semantic HTML table with styled rows, cells, and selection support. Compound component: `Table` (Root), `.Header`, `.Head`, `.Body`, `.Row`, `.Cell`, `.Footer`, `.CheckCell`, `.CheckHead`, `.ResizeHandle`. +Table — semantic HTML table with styled rows, cells, and selection support. Compound component: `Table` (Root), `.Header`, `.Head`, `.Body`, `.Row`, `.Cell`, `.Footer`, `.CheckCell`, `.CheckHead`, `.ResizeHandle`, `.SortIcon`. **Type:** component @@ -4695,6 +4716,10 @@ Footer sub-component ResizeHandle sub-component +#### Table.SortIcon + +SortIcon sub-component + **Examples:** @@ -4857,6 +4882,85 @@ ResizeHandle sub-component ``` +```tsx + + + + + {table.getAllColumns().map((col) => ( + + ))} + {/* Filler column — absorbs remaining space so fixed columns don't stretch */} + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const isSorted = header.column.getIsSorted(); + const canSort = header.column.getCanSort(); + return ( + +
+ {header.isPlaceholder ? null : ( + <> + {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + {canSort && } + + )} +
+ +
+ ); + })} + {/* Filler header cell matching the filler col */} + +
+ ))} +
+ + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + {/* Filler cell matching the filler col */} + + + ))} + +
+
+
+``` + ```tsx diff --git a/packages/kumo/src/components/table/index.ts b/packages/kumo/src/components/table/index.ts index cfe97d3d73..de72e7ddc2 100644 --- a/packages/kumo/src/components/table/index.ts +++ b/packages/kumo/src/components/table/index.ts @@ -4,4 +4,5 @@ export { KUMO_TABLE_VARIANTS, type KumoTableLayout, type KumoTableRowVariant, + type KumoTableSortDirection, } from "./table"; diff --git a/packages/kumo/src/components/table/table.tsx b/packages/kumo/src/components/table/table.tsx index 58590c9254..6813d9875d 100644 --- a/packages/kumo/src/components/table/table.tsx +++ b/packages/kumo/src/components/table/table.tsx @@ -1,4 +1,5 @@ -import { forwardRef } from "react"; +import { forwardRef, useRef } from "react"; +import { ArrowUpIcon } from "@phosphor-icons/react"; import { cn } from "../../utils"; import { Checkbox } from "../checkbox"; @@ -35,6 +36,60 @@ export const KUMO_TABLE_DEFAULT_VARIANTS = { export type KumoTableRowVariant = keyof typeof KUMO_TABLE_VARIANTS.variant; export type KumoTableLayout = keyof typeof KUMO_TABLE_VARIANTS.layout; +/** + * Sort direction for `Table.SortIcon`. + * + * Matches TanStack Table's `column.getIsSorted()` return type exactly: + * `false` when unsorted, `"asc"` or `"desc"` when sorted. + */ +export type KumoTableSortDirection = false | "asc" | "desc"; + +/** + * Sort direction icon for use inside `Table.Head`. + * + * Accepts the same type as TanStack Table's `column.getIsSorted()` — + * pass the value directly without any conversion. + * + * @example + * ```tsx + * // With TanStack Table + * const isSorted = header.column.getIsSorted(); // false | "asc" | "desc" + * + * + * // Standalone + * + * ``` + */ +const TableSortIcon = ({ + direction, + className, +}: { + /** + * - `"asc"` — ascending arrow (`↑`) + * - `"desc"` — descending arrow (`↓`) + * - `false` — renders nothing; column is sortable but not currently sorted + */ + direction: KumoTableSortDirection; + className?: string; +}) => { + if (!direction) return null; + + return ( + + + + ); +}; + +TableSortIcon.displayName = "Table.SortIcon"; + /** * Table root — applies layout, borders, padding, and header styles. * @@ -157,6 +212,7 @@ const TableResizeHandle = forwardRef< "cursor-col-resize touch-none select-none", // Prevent selection and touch events "absolute top-0 right-0", // Position the handle "m-0 bg-kumo-base p-0", // Override the stratus button styles + props.className, )} > @@ -253,7 +309,7 @@ TableCheckHead.displayName = "Table.CheckHead"; * Table — semantic HTML table with styled rows, cells, and selection support. * * Compound component: `Table` (Root), `.Header`, `.Head`, `.Body`, `.Row`, - * `.Cell`, `.Footer`, `.CheckCell`, `.CheckHead`, `.ResizeHandle`. + * `.Cell`, `.Footer`, `.CheckCell`, `.CheckHead`, `.ResizeHandle`, `.SortIcon`. * * @example * ```tsx @@ -285,4 +341,5 @@ export const Table = Object.assign(TableRoot, { CheckHead: TableCheckHead, Footer: TableFooter, ResizeHandle: TableResizeHandle, + SortIcon: TableSortIcon, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cba6af326f..fdef4f6782 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -270,6 +270,9 @@ importers: '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@4.1.17) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@types/turndown': specifier: 5.0.6 version: 5.0.6 @@ -695,37 +698,37 @@ packages: optional: true '@cloudflare/workerd-darwin-64@1.20260111.0': - resolution: {integrity: sha512-UGAjrGLev2/CMLZy7b+v1NIXA4Hupc/QJBFlJwMqldywMcJ/iEqvuUYYuVI2wZXuXeWkgmgFP87oFDQsg78YTQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260111.0.tgz} + resolution: {integrity: sha512-UGAjrGLev2/CMLZy7b+v1NIXA4Hupc/QJBFlJwMqldywMcJ/iEqvuUYYuVI2wZXuXeWkgmgFP87oFDQsg78YTQ==} engines: {node: '>=16'} cpu: [x64] os: [darwin] '@cloudflare/workerd-darwin-arm64@1.20260111.0': - resolution: {integrity: sha512-YFAZwidLCQVa6rKCCaiWrhA+eh87a7MUhyd9lat3KSbLBAGpYM+ORpyTXpi2Gjm3j6Mp1e/wtzcFTSeMIy2UqA==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260111.0.tgz} + resolution: {integrity: sha512-YFAZwidLCQVa6rKCCaiWrhA+eh87a7MUhyd9lat3KSbLBAGpYM+ORpyTXpi2Gjm3j6Mp1e/wtzcFTSeMIy2UqA==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] '@cloudflare/workerd-linux-64@1.20260111.0': - resolution: {integrity: sha512-zx1GW6FwfOBjCV7QUCRzGRkViUtn3Is/zaaVPmm57xyy9sjtInx6/SdeBr2Y45tx9AnOP1CnaOFFdmH1P7VIEg==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260111.0.tgz} + resolution: {integrity: sha512-zx1GW6FwfOBjCV7QUCRzGRkViUtn3Is/zaaVPmm57xyy9sjtInx6/SdeBr2Y45tx9AnOP1CnaOFFdmH1P7VIEg==} engines: {node: '>=16'} cpu: [x64] os: [linux] '@cloudflare/workerd-linux-arm64@1.20260111.0': - resolution: {integrity: sha512-wFVKxNvCyjRaAcgiSnJNJAmIos3p3Vv6Uhf4pFUZ9JIxr69GNlLWlm9SdCPvtwNFAjzSoDaKzDwjj5xqpuCS6Q==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260111.0.tgz} + resolution: {integrity: sha512-wFVKxNvCyjRaAcgiSnJNJAmIos3p3Vv6Uhf4pFUZ9JIxr69GNlLWlm9SdCPvtwNFAjzSoDaKzDwjj5xqpuCS6Q==} engines: {node: '>=16'} cpu: [arm64] os: [linux] '@cloudflare/workerd-windows-64@1.20260111.0': - resolution: {integrity: sha512-zWgd77L7OI1BxgBbG+2gybDahIMgPX5iNo6e3LqcEz1Xm3KfiqgnDyMBcxeQ7xDrj7fHUGAlc//QnKvDchuUoQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260111.0.tgz} + resolution: {integrity: sha512-zWgd77L7OI1BxgBbG+2gybDahIMgPX5iNo6e3LqcEz1Xm3KfiqgnDyMBcxeQ7xDrj7fHUGAlc//QnKvDchuUoQ==} engines: {node: '>=16'} cpu: [x64] os: [win32] '@cloudflare/workers-types@4.20260317.1': - resolution: {integrity: sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ==, tarball: https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260317.1.tgz} + resolution: {integrity: sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -2107,6 +2110,17 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -7697,6 +7711,14 @@ snapshots: tailwindcss: 4.1.17 vite: 7.2.4(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.2) + '@tanstack/react-table@8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@tanstack/table-core@8.21.3': {} + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0