diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index d7db0d2..364e78e 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -3,16 +3,23 @@ import usersImport from "./users.json"; import { ColumnDef, + ColumnFiltersState, + GlobalFilterTableState, + Row, createColumnHelper, flexRender, getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + sortingFns, useReactTable, } from "@tanstack/react-table"; -import { useState } from "react"; +import { ChangeEvent, useState } from "react"; import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon } from "@heroicons/react/24/solid"; +import { compareItems, rankItem } from "@tanstack/match-sorter-utils"; const usersExample = usersImport as unknown as User[]; @@ -27,6 +34,31 @@ type User = { group?: string; }; +const fuzzyFilter = (row: Row, columnId: string, value: any, addMeta: (meta: any) => void) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the ranking info + addMeta(itemRank) + + // Return if the item should be filtered in/out + return itemRank.passed +} + +const fuzzySort = (rowA, rowB, columnId) => { + let dir = 0 + + // Only sort by rank if the column has ranking information + if (rowA.columnFiltersMeta[columnId]) { + dir = compareItems( + rowA.columnFiltersMeta[columnId]!, + rowB.columnFiltersMeta[columnId]! + ) + } + // Provide an alphanumeric fallback for when the item ranks are equal + return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir +} + export const Table = () => { const columnHelper = createColumnHelper(); @@ -39,6 +71,7 @@ export const Table = () => { columnHelper.accessor("username", { header: () => <> Username, cell: (info) => , + sortingFn: fuzzySort, }), columnHelper.accessor("role", { header: () => <> Role, @@ -55,17 +88,47 @@ export const Table = () => { ]; const [data, setData] = useState([...usersExample]); + // const [globalFilter, setGlobalFilter] = useState(); + + // Searching + const [query, setQuery] = useState(""); + const handleSearchChange = (e: ChangeEvent) => { + const target = e.target as HTMLInputElement; + setQuery(String(target.value)); + console.log(query); + + // setColumnFilters([ + // { + // id: "username", + // value: query + // } + // ]) + } + + // TODO: Filtering + // TODO: Sorting const table = useReactTable({ columns, data, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + globalFilter: query, + }, + onGlobalFilterChange: setQuery, + globalFilterFn: fuzzyFilter, getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + // onColumnFiltersChange: setColumnFilters, }); return (
- +
diff --git a/compass/components/Table/TableAction.tsx b/compass/components/Table/TableAction.tsx index f2a72a0..47b3680 100644 --- a/compass/components/Table/TableAction.tsx +++ b/compass/components/Table/TableAction.tsx @@ -1,9 +1,16 @@ +/** The actions (Filter, Sort, Search) at the top of the table. */ import { MagnifyingGlassIcon } from "@heroicons/react/24/solid"; -import { useRef, useState } from "react"; +import { ChangeEventHandler, Dispatch, FunctionComponent, SetStateAction, useRef, useState } from "react"; -export const TableAction = () => { +type TableActionProps = { + query: string + handleChange: ChangeEventHandler +} + +export const TableAction: FunctionComponent = ({query, handleChange}) => { const searchInput = useRef(null); const [searchActive, setSearchActive] = useState(false); + const activateSearch = () => { setSearchActive(true); if (searchInput.current === null) { return; } @@ -30,7 +37,10 @@ export const TableAction = () => { className={"outline-none transition-all duration-300 " + (searchActive ? "w-48" : "w-0")} type="text" name="search" - placeholder="Type to search..." /> + placeholder="Type to search..." + value={query} + onChange={handleChange} + /> ); }; diff --git a/compass/package-lock.json b/compass/package-lock.json index 1c26973..f37a923 100644 --- a/compass/package-lock.json +++ b/compass/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@heroicons/react": "^2.1.1", + "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.15.0", "next": "13.5.6", "react": "^18", @@ -402,6 +403,21 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.15.1", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz", + "integrity": "sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-table": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.15.0.tgz", @@ -3556,6 +3572,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -4395,4 +4416,3 @@ } } } - diff --git a/compass/package.json b/compass/package.json index 4036d60..10ba9d0 100644 --- a/compass/package.json +++ b/compass/package.json @@ -1,4 +1,3 @@ - { "name": "compass", "version": "0.1.0", @@ -11,6 +10,7 @@ }, "dependencies": { "@heroicons/react": "^2.1.1", + "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.15.0", "next": "13.5.6", "react": "^18",