'use client';
import { createContext, ReactNode, useContext } from 'react';
import { cn } from '@/registry/default/lib/utils';
import { ColumnFiltersState, RowData, SortingState, Table } from '@tanstack/react-table';
declare module '@tanstack/react-table' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface ColumnMeta<TData extends RowData, TValue> {
headerTitle?: string;
headerClassName?: string;
cellClassName?: string;
skeleton?: ReactNode;
expandedContent?: (row: TData) => ReactNode;
}
}
export type DataGridApiFetchParams = {
pageIndex: number;
pageSize: number;
sorting?: SortingState;
filters?: ColumnFiltersState;
searchQuery?: string;
};
export type DataGridApiResponse<T> = {
data: T[];
empty: boolean;
pagination: {
total: number;
page: number;
};
};
export interface DataGridContextProps<TData extends object> {
props: DataGridProps<TData>;
table: Table<TData>;
recordCount: number;
isLoading: boolean;
}
export type DataGridRequestParams = {
pageIndex: number;
pageSize: number;
sorting?: SortingState;
columnFilters?: ColumnFiltersState;
};
export interface DataGridProps<TData extends object> {
className?: string;
table?: Table<TData>;
recordCount: number;
children?: ReactNode;
onRowClick?: (row: TData) => void;
isLoading?: boolean;
loadingMode?: 'skeleton' | 'spinner';
loadingMessage?: ReactNode | string;
emptyMessage?: ReactNode | string;
tableLayout?: {
dense?: boolean;
cellBorder?: boolean;
rowBorder?: boolean;
rowRounded?: boolean;
stripped?: boolean;
headerBackground?: boolean;
headerBorder?: boolean;
headerSticky?: boolean;
width?: 'auto' | 'fixed';
columnsVisibility?: boolean;
columnsResizable?: boolean;
columnsPinnable?: boolean;
columnsMovable?: boolean;
columnsDraggable?: boolean;
rowsDraggable?: boolean;
};
tableClassNames?: {
base?: string;
header?: string;
headerRow?: string;
headerSticky?: string;
body?: string;
bodyRow?: string;
footer?: string;
edgeCell?: string;
};
}
const DataGridContext = createContext<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
DataGridContextProps<any> | undefined
>(undefined);
function useDataGrid() {
const context = useContext(DataGridContext);
if (!context) {
throw new Error('useDataGrid must be used within a DataGridProvider');
}
return context;
}
function DataGridProvider<TData extends object>({
children,
table,
...props
}: DataGridProps<TData> & { table: Table<TData> }) {
return (
<DataGridContext.Provider
value={{
props,
table,
recordCount: props.recordCount,
isLoading: props.isLoading || false,
}}
>
{children}
</DataGridContext.Provider>
);
}
function DataGrid<TData extends object>({ children, table, ...props }: DataGridProps<TData>) {
const defaultProps: Partial<DataGridProps<TData>> = {
loadingMode: 'skeleton',
tableLayout: {
dense: false,
cellBorder: false,
rowBorder: true,
rowRounded: false,
stripped: false,
headerSticky: false,
headerBackground: true,
headerBorder: true,
width: 'fixed',
columnsVisibility: false,
columnsResizable: false,
columnsPinnable: false,
columnsMovable: false,
columnsDraggable: false,
rowsDraggable: false,
},
tableClassNames: {
base: '',
header: '',
headerRow: '',
headerSticky: 'sticky top-0 z-10 bg-background/90 backdrop-blur-xs',
body: '',
bodyRow: '',
footer: '',
edgeCell: '',
},
};
const mergedProps: DataGridProps<TData> = {
...defaultProps,
...props,
tableLayout: {
...defaultProps.tableLayout,
...(props.tableLayout || {}),
},
tableClassNames: {
...defaultProps.tableClassNames,
...(props.tableClassNames || {}),
},
};
// Ensure table is provided
if (!table) {
throw new Error('DataGrid requires a "table" prop');
}
return (
<DataGridProvider table={table} {...mergedProps}>
{children}
</DataGridProvider>
);
}
function DataGridContainer({
children,
className,
border = true,
}: {
children: ReactNode;
className?: string;
border?: boolean;
}) {
return (
<div data-slot="data-grid" className={cn('grid w-full', border && 'border border-border rounded-lg', className)}>
{children}
</div>
);
}
export { useDataGrid, DataGridProvider, DataGrid, DataGridContainer };