mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 08:56:46 -04:00
refactor(ui-patterns): Standardise TanStack sort headers (#44212)
## What kind of change does this PR introduce? Component update. ## What is the current behaviour? TanStack tables in the repo are split between the shared `TableHeadSort` primitive and the older Studio-local `DataTableColumnHeader` helper, which makes the sorting UI and integration path inconsistent. If you were to just use `DataTableColumnHeader` in `ui-patterns/Table`, you’d get a very different visual result to the `TableHeadSort` UI you see in most other tables. ## What is the new behaviour? Adds a shared `TanStackTableHeadSort` adapter in `ui-patterns/Table`, backed by the existing `TableHeadSort` primitive, and switches the webhook table plus the design-system TanStack demo to that canonical path. `DataTableColumnHeader` stays as a deprecated wrapper for now, Studio gets a lint guard to block new imports of it, and the table docs now point TanStack tables at the shared adapter explicitly. ## To test Check out column sorting on the Platform Webhook endpoint deliveries table.
This commit is contained in:
@@ -144,6 +144,23 @@ The component displays:
|
||||
|
||||
<ComponentPreview name="table-sort" />
|
||||
|
||||
For TanStack tables, prefer the shared adapter instead of reimplementing the `TableHeadSort` bridge in each table.
|
||||
|
||||
```tsx
|
||||
import { TanStackTableHeadSort } from 'ui-patterns/Table'
|
||||
```
|
||||
|
||||
```tsx showLineNumbers
|
||||
const columns: ColumnDef<Row>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Name</TanStackTableHeadSort>,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This keeps TanStack tables aligned with the same `TableHeadSort` visual treatment and sorting cycle used by manual tables.
|
||||
|
||||
### Row icons
|
||||
|
||||
When adding icon columns to your table, use [Accessibility](../accessibility) markup by including a screen reader-only label in the corresponding Table Head using the `sr-only` class. This ensures that assistive technologies can properly identify the column's purpose. Remove these icon cells when loading or displaying zero results to maintain a clean and consistent table structure.
|
||||
|
||||
@@ -30,9 +30,9 @@ import {
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableHeadSort,
|
||||
TableRow,
|
||||
} from 'ui'
|
||||
import { TanStackTableHeadSort } from 'ui-patterns/Table'
|
||||
|
||||
const data: Payment[] = [
|
||||
{
|
||||
@@ -99,19 +99,23 @@ export const columns: ColumnDef<Payment>[] = [
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Status</TanStackTableHeadSort>,
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => <div className="capitalize">{row.getValue('status')}</div>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: 'Email',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Email</TanStackTableHeadSort>,
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => <div className="lowercase">{row.getValue('email')}</div>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
header: () => <div className="text-right">Amount</div>,
|
||||
header: ({ column }) => (
|
||||
<TanStackTableHeadSort column={column} className="justify-end">
|
||||
Amount
|
||||
</TanStackTableHeadSort>
|
||||
),
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const amount = parseFloat(row.getValue('amount'))
|
||||
@@ -164,33 +168,6 @@ export default function DataTableDemo() {
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
|
||||
// Convert TanStack Table's SortingState to the string format expected by TableHeadSort
|
||||
const getSortString = React.useMemo(() => {
|
||||
if (sorting.length === 0) return ''
|
||||
const sort = sorting[0]
|
||||
return `${sort.id}:${sort.desc ? 'desc' : 'asc'}`
|
||||
}, [sorting])
|
||||
|
||||
// Handle sort changes from TableHeadSort and convert to TanStack Table's SortingState
|
||||
const handleSortChange = React.useCallback(
|
||||
(column: string) => {
|
||||
const currentSort = sorting.find((s) => s.id === column)
|
||||
if (currentSort) {
|
||||
if (currentSort.desc) {
|
||||
// Cycle: desc -> remove sort
|
||||
setSorting([])
|
||||
} else {
|
||||
// Cycle: asc -> desc
|
||||
setSorting([{ id: column, desc: true }])
|
||||
}
|
||||
} else {
|
||||
// New column, start with asc
|
||||
setSorting([{ id: column, desc: false }])
|
||||
}
|
||||
},
|
||||
[sorting]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
@@ -254,11 +231,20 @@ export default function DataTableDemo() {
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const columnId = header.column.id
|
||||
const canSort = header.column.getCanSort()
|
||||
const sort = header.column.getIsSorted()
|
||||
|
||||
return (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
aria-sort={
|
||||
header.column.getCanSort()
|
||||
? sort === 'asc'
|
||||
? 'ascending'
|
||||
: sort === 'desc'
|
||||
? 'descending'
|
||||
: 'none'
|
||||
: undefined
|
||||
}
|
||||
className={
|
||||
columnId === 'amount'
|
||||
? 'text-right'
|
||||
@@ -267,18 +253,9 @@ export default function DataTableDemo() {
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{header.isPlaceholder ? null : canSort ? (
|
||||
<TableHeadSort
|
||||
column={columnId}
|
||||
currentSort={getSortString}
|
||||
onSortChange={handleSortChange}
|
||||
className={columnId === 'amount' ? 'justify-end' : undefined}
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHeadSort>
|
||||
) : (
|
||||
flexRender(header.column.columnDef.header, header.getContext())
|
||||
)}
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
|
||||
+22
-47
@@ -24,11 +24,11 @@ import {
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableHeadSort,
|
||||
TableRow,
|
||||
} from 'ui'
|
||||
import { TimestampInfo } from 'ui-patterns'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
import { TanStackTableHeadSort } from 'ui-patterns/Table'
|
||||
|
||||
import type { WebhookDelivery, WebhookEndpoint } from './PlatformWebhooks.types'
|
||||
import { statusBadgeVariant } from './PlatformWebhooksView.utils'
|
||||
@@ -59,39 +59,24 @@ const DELIVERIES_PAGE_SIZE = 5
|
||||
const DELIVERY_ACTIONS_COLUMN_ID = 'actions'
|
||||
const DEFAULT_DELIVERY_SORTING: SortingState = [{ id: 'attemptAt', desc: true }]
|
||||
|
||||
const getCurrentSort = (sorting: SortingState) => {
|
||||
if (sorting.length === 0) return ''
|
||||
|
||||
const [currentSort] = sorting
|
||||
return `${currentSort.id}:${currentSort.desc ? 'desc' : 'asc'}`
|
||||
}
|
||||
|
||||
const getAriaSort = (
|
||||
sorting: SortingState,
|
||||
columnId: string
|
||||
): 'ascending' | 'descending' | 'none' => {
|
||||
const currentSort = sorting.find((sort) => sort.id === columnId)
|
||||
|
||||
if (!currentSort) return 'none'
|
||||
return currentSort.desc ? 'descending' : 'ascending'
|
||||
}
|
||||
|
||||
const DELIVERY_COLUMNS: ColumnDef<WebhookDelivery>[] = [
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Status</TanStackTableHeadSort>,
|
||||
cell: ({ row }) => (
|
||||
<Badge variant={statusBadgeVariant[row.original.status]}>{row.original.status}</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'eventType',
|
||||
header: 'Event type',
|
||||
header: ({ column }) => (
|
||||
<TanStackTableHeadSort column={column}>Event type</TanStackTableHeadSort>
|
||||
),
|
||||
cell: ({ row }) => <code className="text-code-inline">{row.original.eventType}</code>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'responseCode',
|
||||
header: 'Response',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Response</TanStackTableHeadSort>,
|
||||
sortingFn: (rowA, rowB, columnId) => {
|
||||
const responseA = rowA.getValue<number | undefined>(columnId) ?? -1
|
||||
const responseB = rowB.getValue<number | undefined>(columnId) ?? -1
|
||||
@@ -110,7 +95,9 @@ const DELIVERY_COLUMNS: ColumnDef<WebhookDelivery>[] = [
|
||||
},
|
||||
{
|
||||
accessorKey: 'attemptAt',
|
||||
header: 'Attempted',
|
||||
header: ({ column }) => (
|
||||
<TanStackTableHeadSort column={column}>Attempted</TanStackTableHeadSort>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<TimestampInfo
|
||||
className="text-sm text-foreground-lighter"
|
||||
@@ -175,18 +162,6 @@ export const PlatformWebhooksEndpointDetails = ({
|
||||
pageIndex: 0,
|
||||
pageSize: DELIVERIES_PAGE_SIZE,
|
||||
})
|
||||
const currentSort = getCurrentSort(sorting)
|
||||
|
||||
const handleSortChange = (columnId: string) => {
|
||||
const currentColumnSort = sorting.find((sort) => sort.id === columnId)
|
||||
|
||||
if (!currentColumnSort) {
|
||||
setSorting([{ id: columnId, desc: false }])
|
||||
return
|
||||
}
|
||||
|
||||
setSorting([{ id: columnId, desc: !currentColumnSort.desc }])
|
||||
}
|
||||
|
||||
const table = useReactTable({
|
||||
data: filteredDeliveries,
|
||||
@@ -290,25 +265,25 @@ export const PlatformWebhooksEndpointDetails = ({
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const columnId = header.column.id
|
||||
const canSort = header.column.getCanSort()
|
||||
const sort = header.column.getIsSorted()
|
||||
|
||||
return (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
aria-sort={canSort ? getAriaSort(sorting, columnId) : undefined}
|
||||
aria-sort={
|
||||
header.column.getCanSort()
|
||||
? sort === 'asc'
|
||||
? 'ascending'
|
||||
: sort === 'desc'
|
||||
? 'descending'
|
||||
: 'none'
|
||||
: undefined
|
||||
}
|
||||
className={columnId === DELIVERY_ACTIONS_COLUMN_ID ? 'w-1' : ''}
|
||||
>
|
||||
{header.isPlaceholder ? null : canSort ? (
|
||||
<TableHeadSort
|
||||
column={columnId}
|
||||
currentSort={currentSort}
|
||||
onSortChange={handleSortChange}
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHeadSort>
|
||||
) : (
|
||||
flexRender(header.column.columnDef.header, header.getContext())
|
||||
)}
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ColumnDef } from '@tanstack/react-table'
|
||||
|
||||
import { DataTableColumnHeader } from 'components/ui/DataTable/DataTableColumn/DataTableColumnHeader'
|
||||
import { DataTableColumnLevelIndicator } from 'components/ui/DataTable/DataTableColumn/DataTableColumnLevelIndicator'
|
||||
import { DataTableColumnStatusCode } from 'components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { ColumnFilterSchema, ColumnSchema } from '../UnifiedLogs.schema'
|
||||
|
||||
import { STATUS_CODE_LABELS } from '../UnifiedLogs.constants'
|
||||
import { ColumnFilterSchema, ColumnSchema } from '../UnifiedLogs.schema'
|
||||
import { AuthUserHoverCard } from './AuthUserHoverCard'
|
||||
import { HoverCardTimestamp } from './HoverCardTimestamp'
|
||||
import { LogTypeIcon } from './LogTypeIcon'
|
||||
@@ -64,7 +63,7 @@ export function generateDynamicColumns(data: ColumnSchema[]): {
|
||||
// Date column - always visible
|
||||
{
|
||||
accessorKey: 'date',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Date" />,
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
const date = new Date(row.getValue<ColumnSchema['date']>('date'))
|
||||
return <HoverCardTimestamp date={date} />
|
||||
@@ -195,7 +194,7 @@ export function generateDynamicColumns(data: ColumnSchema[]): {
|
||||
// Event message column - controlled by columnVisibility
|
||||
{
|
||||
accessorKey: 'event_message',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Event message" />,
|
||||
header: 'Event message',
|
||||
cell: ({ row }) => {
|
||||
const value = row.getValue<ColumnSchema['event_message']>('event_message')
|
||||
const logCount = row.original.log_count
|
||||
|
||||
@@ -1,54 +1,23 @@
|
||||
import { type Column } from '@tanstack/react-table'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { TanStackTableHeadSort } from 'ui-patterns/Table'
|
||||
|
||||
import { Button, cn, type ButtonProps } from 'ui'
|
||||
|
||||
interface DataTableColumnHeaderProps<TData, TValue> extends ButtonProps {
|
||||
interface DataTableColumnHeaderProps<TData, TValue> {
|
||||
column: Column<TData, TValue>
|
||||
title: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `TanStackTableHeadSort` from `ui-patterns/Table` instead.
|
||||
*/
|
||||
export const DataTableColumnHeader = <TData, TValue>({
|
||||
column,
|
||||
title,
|
||||
className,
|
||||
...props
|
||||
}: DataTableColumnHeaderProps<TData, TValue>) => {
|
||||
if (!column.getCanSort()) {
|
||||
return <div className={cn(className)}>{title}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
column.toggleSorting(undefined)
|
||||
}}
|
||||
className={cn(
|
||||
'text-xs',
|
||||
'py-0 px-0 h-7 hover:bg-transparent flex gap-2 items-center justify-between w-full',
|
||||
className
|
||||
)}
|
||||
iconRight={
|
||||
<span className="flex flex-col">
|
||||
<ChevronUp
|
||||
className={cn(
|
||||
'-mb-1 hover:text-foreground-lighter',
|
||||
column.getIsSorted() === 'asc' ? 'text-foreground' : 'text-foreground-muted'
|
||||
)}
|
||||
/>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'-mt-1 hover:text-foreground-lighter',
|
||||
column.getIsSorted() === 'desc' ? 'text-foreground' : 'text-foreground-muted'
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<span>{title}</span>
|
||||
</Button>
|
||||
<TanStackTableHeadSort column={column} className={className}>
|
||||
{title}
|
||||
</TanStackTableHeadSort>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,17 @@ module.exports = defineConfig([
|
||||
'react/display-name': 'warn',
|
||||
'react/no-unstable-nested-components': 'warn',
|
||||
'react/jsx-key': 'error',
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'components/ui/DataTable/DataTableColumn/DataTableColumnHeader',
|
||||
message: 'Use TanStackTableHeadSort from ui-patterns/Table instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'barrel-files/avoid-re-export-all': 'error',
|
||||
'jsx-a11y/alt-text': 'warn',
|
||||
'jsx-a11y/role-has-required-aria-props': 'error',
|
||||
|
||||
@@ -666,6 +666,10 @@
|
||||
"import": "./src/TimestampInfo/index.tsx",
|
||||
"types": "./src/TimestampInfo/index.tsx"
|
||||
},
|
||||
"./Table": {
|
||||
"import": "./src/Table/index.ts",
|
||||
"types": "./src/Table/index.ts"
|
||||
},
|
||||
"./Toc": {
|
||||
"import": "./src/Toc/index.ts",
|
||||
"types": "./src/Toc/index.ts"
|
||||
@@ -769,6 +773,7 @@
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-visually-hidden": "^1.1.3",
|
||||
"@std/toml": "jsr:^1.0.11",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@supabase/sql-to-rest": "^0.1.6",
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"@vitest/coverage-v8": "^3.2.0",
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
type ColumnDef,
|
||||
type SortingState,
|
||||
} from '@tanstack/react-table'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useState } from 'react'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { TanStackTableHeadSort } from './TanStackTableHeadSort'
|
||||
|
||||
type Row = {
|
||||
name: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data: Row[] = [
|
||||
{ name: 'Bravo', amount: 200 },
|
||||
{ name: 'Alpha', amount: 100 },
|
||||
]
|
||||
|
||||
const columns: ColumnDef<Row>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Name</TanStackTableHeadSort>,
|
||||
cell: ({ row }) => row.getValue('name'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
enableSorting: false,
|
||||
header: ({ column }) => <TanStackTableHeadSort column={column}>Amount</TanStackTableHeadSort>,
|
||||
cell: ({ row }) => row.getValue('amount'),
|
||||
},
|
||||
]
|
||||
|
||||
const classNameColumns: ColumnDef<Row>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: ({ column }) => (
|
||||
<TanStackTableHeadSort column={column} className="justify-end">
|
||||
Name
|
||||
</TanStackTableHeadSort>
|
||||
),
|
||||
cell: ({ row }) => row.getValue('name'),
|
||||
},
|
||||
]
|
||||
|
||||
const TestTable = ({ tableColumns = columns }: { tableColumns?: ColumnDef<Row>[] }) => {
|
||||
const [sorting, setSorting] = useState<SortingState>([])
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns: tableColumns,
|
||||
state: { sorting },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
})
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
describe('TanStackTableHeadSort', () => {
|
||||
it('cycles unsorted, ascending, descending, and cleared', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
render(<TestTable />)
|
||||
|
||||
expect(screen.getAllByRole('row')[1]).toHaveTextContent('Bravo')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Name' }))
|
||||
expect(screen.getAllByRole('row')[1]).toHaveTextContent('Alpha')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Name' }))
|
||||
expect(screen.getAllByRole('row')[1]).toHaveTextContent('Bravo')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Name' }))
|
||||
expect(screen.getAllByRole('row')[1]).toHaveTextContent('Bravo')
|
||||
})
|
||||
|
||||
it('renders non-sortable columns as plain content', () => {
|
||||
render(<TestTable />)
|
||||
|
||||
expect(screen.getByText('Amount')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'Amount' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('passes className through to the rendered sort control', () => {
|
||||
render(<TestTable tableColumns={classNameColumns} />)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Name' })).toHaveClass('justify-end')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import { type Column } from '@tanstack/react-table'
|
||||
import type { ReactNode } from 'react'
|
||||
import { cn, TableHeadSort } from 'ui'
|
||||
|
||||
interface TanStackTableHeadSortProps<TData, TValue> {
|
||||
column: Column<TData, TValue>
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared TanStack adapter for the `TableHeadSort` primitive.
|
||||
* Prefer this in TanStack tables instead of wiring `TableHeadSort` manually.
|
||||
*/
|
||||
export const TanStackTableHeadSort = <TData, TValue>({
|
||||
column,
|
||||
children,
|
||||
className,
|
||||
}: TanStackTableHeadSortProps<TData, TValue>) => {
|
||||
if (!column.getCanSort()) {
|
||||
return <div className={cn(className)}>{children}</div>
|
||||
}
|
||||
|
||||
const sort = column.getIsSorted()
|
||||
const currentSort = sort ? `${column.id}:${sort}` : ''
|
||||
|
||||
const handleSortChange = () => {
|
||||
if (!sort) {
|
||||
column.toggleSorting(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (sort === 'asc') {
|
||||
column.toggleSorting(true)
|
||||
return
|
||||
}
|
||||
|
||||
column.clearSorting()
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHeadSort
|
||||
column={column.id}
|
||||
currentSort={currentSort}
|
||||
onSortChange={handleSortChange}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</TableHeadSort>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './TanStackTableHeadSort'
|
||||
Generated
+3
@@ -2607,6 +2607,9 @@ importers:
|
||||
'@supabase/supabase-js':
|
||||
specifier: 'catalog:'
|
||||
version: 2.100.0
|
||||
'@tanstack/react-table':
|
||||
specifier: ^8.21.3
|
||||
version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.4(supports-color@8.1.1)(vitest@3.2.4)
|
||||
|
||||
Reference in New Issue
Block a user