feat: add onSortChange callback to DataTable for server-side sorting
All checks were successful
Build & Publish / publish (push) Successful in 1m21s
All checks were successful
Build & Publish / publish (push) Successful in 1m21s
When onSortChange is provided, DataTable operates in controlled mode: it renders sort indicators and fires the callback on header clicks, but skips client-side sorting — the caller provides pre-sorted data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ export function DataTable<T extends { id: string }>({
|
|||||||
rowAccent,
|
rowAccent,
|
||||||
expandedContent,
|
expandedContent,
|
||||||
flush = false,
|
flush = false,
|
||||||
|
onSortChange,
|
||||||
}: DataTableProps<T>) {
|
}: DataTableProps<T>) {
|
||||||
const [sortKey, setSortKey] = useState<string | null>(null)
|
const [sortKey, setSortKey] = useState<string | null>(null)
|
||||||
const [sortDir, setSortDir] = useState<SortDir>('asc')
|
const [sortDir, setSortDir] = useState<SortDir>('asc')
|
||||||
@@ -31,14 +32,16 @@ export function DataTable<T extends { id: string }>({
|
|||||||
const [pageSize, setPageSize] = useState(initialPageSize)
|
const [pageSize, setPageSize] = useState(initialPageSize)
|
||||||
const [expandedId, setExpandedId] = useState<string | null>(null)
|
const [expandedId, setExpandedId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
// When onSortChange is provided (controlled mode), skip client-side sorting
|
||||||
const sorted = useMemo(() => {
|
const sorted = useMemo(() => {
|
||||||
|
if (onSortChange) return data
|
||||||
if (!sortKey) return data
|
if (!sortKey) return data
|
||||||
return [...data].sort((a, b) => {
|
return [...data].sort((a, b) => {
|
||||||
const av = (a as Record<string, unknown>)[sortKey]
|
const av = (a as Record<string, unknown>)[sortKey]
|
||||||
const bv = (b as Record<string, unknown>)[sortKey]
|
const bv = (b as Record<string, unknown>)[sortKey]
|
||||||
return compareValues(av, bv, sortDir)
|
return compareValues(av, bv, sortDir)
|
||||||
})
|
})
|
||||||
}, [data, sortKey, sortDir])
|
}, [data, sortKey, sortDir, onSortChange])
|
||||||
|
|
||||||
const totalRows = sorted.length
|
const totalRows = sorted.length
|
||||||
const totalPages = Math.max(1, Math.ceil(totalRows / pageSize))
|
const totalPages = Math.max(1, Math.ceil(totalRows / pageSize))
|
||||||
@@ -52,13 +55,17 @@ export function DataTable<T extends { id: string }>({
|
|||||||
|
|
||||||
function handleHeaderClick(col: Column<T>) {
|
function handleHeaderClick(col: Column<T>) {
|
||||||
if (!sortable && !col.sortable) return
|
if (!sortable && !col.sortable) return
|
||||||
|
let newDir: SortDir
|
||||||
if (sortKey === col.key) {
|
if (sortKey === col.key) {
|
||||||
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))
|
newDir = sortDir === 'asc' ? 'desc' : 'asc'
|
||||||
|
setSortDir(newDir)
|
||||||
} else {
|
} else {
|
||||||
|
newDir = 'asc'
|
||||||
setSortKey(col.key)
|
setSortKey(col.key)
|
||||||
setSortDir('asc')
|
setSortDir(newDir)
|
||||||
}
|
}
|
||||||
setPage(1)
|
setPage(1)
|
||||||
|
onSortChange?.(col.key, newDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRowClick(row: T) {
|
function handleRowClick(row: T) {
|
||||||
|
|||||||
@@ -20,4 +20,8 @@ export interface DataTableProps<T extends { id: string }> {
|
|||||||
expandedContent?: (row: T) => ReactNode | null
|
expandedContent?: (row: T) => ReactNode | null
|
||||||
/** Strip border, radius, and shadow so the table sits flush inside a parent container. */
|
/** Strip border, radius, and shadow so the table sits flush inside a parent container. */
|
||||||
flush?: boolean
|
flush?: boolean
|
||||||
|
/** Controlled sort: called when the user clicks a sortable column header.
|
||||||
|
* When provided, the component skips client-side sorting — the caller is
|
||||||
|
* responsible for providing `data` in the desired order. */
|
||||||
|
onSortChange?: (key: string, dir: 'asc' | 'desc') => void
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user