@stackshift-ui/data-table 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@stackshift-ui/data-table",
3
+ "description": "Powerful table and datagrids built using TanStack Table.",
4
+ "version": "1.0.0-beta.1",
5
+ "private": false,
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "files": [
11
+ "dist/**",
12
+ "src"
13
+ ],
14
+ "author": "WebriQ <info@webriq.com>",
15
+ "devDependencies": {
16
+ "@testing-library/jest-dom": "^6.5.0",
17
+ "@testing-library/react": "^16.0.1",
18
+ "@testing-library/user-event": "^14.6.1",
19
+ "@types/node": "^22.7.0",
20
+ "@types/react": "^18.3.9",
21
+ "@types/react-dom": "^18.3.0",
22
+ "@vitejs/plugin-react": "^4.3.1",
23
+ "@vitest/coverage-v8": "^2.1.1",
24
+ "esbuild-plugin-rdi": "^0.0.0",
25
+ "esbuild-plugin-react18": "^0.2.5",
26
+ "esbuild-plugin-react18-css": "^0.0.4",
27
+ "jsdom": "^25.0.1",
28
+ "react": "^18.3.1",
29
+ "react-dom": "^18.3.1",
30
+ "tsup": "^8.3.0",
31
+ "typescript": "^5.6.2",
32
+ "vite-tsconfig-paths": "^5.0.1",
33
+ "vitest": "^2.1.1",
34
+ "@stackshift-ui/typescript-config": "6.0.10",
35
+ "@stackshift-ui/eslint-config": "6.0.10"
36
+ },
37
+ "dependencies": {
38
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
39
+ "@tanstack/react-table": "^8.21.3",
40
+ "classnames": "^2.5.1",
41
+ "lucide-react": "^0.468.0",
42
+ "@stackshift-ui/button": "6.1.0-beta.0",
43
+ "@stackshift-ui/select": "7.0.0-beta.0",
44
+ "@stackshift-ui/system": "6.1.0-beta.0",
45
+ "@stackshift-ui/scripts": "6.1.0-beta.0",
46
+ "@stackshift-ui/table": "1.0.0-beta.1",
47
+ "@stackshift-ui/dropdown-menu": "1.0.0-beta.1"
48
+ },
49
+ "peerDependencies": {
50
+ "@stackshift-ui/system": ">=6.1.0-beta.0",
51
+ "@types/react": "16.8 - 19",
52
+ "next": "10 - 14",
53
+ "react": "16.8 - 19",
54
+ "react-dom": "16.8 - 19"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "next": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "scripts": {
62
+ "build": "tsup && tsc -p tsconfig-build.json",
63
+ "clean": "rm -rf dist",
64
+ "dev": "tsup --watch && tsc -p tsconfig-build.json -w",
65
+ "typecheck": "tsc --noEmit",
66
+ "lint": "eslint src/",
67
+ "test": "vitest run --coverage"
68
+ }
69
+ }
@@ -0,0 +1,70 @@
1
+ import { Button } from "@stackshift-ui/button";
2
+ import {
3
+ DropdownMenu,
4
+ DropdownMenuContent,
5
+ DropdownMenuItem,
6
+ DropdownMenuSeparator,
7
+ DropdownMenuTrigger,
8
+ } from "@stackshift-ui/dropdown-menu";
9
+ import { cn, DefaultComponent, useStackShiftUIComponents } from "@stackshift-ui/system";
10
+ import { Column } from "@tanstack/react-table";
11
+ import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react";
12
+
13
+ const displayName = "DataTableColumnHeader";
14
+
15
+ interface DataTableColumnHeaderProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {
16
+ column: Column<TData, TValue>;
17
+ title: string;
18
+ }
19
+
20
+ export function DataTableColumnHeader<TData, TValue>({
21
+ column,
22
+ title,
23
+ className,
24
+ ...props
25
+ }: DataTableColumnHeaderProps<TData, TValue>) {
26
+ const { [displayName]: Component = DefaultComponent } = useStackShiftUIComponents();
27
+
28
+ if (!column.getCanSort()) {
29
+ return (
30
+ <Component className={cn(className)} {...props}>
31
+ {title}
32
+ </Component>
33
+ );
34
+ }
35
+
36
+ return (
37
+ <Component className={cn("flex items-center gap-2", className)} {...props}>
38
+ <DropdownMenu>
39
+ <DropdownMenuTrigger asChild>
40
+ <Button variant="ghost" size="sm" className="data-[state=open]:bg-accent -ml-3 h-8">
41
+ <span>{title}</span>
42
+ {column.getIsSorted() === "desc" ? (
43
+ <ArrowDown />
44
+ ) : column.getIsSorted() === "asc" ? (
45
+ <ArrowUp />
46
+ ) : (
47
+ <ChevronsUpDown />
48
+ )}
49
+ </Button>
50
+ </DropdownMenuTrigger>
51
+ <DropdownMenuContent align="start">
52
+ <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
53
+ <ArrowUp />
54
+ Asc
55
+ </DropdownMenuItem>
56
+ <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
57
+ <ArrowDown />
58
+ Desc
59
+ </DropdownMenuItem>
60
+ <DropdownMenuSeparator />
61
+ <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
62
+ <EyeOff />
63
+ Hide
64
+ </DropdownMenuItem>
65
+ </DropdownMenuContent>
66
+ </DropdownMenu>
67
+ </Component>
68
+ );
69
+ }
70
+ DataTableColumnHeader.displayName = displayName;
@@ -0,0 +1,94 @@
1
+ import { DefaultComponent, useStackShiftUIComponents } from "@stackshift-ui/system";
2
+ import { Table } from "@tanstack/react-table";
3
+ import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
4
+
5
+ import { Button } from "@stackshift-ui/button";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from "@stackshift-ui/select";
13
+
14
+ const displayName = "DataTablePagination";
15
+
16
+ interface DataTablePaginationProps<TData> {
17
+ table: Table<TData>;
18
+ }
19
+
20
+ export function DataTablePagination<TData>({ table, ...props }: DataTablePaginationProps<TData>) {
21
+ const { [displayName]: Component = DefaultComponent } = useStackShiftUIComponents();
22
+
23
+ return (
24
+ <Component className="flex items-center justify-between px-2" {...props}>
25
+ <div className="text-muted-foreground flex-1 text-sm">
26
+ {table.getFilteredSelectedRowModel().rows.length} of{" "}
27
+ {table.getFilteredRowModel().rows.length} row(s) selected.
28
+ </div>
29
+ <div className="flex items-center space-x-6 lg:space-x-8">
30
+ <div className="flex items-center space-x-2">
31
+ <p className="text-sm font-medium">Rows per page</p>
32
+ <Select
33
+ value={`${table.getState().pagination.pageSize}`}
34
+ onValueChange={value => {
35
+ table.setPageSize(Number(value));
36
+ }}>
37
+ <SelectTrigger className="h-8 w-[70px]">
38
+ <SelectValue placeholder={table.getState().pagination.pageSize} />
39
+ </SelectTrigger>
40
+ <SelectContent side="top">
41
+ {[10, 20, 25, 30, 40, 50].map(pageSize => (
42
+ <SelectItem key={pageSize} value={`${pageSize}`}>
43
+ {pageSize}
44
+ </SelectItem>
45
+ ))}
46
+ </SelectContent>
47
+ </Select>
48
+ </div>
49
+ <div className="flex w-[100px] items-center justify-center text-sm font-medium">
50
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
51
+ </div>
52
+ <div className="flex items-center space-x-2">
53
+ <Button
54
+ variant="outline"
55
+ size="icon"
56
+ className="hidden size-8 lg:flex"
57
+ onClick={() => table.setPageIndex(0)}
58
+ disabled={!table.getCanPreviousPage()}>
59
+ <span className="sr-only">Go to first page</span>
60
+ <ChevronsLeft />
61
+ </Button>
62
+ <Button
63
+ variant="outline"
64
+ size="icon"
65
+ className="size-8"
66
+ onClick={() => table.previousPage()}
67
+ disabled={!table.getCanPreviousPage()}>
68
+ <span className="sr-only">Go to previous page</span>
69
+ <ChevronLeft />
70
+ </Button>
71
+ <Button
72
+ variant="outline"
73
+ size="icon"
74
+ className="size-8"
75
+ onClick={() => table.nextPage()}
76
+ disabled={!table.getCanNextPage()}>
77
+ <span className="sr-only">Go to next page</span>
78
+ <ChevronRight />
79
+ </Button>
80
+ <Button
81
+ variant="outline"
82
+ size="icon"
83
+ className="hidden size-8 lg:flex"
84
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
85
+ disabled={!table.getCanNextPage()}>
86
+ <span className="sr-only">Go to last page</span>
87
+ <ChevronsRight />
88
+ </Button>
89
+ </div>
90
+ </div>
91
+ </Component>
92
+ );
93
+ }
94
+ DataTablePagination.displayName = displayName;
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
4
+ import { Table } from "@tanstack/react-table";
5
+ import { Settings2 } from "lucide-react";
6
+
7
+ import { Button } from "@stackshift-ui/button";
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuCheckboxItem,
11
+ DropdownMenuContent,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ } from "@stackshift-ui/dropdown-menu";
15
+ import { DefaultComponent, useStackShiftUIComponents } from "@stackshift-ui/system";
16
+
17
+ const displayName = "DataTableViewOptions";
18
+
19
+ export function DataTableViewOptions<TData>({ table, ...props }: { table: Table<TData> }) {
20
+ const { [displayName]: Component = DefaultComponent } = useStackShiftUIComponents();
21
+
22
+ return (
23
+ <Component {...props}>
24
+ <DropdownMenu>
25
+ <DropdownMenuTrigger asChild>
26
+ <Button variant="outline" size="sm" className="ml-auto hidden h-8 lg:flex">
27
+ <Settings2 />
28
+ View
29
+ </Button>
30
+ </DropdownMenuTrigger>
31
+ <DropdownMenuContent align="end" className="w-[150px]">
32
+ <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
33
+ <DropdownMenuSeparator />
34
+ {table
35
+ .getAllColumns()
36
+ .filter(column => typeof column.accessorFn !== "undefined" && column.getCanHide())
37
+ .map(column => {
38
+ return (
39
+ <DropdownMenuCheckboxItem
40
+ key={column.id}
41
+ className="capitalize"
42
+ checked={column.getIsVisible()}
43
+ onCheckedChange={value => column.toggleVisibility(!!value)}>
44
+ {column.id}
45
+ </DropdownMenuCheckboxItem>
46
+ );
47
+ })}
48
+ </DropdownMenuContent>
49
+ </DropdownMenu>
50
+ </Component>
51
+ );
52
+ }
53
+ DataTableViewOptions.displayName = displayName;
@@ -0,0 +1,79 @@
1
+ import { cleanup, render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { afterEach, describe, test } from "vitest";
4
+ import { ColumnDef, DataTable } from "./data-table";
5
+
6
+ // TODO: add more tests
7
+ describe.concurrent("data-table", () => {
8
+ afterEach(cleanup);
9
+
10
+ test("Dummy test - test if renders without errors", ({ expect }) => {
11
+ const columns: ColumnDef<{ name: string }>[] = [
12
+ {
13
+ accessorKey: "name",
14
+ header: "Name",
15
+ },
16
+ ];
17
+
18
+ const data = [
19
+ {
20
+ name: "John Doe",
21
+ },
22
+ ];
23
+
24
+ render(<DataTable data-testid="data-table" columns={columns} data={data} />);
25
+ expect(screen.getByText("John Doe")).not.toBeNull();
26
+ });
27
+
28
+ test("Common: Data Table - test if renders with correct number of rows", ({ expect }) => {
29
+ const columns: ColumnDef<{ name: string }>[] = [
30
+ {
31
+ accessorKey: "name",
32
+ header: "Name",
33
+ },
34
+ ];
35
+
36
+ const data = [
37
+ {
38
+ name: "John Doe",
39
+ },
40
+ {
41
+ name: "Jane Doe",
42
+ },
43
+ ];
44
+
45
+ render(<DataTable data-testid="data-table-2" columns={columns} data={data} />);
46
+
47
+ const table = screen.getByTestId("data-table-2");
48
+ const rows = table.querySelectorAll("tr");
49
+
50
+ expect(rows).toHaveLength(3);
51
+ });
52
+
53
+ test("Common: Data Table - test if renders with correct number of columns", ({ expect }) => {
54
+ const columns: ColumnDef<{ name: string; email: string }>[] = [
55
+ {
56
+ accessorKey: "name",
57
+ header: "Name",
58
+ },
59
+ {
60
+ accessorKey: "email",
61
+ header: "Email",
62
+ },
63
+ ];
64
+
65
+ const data = [
66
+ {
67
+ name: "John Doe",
68
+ email: "john@example.com",
69
+ },
70
+ ];
71
+
72
+ render(<DataTable data-testid="data-table-3" columns={columns} data={data} />);
73
+
74
+ const table = screen.getByTestId("data-table-3");
75
+ const headers = table.querySelectorAll("th");
76
+
77
+ expect(headers).toHaveLength(2);
78
+ });
79
+ });
@@ -0,0 +1,112 @@
1
+ "use client";
2
+
3
+ import { DefaultComponent, useStackShiftUIComponents } from "@stackshift-ui/system";
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from "@stackshift-ui/table";
12
+ import {
13
+ ColumnDef,
14
+ ColumnFiltersState,
15
+ flexRender,
16
+ getCoreRowModel,
17
+ getFilteredRowModel,
18
+ getPaginationRowModel,
19
+ getSortedRowModel,
20
+ SortingState,
21
+ useReactTable,
22
+ } from "@tanstack/react-table";
23
+ import { useState } from "react";
24
+ import { DataTablePagination } from "./data-table-pagination";
25
+
26
+ const displayName = "DataTable";
27
+
28
+ interface DataTableProps<TData, TValue> {
29
+ columns: ColumnDef<TData, TValue>[];
30
+ data: TData[];
31
+ }
32
+
33
+ export function DataTable<TData, TValue>({
34
+ columns,
35
+ data,
36
+ ...props
37
+ }: DataTableProps<TData, TValue>) {
38
+ const { [displayName]: Component = DefaultComponent } = useStackShiftUIComponents();
39
+
40
+ const [sorting, setSorting] = useState<SortingState>([]);
41
+ const [rowSelection, setRowSelection] = useState({});
42
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
43
+
44
+ const table = useReactTable({
45
+ data,
46
+ columns,
47
+ getCoreRowModel: getCoreRowModel(),
48
+ getPaginationRowModel: getPaginationRowModel(),
49
+ onSortingChange: setSorting,
50
+ getSortedRowModel: getSortedRowModel(),
51
+ onRowSelectionChange: setRowSelection,
52
+ onColumnFiltersChange: setColumnFilters,
53
+ getFilteredRowModel: getFilteredRowModel(),
54
+ state: {
55
+ sorting,
56
+ rowSelection,
57
+ columnFilters,
58
+ },
59
+ });
60
+
61
+ return (
62
+ <Component className="relative w-full h-full flex flex-col gap-2" {...props}>
63
+ <div className="rounded-md border">
64
+ <Table>
65
+ <TableHeader>
66
+ {table.getHeaderGroups().map(headerGroup => (
67
+ <TableRow key={headerGroup.id}>
68
+ {headerGroup.headers.map(header => {
69
+ return (
70
+ <TableHead key={header.id}>
71
+ {header.isPlaceholder
72
+ ? null
73
+ : flexRender(header.column.columnDef.header, header.getContext())}
74
+ </TableHead>
75
+ );
76
+ })}
77
+ </TableRow>
78
+ ))}
79
+ </TableHeader>
80
+ <TableBody>
81
+ {table.getRowModel().rows?.length ? (
82
+ table.getRowModel().rows.map(row => (
83
+ <TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
84
+ {row.getVisibleCells().map(cell => (
85
+ <TableCell key={cell.id}>
86
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
87
+ </TableCell>
88
+ ))}
89
+ </TableRow>
90
+ ))
91
+ ) : (
92
+ <TableRow>
93
+ <TableCell colSpan={columns.length} className="h-24 text-center">
94
+ No results.
95
+ </TableCell>
96
+ </TableRow>
97
+ )}
98
+ </TableBody>
99
+ </Table>
100
+ </div>
101
+ <div className="w-full h-fit py-10">
102
+ <DataTablePagination table={table} />
103
+ </div>
104
+ </Component>
105
+ );
106
+ }
107
+ DataTable.displayName = displayName;
108
+
109
+ export { type ColumnDef } from "@tanstack/react-table";
110
+ export { DataTableColumnHeader } from "./data-table-column-header";
111
+ export { DataTablePagination } from "./data-table-pagination";
112
+ export { DataTableViewOptions } from "./data-table-view-options";
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ "use client";
2
+
3
+ // component exports
4
+ export * from "./data-table";
@@ -0,0 +1,4 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ export { };
4
+