87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ));
97 | TableCell.displayName = "TableCell";
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ));
109 | TableCaption.displayName = "TableCaption";
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | };
121 |
--------------------------------------------------------------------------------
/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
5 | import { VariantProps } from "class-variance-authority";
6 |
7 | import { cn } from "@/lib/utils";
8 | import { toggleVariants } from "@/components/ui/toggle";
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default",
15 | });
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ));
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext);
41 |
42 | return (
43 |
54 | {children}
55 |
56 | );
57 | });
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
60 |
61 | export { ToggleGroup, ToggleGroupItem };
62 |
--------------------------------------------------------------------------------
/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TogglePrimitive from "@radix-ui/react-toggle";
5 | import { cva, type VariantProps } from "class-variance-authority";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
17 | },
18 | size: {
19 | default: "h-9 px-3",
20 | sm: "h-8 px-2",
21 | lg: "h-10 px-3",
22 | },
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default",
27 | },
28 | }
29 | );
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ));
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName;
44 |
45 | export { Toggle, toggleVariants };
46 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider;
9 |
10 | const Tooltip = TooltipPrimitive.Root;
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger;
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ));
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
31 |
--------------------------------------------------------------------------------
/hooks/useDebounce.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | export function useDebounce(value: T, delay?: number): T {
4 | const [debouncedValue, setDebouncedValue] = React.useState(value);
5 |
6 | React.useEffect(() => {
7 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
8 |
9 | return () => {
10 | clearTimeout(timer);
11 | };
12 | }, [value, delay]);
13 |
14 | return debouncedValue;
15 | }
--------------------------------------------------------------------------------
/interface/IDataTable.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import {
3 | Cell,
4 | ColumnDef,
5 | type Table
6 | } from "@tanstack/react-table";
7 | import {
8 | TDataTableAddDataProps,
9 | TDataTableContextMenuProps,
10 | TDataTableDataValidation,
11 | TDataTableEditDataProps,
12 | TDataTableExportProps
13 | } from "@/@types";
14 | import {VirtualItem, Virtualizer} from "@tanstack/virtual-core";
15 |
16 | export interface IAdvancedDataTable {
17 | id: string;
18 | columns: ColumnDef[];
19 | data: T[];
20 | exportProps?: TDataTableExportProps;
21 | actionProps?: {
22 | onDelete?: (rows: T[])=> void;
23 | onUserExport?: (rows: T[])=> void;
24 | };
25 | addDataProps?: TDataTableAddDataProps;
26 | editDataProps?: TDataTableEditDataProps;
27 | contextMenuProps?: TDataTableContextMenuProps;
28 | onRowClick?: (prop: T) => void;
29 | isLoading?: boolean;
30 | dataValidationProps?: TDataTableDataValidation[];
31 | }
32 |
33 | export interface DataTablePaginationProps {
34 | table: Table
35 | pageSizeOptions?: number[]
36 | }
37 |
38 | export interface IDataTableFloatingBar {
39 | table: Table;
40 | onUserExport?: (rows: T[]) => void;
41 | onDelete?: (rows: T[]) => void;
42 | }
43 |
44 | export interface IDataTableExport {
45 | table:Table;
46 | onUserExport?: (data: T[])=> void;
47 | }
48 |
49 | export interface DataTableViewOptionsProps {
50 | table: Table
51 | }
52 |
53 | export interface IDataTableCellEdit {
54 | cell: Cell;
55 | }
56 |
57 | export interface IDataTableBody {
58 | table: Table;
59 | columnOrder: string[];
60 | onClick?: (prop: T) => void;
61 | rowVirtualizer: Virtualizer;
62 | virtualPaddingLeft: number | undefined;
63 | virtualPaddingRight: number | undefined;
64 | virtualColumns: VirtualItem[];
65 | }
66 |
--------------------------------------------------------------------------------
/lib/columns.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {Column} from "@tanstack/react-table";
4 | import {CSSProperties} from "react";
5 |
6 | export function getCommonPinningStyles(column: Column): CSSProperties {
7 | const isPinned = column.getIsPinned();
8 | const isLastLeftPinnedColumn =
9 | isPinned === "left" && column.getIsLastColumn("left");
10 | const isFirstRightPinnedColumn =
11 | isPinned === "right" && column.getIsFirstColumn("right");
12 |
13 | return {
14 | boxShadow: isLastLeftPinnedColumn
15 | ? "-4px 0 4px -4px gray inset"
16 | : isFirstRightPinnedColumn
17 | ? "4px 0 4px -4px gray inset"
18 | : undefined,
19 | left: isPinned === "left" ? `${column.getStart("left")}px` : undefined,
20 | right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined,
21 | opacity: isPinned ? 0.95 : 1,
22 | position: isPinned ? "sticky" : "relative",
23 | background: "white",
24 | width: column.getSize(),
25 | zIndex: isPinned ? 1 : 0,
26 | };
27 | }
--------------------------------------------------------------------------------
/lib/exportExcel.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {Column} from "@tanstack/react-table";
4 | import {utils, writeFile} from "xlsx";
5 |
6 | export function exportExcelData(rows: T[], columns: Column[], excludeColumns: string[]): T[] {
7 | const data: T[] = [];
8 | const naming: { [k: string]: any } = {};
9 | for (let i = 0; i < columns.length; i++) {
10 | const col = columns[i];
11 | if (excludeColumns.length > 0 && excludeColumns.includes(col.id)) {
12 | continue;
13 | }
14 | naming[col.id] = col.columnDef.header;
15 | }
16 | rows.forEach(item=>{
17 | const obj = item as {[K:string]: any};
18 | const tempObj: { [k: string]: any } = {};
19 | for (const objKey in obj) {
20 | if (Object.hasOwn(naming,objKey)) {
21 | tempObj[naming[objKey]] = obj[objKey];
22 | }
23 | }
24 | data.push(tempObj as T);
25 | });
26 | return data;
27 | }
28 |
29 | export function exportExcel(data: T[], exportFilename: string) {
30 | try {
31 | const wb = utils.book_new();
32 | const ws = utils.json_to_sheet(data);
33 | utils.book_append_sheet(wb, ws, "Exported");
34 | writeFile(wb, exportFilename.concat(".xlsx"));
35 | } catch (ex: any) {
36 | alert(ex.message);
37 | }
38 | }
--------------------------------------------------------------------------------
/lib/makeData.ts:
--------------------------------------------------------------------------------
1 | import { faker } from "@faker-js/faker";
2 |
3 | export type Person = {
4 | firstName: string
5 | lastName: string
6 | gender: string
7 | jobType: string
8 | address: string
9 | locality: string
10 | age: number
11 | visits: number
12 | lastUpdate: Date
13 | status: "relationship" | "complicated" | "single"
14 | subRows?: Person[]
15 | }
16 |
17 | const range = (len: number) => {
18 | const arr: number[] = [];
19 | for (let i = 0; i < len; i++) {
20 | arr.push(i);
21 | }
22 | return arr;
23 | };
24 |
25 | const newPerson = (): Person => {
26 | return {
27 | firstName: faker.person.firstName(),
28 | lastName: faker.person.lastName(),
29 | jobType: faker.person.jobType(),
30 | gender: faker.helpers.shuffle([
31 | "male",
32 | "female"
33 | ])[0]!,
34 | address: faker.location.streetAddress({useFullAddress: true}),
35 | locality: faker.location.country(),
36 | age: faker.number.int(40),
37 | visits: faker.number.int(1000),
38 | lastUpdate: faker.date.future(),
39 | status: faker.helpers.shuffle([
40 | "relationship",
41 | "complicated",
42 | "single",
43 | ])[0]!,
44 | };
45 | };
46 |
47 | export function makeData(...lens: number[]) {
48 | const makeDataLevel = (depth = 0): Person[] => {
49 | const len = lens[depth]!;
50 | return range(len).map((d): Person => {
51 | return {
52 | ...newPerson(),
53 | subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
54 | };
55 | });
56 | };
57 |
58 | return makeDataLevel();
59 | }
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 | import {FilterFn, SortingFn, sortingFns} from "@tanstack/table-core";
4 | import {compareItems, rankItem} from "@tanstack/match-sorter-utils";
5 |
6 | export function cn(...inputs: ClassValue[]) {
7 | return twMerge(clsx(inputs));
8 | }
9 |
10 | export const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => {
11 | // Rank the item
12 | const itemRank = rankItem(row.getValue(columnId), value);
13 |
14 | // Store the itemRank info
15 | addMeta({
16 | itemRank,
17 | });
18 |
19 | // Return if the item should be filtered in/out
20 | return itemRank.passed;
21 | };
22 |
23 | export const fuzzySort: SortingFn = (rowA, rowB, columnId) => {
24 | let dir = 0;
25 |
26 | // Only sort by rank if the column has ranking information
27 | if (rowA.columnFiltersMeta[columnId]) {
28 | dir = compareItems(
29 | rowA.columnFiltersMeta[columnId]?.itemRank!,
30 | rowB.columnFiltersMeta[columnId]?.itemRank!
31 | );
32 | }
33 |
34 | // Provide an alphanumeric fallback for when the item ranks are equal
35 | return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir;
36 | };
37 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | eslint: {
4 | ignoreDuringBuilds: true
5 | }
6 | };
7 |
8 | export default nextConfig;
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "advance-data-table-example",
3 | "version": "0.0.10",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@dnd-kit/core": "^6.1.0",
13 | "@dnd-kit/modifiers": "^7.0.0",
14 | "@dnd-kit/sortable": "^8.0.0",
15 | "@dnd-kit/utilities": "^3.2.2",
16 | "@faker-js/faker": "^8.4.1",
17 | "@hookform/resolvers": "^3.3.4",
18 | "@radix-ui/react-alert-dialog": "^1.0.5",
19 | "@radix-ui/react-checkbox": "^1.0.4",
20 | "@radix-ui/react-context-menu": "^2.1.5",
21 | "@radix-ui/react-dialog": "^1.0.5",
22 | "@radix-ui/react-dropdown-menu": "^2.0.6",
23 | "@radix-ui/react-icons": "^1.3.0",
24 | "@radix-ui/react-label": "^2.0.2",
25 | "@radix-ui/react-popover": "^1.0.7",
26 | "@radix-ui/react-radio-group": "^1.1.3",
27 | "@radix-ui/react-select": "^2.0.0",
28 | "@radix-ui/react-separator": "^1.0.3",
29 | "@radix-ui/react-slot": "^1.0.2",
30 | "@radix-ui/react-toggle": "^1.0.3",
31 | "@radix-ui/react-toggle-group": "^1.0.4",
32 | "@radix-ui/react-tooltip": "^1.0.7",
33 | "@tanstack/match-sorter-utils": "^8.15.1",
34 | "@tanstack/react-query": "^5.35.1",
35 | "@tanstack/react-query-devtools": "^5.35.1",
36 | "@tanstack/react-table": "^8.16.0",
37 | "@tanstack/react-virtual": "^3.5.0",
38 | "@tanstack/table-core": "^8.16.0",
39 | "@types/lodash": "^4.17.1",
40 | "class-variance-authority": "^0.7.0",
41 | "clsx": "^2.1.1",
42 | "cmdk": "^1.0.0",
43 | "date-fns": "^3.6.0",
44 | "lodash": "^4.17.21",
45 | "lucide-react": "^0.376.0",
46 | "next": "14.2.3",
47 | "react": "^18",
48 | "react-day-picker": "^8.10.1",
49 | "react-dom": "^18",
50 | "react-hook-form": "^7.51.4",
51 | "react-hot-keys": "^2.7.3",
52 | "react-resizable-panels": "^2.0.19",
53 | "react-virtual": "^2.10.4",
54 | "tailwind-merge": "^2.3.0",
55 | "tailwindcss-animate": "^1.0.7",
56 | "xlsx": "^0.18.5",
57 | "zod": "^3.23.7",
58 | "zustand": "^4.5.2"
59 | },
60 | "devDependencies": {
61 | "@types/node": "^20",
62 | "@types/react": "^18",
63 | "@types/react-dom": "^18",
64 | "@types/xlsx": "^0.0.36",
65 | "eslint": "^8",
66 | "eslint-config-next": "14.2.3",
67 | "postcss": "^8",
68 | "tailwindcss": "^3.4.1",
69 | "typescript": "^5"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/store/dataTableStore.ts:
--------------------------------------------------------------------------------
1 | import {createContext, useContext} from "react";
2 | import {combine} from "zustand/middleware";
3 | import {createStore as createZustandStore, useStore as useZustandStore} from "zustand";
4 | import {
5 | TDataTableAddDataProps,
6 | TDataTableContextMenuProps, TDataTableDataValidation,
7 | TDataTableEditDataProps,
8 | TDataTableExportProps
9 | } from "@/@types";
10 |
11 | export interface IDataTableStore {
12 | isSelecting: boolean;
13 | exportProps?: TDataTableExportProps;
14 | contextMenuProps?: TDataTableContextMenuProps;
15 | addDataProps?: TDataTableAddDataProps;
16 | editDataProps?: TDataTableEditDataProps;
17 | dataValidationProps?: TDataTableDataValidation[];
18 | }
19 |
20 | const getDefaultState = (): IDataTableStore => ({ isSelecting: false, exportProps: undefined, contextMenuProps: undefined, addDataProps: undefined, editDataProps: undefined, dataValidationProps: [] });
21 |
22 | export const createDataTableStore = (preloadedState: IDataTableStore) => {
23 | return createZustandStore(combine({...getDefaultState(), ...preloadedState},(set, get, store)=>({
24 | toggleSelection: () => {
25 | set(prev=>({
26 | isSelecting: !prev.isSelecting
27 | }));
28 | },
29 | setExtraProps: (
30 | exportProps: TDataTableExportProps | undefined,
31 | contextMenuProps: TDataTableContextMenuProps | undefined,
32 | addDataProps: TDataTableAddDataProps | undefined,
33 | editDataProps: TDataTableEditDataProps | undefined,
34 | dataValidationProps: TDataTableDataValidation[] | undefined,
35 | ) => {
36 | set(()=>({
37 | exportProps,
38 | contextMenuProps,
39 | addDataProps,
40 | editDataProps,
41 | dataValidationProps: dataValidationProps ?? []
42 | }));
43 | }
44 | })));
45 | };
46 |
47 | export type DataTableStoreType = ReturnType;
48 | type DataTableStoreInterface = ReturnType;
49 |
50 | const zustandContext = createContext(null);
51 | export const DataTableProvider = zustandContext.Provider;
52 |
53 | export const useDataTableStore = (selector: (state: DataTableStoreInterface) => T) => {
54 | const store = useContext(zustandContext);
55 | if (!store) throw new Error("Language Store is missing the provider");
56 | return useZustandStore(store, selector);
57 | };
--------------------------------------------------------------------------------
/store/dataTableStoreProvider.tsx:
--------------------------------------------------------------------------------
1 | import {PropsWithChildren, useRef} from "react";
2 | import {createDataTableStore, DataTableProvider, DataTableStoreType, IDataTableStore} from "@/store/dataTableStore";
3 |
4 | export const DataTableStoreProvider = ({children, ...props}:PropsWithChildren) => {
5 | const storeRef = useRef();
6 | if (!storeRef.current) {
7 | storeRef.current = createDataTableStore({...props});
8 | }
9 | return |