From 21f2e3c6f8817c8e4656311ff5019de331be9d93 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Feb 2026 10:53:18 +0000 Subject: [PATCH] 0.14.0 - Add sortableColumns and defaultSort props to Datatable component https://claude.ai/code/session_01VmTdoiVV81v1c9Ap4EcdDk --- package.json | 2 +- .../ui/datatable/Datatable.stories.tsx | 37 +++++++++++ src/components/ui/datatable/datatable.tsx | 66 +++++++++++++++++-- 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index da0ed1d..7ef3bc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@schemavaults/ui", - "version": "0.13.15", + "version": "0.14.0", "private": false, "license": "UNLICENSED", "description": "React.js UI components for SchemaVaults frontend applications", diff --git a/src/components/ui/datatable/Datatable.stories.tsx b/src/components/ui/datatable/Datatable.stories.tsx index 5743c7d..b9ea062 100644 --- a/src/components/ui/datatable/Datatable.stories.tsx +++ b/src/components/ui/datatable/Datatable.stories.tsx @@ -198,3 +198,40 @@ export const CustomPageSize: Story = { export const LargeDefaultPageSize: Story = { render: (): ReactElement => , }; + +function SortableColumnsDemo(): ReactElement { + return ( + + ); +} + +function DefaultSortDemo(): ReactElement { + return ( + + ); +} + +export const SortableColumns: Story = { + render: (): ReactElement => , +}; + +export const DefaultSort: Story = { + render: (): ReactElement => , +}; diff --git a/src/components/ui/datatable/datatable.tsx b/src/components/ui/datatable/datatable.tsx index a6cfbf8..114c611 100644 --- a/src/components/ui/datatable/datatable.tsx +++ b/src/components/ui/datatable/datatable.tsx @@ -15,6 +15,9 @@ import { type RowSelectionState, } from "@tanstack/react-table"; import { + ArrowDown, + ArrowUp, + ArrowUpDown, ChevronDown, ChevronLeft, ChevronRight, @@ -45,7 +48,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { type ReactElement, useState, type FC } from "react"; +import { type ReactElement, useMemo, useState, type FC } from "react"; export type { ColumnDef }; @@ -72,6 +75,10 @@ export interface DatatableProps { defaultPageSize?: number; /** Options shown in the rows-per-page selector. Defaults to [10, 20, 50, 100]. */ pageSizeOptions?: number[]; + /** Column IDs that should be sortable via clickable headers. If omitted, no columns are sortable. */ + sortableColumns?: string[]; + /** Initial sort state applied on mount. The column must also be listed in sortableColumns. */ + defaultSort?: { id: string; desc: boolean }; } export function Datatable({ @@ -84,8 +91,12 @@ export function Datatable({ HeaderButtons, defaultPageSize = 10, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS as unknown as number[], + sortableColumns, + defaultSort, }: DatatableProps): ReactElement { - const [sorting, setSorting] = useState([]); + const [sorting, setSorting] = useState( + defaultSort ? [defaultSort] : [], + ); const [columnFilters, setColumnFilters] = useState([]); const [globalFilter, setGlobalFilter] = useState(""); const [columnVisibility, setColumnVisibility] = useState( @@ -118,9 +129,30 @@ export function Datatable({ }); }; + // Set of column IDs that are sortable, for O(1) lookups. + const sortableSet = useMemo>( + () => new Set(sortableColumns ?? []), + [sortableColumns], + ); + + // Override enableSorting per column so only sortableColumns are interactive. + const columnsWithSorting = useMemo[]>( + () => + columns.map((col) => { + const colId = + (col as { accessorKey?: string }).accessorKey ?? + (col as { id?: string }).id; + return { + ...col, + enableSorting: colId ? sortableSet.has(colId) : false, + }; + }), + [columns, sortableSet], + ); + const table = useReactTable({ data, - columns: columns satisfies ColumnDef[], + columns: columnsWithSorting satisfies ColumnDef[], initialState: { pagination: { pageSize: defaultPageSize, @@ -221,12 +253,34 @@ export function Datatable({ {headerGroup.headers.map((header) => { return ( - {header.isPlaceholder - ? null - : flexRender( + {header.isPlaceholder ? null : header.column.getCanSort() ? ( + + ) : ( + flexRender( + header.column.columnDef.header, + header.getContext(), + ) + )} ); })}