From 1a2241c40e10193c5ff7008a7b7b36cc1d855d96 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Tue, 25 Mar 2025 15:55:45 +0900 Subject: initial commit --- components/data-table/data-table-sort-list.tsx | 370 +++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 components/data-table/data-table-sort-list.tsx (limited to 'components/data-table/data-table-sort-list.tsx') diff --git a/components/data-table/data-table-sort-list.tsx b/components/data-table/data-table-sort-list.tsx new file mode 100644 index 00000000..686545fc --- /dev/null +++ b/components/data-table/data-table-sort-list.tsx @@ -0,0 +1,370 @@ +"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} +
+
+
+ + ) +} -- cgit v1.2.3