"use client" import * as React from "react" import type { ExtendedColumnSort, ExtendedSortingState, StringKeyOf, } from "@/types/table" import type { SortDirection, Table } from "@tanstack/react-table" import { ArrowDownUp, Check, ChevronsUpDown, GripVertical, Trash2, } from "lucide-react" import { useQueryState } from "nuqs" import { dataTableConfig } from "@/config/data-table" import { getSortingStateParser } from "@/lib/parsers" import { cn, toSentenceCase } from "@/lib/utils" import { useDebouncedCallback } from "@/hooks/use-debounced-callback" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Sortable, SortableDragHandle, SortableItem, } from "@/components/ui/sortable" interface DataTableSortListProps { table: Table debounceMs: number shallow?: boolean } export function DataTableSortList({ table, debounceMs, shallow, }: DataTableSortListProps) { const id = React.useId() const initialSorting = (table.initialState.sorting ?? []) as ExtendedSortingState const [sorting, setSorting] = useQueryState( "sort", getSortingStateParser(table.getRowModel().rows[0]?.original) .withDefault(initialSorting) .withOptions({ clearOnDefault: true, shallow, }) ) const uniqueSorting = React.useMemo( () => sorting.filter( (sort, index, self) => index === self.findIndex((t) => t.id === sort.id) ), [sorting] ) const debouncedSetSorting = useDebouncedCallback(setSorting, debounceMs) const sortableColumns = React.useMemo( () => table .getAllColumns() .filter( (column) => column.getCanSort() && !sorting.some((s) => s.id === column.id) ) .map((column) => ({ id: column.id, label: toSentenceCase(column.id), selected: false, })), [sorting, table] ) function addSort() { const firstAvailableColumn = sortableColumns.find( (column) => !sorting.some((s) => s.id === column.id) ) if (!firstAvailableColumn) return void setSorting([ ...sorting, { id: firstAvailableColumn.id as StringKeyOf, desc: false, }, ]) } function updateSort({ id, field, debounced = false, }: { id: string field: Partial> debounced?: boolean }) { const updateFunction = debounced ? debouncedSetSorting : setSorting updateFunction((prevSorting) => { if (!prevSorting) return prevSorting const updatedSorting = prevSorting.map((sort) => sort.id === id ? { ...sort, ...field } : sort ) return updatedSorting }) } function removeSort(id: string) { void setSorting((prevSorting) => prevSorting.filter((item) => item.id !== id) ) } return (
} > 0 ? "gap-3.5" : "gap-2" )} > {uniqueSorting.length > 0 ? (

Sort by

) : (

No sorting applied

Add sorting to organize your results.

)}
{uniqueSorting.map((sort) => { const sortId = `${id}-sort-${sort.id}` const fieldListboxId = `${sortId}-field-listbox` const fieldTriggerId = `${sortId}-field-trigger` const directionListboxId = `${sortId}-direction-listbox` return (
document.getElementById(fieldTriggerId)?.focus() } > No fields found. {sortableColumns.map((column) => ( { const newFieldTriggerId = `${id}-sort-${value}-field-trigger` updateSort({ id: sort.id, field: { id: value as StringKeyOf, }, }) requestAnimationFrame(() => { document .getElementById(newFieldTriggerId) ?.focus() }) }} > {column.label} ))}
) })}
{sorting.length > 0 ? ( ) : null}
) }