@reactorui/datagrid 1.0.20 → 1.0.22

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.
@@ -1,5 +1,5 @@
1
1
  import { DataGridProps } from '../../types';
2
2
  export declare const DataGrid: <T extends {
3
3
  [key: string]: any;
4
- } = any>({ data, endpoint, columns: columnsProp, enableSearch, enableSorting, enableFilters, enableSelection, enableDelete, deleteConfirmation, pageSize, serverPageSize, pageSizeOptions, httpConfig, variant, size, className, enableRefresh, onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, onSortChange, onFilterChange, onSearchChange, onTableRefresh, onTableRowClick, onTableRowDoubleClick, onRowSelect, onSelectionChange, onTableRowHover, onCellClick, onBulkDelete, ...rest }: DataGridProps<T>) => import("react/jsx-runtime").JSX.Element;
4
+ } = any>({ data, columns: columnsProp, loading: simpleLoading, loadingState: externalLoadingState, totalRecords: externalTotalRecords, error: externalError, currentPage: externalCurrentPage, enableSearch, enableSorting, enableFilters, enableSelection, enableDelete, enableRefresh, deleteConfirmation, maxHeight, stickyHeader, pageSize, pageSizeOptions, variant, size, className, onPageChange, onPageSizeChange, onSortChange, onSearchChange, onApplyFilter, onRemoveFilter, onClearFilters, onFilterChange, onTableRowClick, onTableRowDoubleClick, onRowSelect, onSelectionChange, onTableRowHover, onCellClick, onTableRefresh, onBulkDelete, ...rest }: DataGridProps<T>) => import("react/jsx-runtime").JSX.Element;
5
5
  //# sourceMappingURL=DataGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataGrid.d.ts","sourceRoot":"","sources":["../../../src/components/DataGrid/DataGrid.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAU,MAAM,aAAa,CAAC;AAOpD,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,GAAG,GAAG,EAAE,mfAsC9D,aAAa,CAAC,CAAC,CAAC,4CAiUlB,CAAC"}
1
+ {"version":3,"file":"DataGrid.d.ts","sourceRoot":"","sources":["../../../src/components/DataGrid/DataGrid.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAgClE,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,GAAG,GAAG,EAAE,8nBA+D9D,aAAa,CAAC,CAAC,CAAC,4CA6VlB,CAAC"}
@@ -1,37 +1,85 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // File: src/components/DataGrid/DataGrid.tsx
2
3
  import React, { useMemo, useCallback } from 'react';
3
4
  import { useDataGrid } from '../../hooks';
4
5
  import { SearchInput } from '../Search';
5
6
  import { FilterControls } from '../Filter';
6
7
  import { TableHeader, TableBody } from '../Table';
7
8
  import { getTheme } from '../../themes';
8
- export const DataGrid = ({ data, endpoint, columns: columnsProp = [], enableSearch = true, enableSorting = true, enableFilters = true, enableSelection = true, enableDelete = false, deleteConfirmation = false, pageSize = 10, serverPageSize = 100, pageSizeOptions = [5, 10, 25, 50, 100], httpConfig, variant = 'default', size = 'md', className = '', enableRefresh = false,
9
- // Event callbacks
10
- onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, onSortChange, onFilterChange, onSearchChange, onTableRefresh, onTableRowClick, onTableRowDoubleClick, onRowSelect, onSelectionChange, onTableRowHover, onCellClick, onBulkDelete, ...rest }) => {
9
+ import { inferDataType } from '../../utils';
10
+ // ============================================================================
11
+ // Spinner Component (reusable)
12
+ // ============================================================================
13
+ const Spinner = ({ className = 'w-4 h-4' }) => (_jsxs("svg", { className: `animate-spin ${className}`, xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }));
14
+ // ============================================================================
15
+ // Main Component
16
+ // ============================================================================
17
+ export const DataGrid = ({
18
+ // Data
19
+ data, columns: columnsProp = [],
20
+ // Loading states
21
+ loading: simpleLoading = false, loadingState: externalLoadingState,
22
+ // External state (controlled mode)
23
+ totalRecords: externalTotalRecords, error: externalError, currentPage: externalCurrentPage,
24
+ // Features
25
+ enableSearch = true, enableSorting = true, enableFilters = true, enableSelection = true, enableDelete = false, enableRefresh = false, deleteConfirmation = false,
26
+ // Layout
27
+ maxHeight, stickyHeader = false,
28
+ // Pagination
29
+ pageSize = 10, pageSizeOptions = [5, 10, 25, 50, 100],
30
+ // Styling
31
+ variant = 'default', size = 'md', className = '',
32
+ // Pagination Events
33
+ onPageChange, onPageSizeChange,
34
+ // Sort & Search Events
35
+ onSortChange, onSearchChange,
36
+ // Filter Events
37
+ onApplyFilter, onRemoveFilter, onClearFilters, onFilterChange,
38
+ // Row & Cell Events
39
+ onTableRowClick, onTableRowDoubleClick, onRowSelect, onSelectionChange, onTableRowHover, onCellClick,
40
+ // Action Events
41
+ onTableRefresh, onBulkDelete, ...rest }) => {
11
42
  const theme = getTheme(variant);
43
+ // ===== Normalize Loading State =====
44
+ // If simple `loading` is passed, convert to loadingState.data
45
+ // If loadingState is passed, use it directly
46
+ const loadingState = useMemo(() => {
47
+ if (externalLoadingState)
48
+ return externalLoadingState;
49
+ if (simpleLoading)
50
+ return { data: true };
51
+ return {};
52
+ }, [externalLoadingState, simpleLoading]);
53
+ // Destructure for convenience
54
+ const isDataLoading = loadingState.data ?? false;
55
+ const isFilterLoading = loadingState.filter ?? false;
56
+ const isSearchLoading = loadingState.search ?? false;
57
+ const isRefreshLoading = loadingState.refresh ?? false;
58
+ const isDeleteLoading = loadingState.delete ?? false;
59
+ // Any loading state disables controls
60
+ const isAnyLoading = isDataLoading || isFilterLoading || isSearchLoading || isRefreshLoading || isDeleteLoading;
12
61
  // Use the data grid hook
13
- const { data: sourceData, processedData, paginatedData, loading, error, searchTerm, activeFilters, sortConfig, selectedRows, currentPage, currentPageSize, setSearchTerm, setSort, setCurrentPageSize, navigateNext, navigatePrevious, addFilter, removeFilter, clearFilters, selectRow, selectAll, paginationInfo, selectedData, refresh, getRowId, // Get the stable ID generator
14
- } = useDataGrid({
62
+ const { processedData, paginatedData, searchTerm, activeFilters, sortConfig, selectedRows, currentPage, currentPageSize, setSearchTerm, setSort, setCurrentPage, setCurrentPageSize, navigateNext, navigatePrevious, addFilter, removeFilter, clearFilters, selectRow, selectAll, paginationInfo, selectedData, refresh, getRowId, } = useDataGrid({
15
63
  data,
16
- endpoint,
17
- httpConfig,
18
64
  pageSize,
19
- serverPageSize,
20
- onDataLoad,
21
- onDataError,
22
- onLoadingStateChange,
65
+ totalRecords: externalTotalRecords,
66
+ currentPage: externalCurrentPage,
67
+ loading: isDataLoading,
23
68
  onPageChange,
24
69
  onPageSizeChange,
25
70
  onSortChange,
26
- onFilterChange,
27
71
  onSearchChange,
72
+ onApplyFilter,
73
+ onRemoveFilter,
74
+ onClearFilters,
75
+ onFilterChange,
28
76
  });
29
77
  // Auto-detect columns if not provided
30
78
  const columns = useMemo(() => {
31
79
  if (columnsProp.length > 0)
32
80
  return columnsProp;
33
- if (sourceData.length > 0) {
34
- const firstRow = sourceData[0];
81
+ if (data.length > 0) {
82
+ const firstRow = data[0];
35
83
  return Object.keys(firstRow).map((key) => ({
36
84
  key,
37
85
  label: key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1'),
@@ -41,32 +89,27 @@ onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, o
41
89
  }));
42
90
  }
43
91
  return [];
44
- }, [columnsProp, sourceData]);
45
- // Memoize the selection change handler
46
- const handleSelectionChange = useCallback((newSelectedData) => {
47
- onSelectionChange?.(newSelectedData);
48
- }, [onSelectionChange]);
49
- // Use a ref to track previous selectedData to avoid unnecessary calls
92
+ }, [columnsProp, data]);
93
+ // Selection change effect
50
94
  const previousSelectedData = React.useRef([]);
51
- // Only call onSelectionChange when selectedData actually changes
52
95
  React.useLayoutEffect(() => {
53
96
  const hasChanged = selectedData.length !== previousSelectedData.current.length ||
54
97
  selectedData.some((item, index) => item !== previousSelectedData.current[index]);
55
98
  if (hasChanged && onSelectionChange) {
56
99
  previousSelectedData.current = selectedData;
57
- handleSelectionChange(selectedData);
100
+ onSelectionChange(selectedData);
58
101
  }
59
- }, [selectedData, handleSelectionChange]);
102
+ }, [selectedData, onSelectionChange]);
60
103
  // Handle row selection with callback
61
104
  const handleRowSelect = useCallback((rowId, selected) => {
62
105
  selectRow(rowId, selected);
63
106
  if (onRowSelect) {
64
- const row = sourceData.find((r) => getRowId(r) === rowId);
107
+ const row = data.find((r) => getRowId(r) === rowId);
65
108
  if (row) {
66
109
  onRowSelect(row, selected);
67
110
  }
68
111
  }
69
- }, [selectRow, onRowSelect, sourceData, getRowId]);
112
+ }, [selectRow, onRowSelect, data, getRowId]);
70
113
  // Handle delete action
71
114
  const handleDelete = useCallback(() => {
72
115
  if (selectedRows.size === 0)
@@ -92,29 +135,21 @@ onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, o
92
135
  refresh();
93
136
  onTableRefresh?.();
94
137
  }, [refresh, onTableRefresh]);
95
- if (error) {
96
- return (_jsx("div", { className: `${theme.container} ${className}`, ...rest, children: _jsxs("div", { className: "px-4 py-8 text-center", children: [_jsx("div", { className: "text-red-600 dark:text-red-400 mb-2", children: "Error loading data" }), _jsx("div", { className: "text-sm text-gray-600 dark:text-gray-400 mb-4", children: error }), _jsx("button", { onClick: handleRefresh, className: theme.button, children: "Try Again" })] }) }));
138
+ // Determine if fixed layout is enabled
139
+ const hasFixedLayout = maxHeight !== undefined || stickyHeader;
140
+ // Error state
141
+ if (externalError) {
142
+ return (_jsx("div", { className: `${theme.container} ${className}`, ...rest, children: _jsxs("div", { className: "px-4 py-8 text-center", children: [_jsx("div", { className: "text-red-600 dark:text-red-400 mb-2", children: "Error loading data" }), _jsx("div", { className: "text-sm text-gray-600 dark:text-gray-400 mb-4", children: externalError }), _jsx("button", { onClick: handleRefresh, className: theme.button, children: "Try Again" })] }) }));
97
143
  }
98
- return (_jsxs("div", { className: `${theme.container} ${className}`, ...rest, children: [enableFilters && (_jsx("div", { className: "p-4 pb-2", children: _jsx("div", { className: "flex justify-between items-start gap-4", children: _jsx("div", { className: "flex-1", children: _jsx(FilterControls, { columns: columns, activeFilters: activeFilters, onAddFilter: addFilter, onRemoveFilter: removeFilter, onClearFilters: clearFilters }) }) }) })), _jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "Show" }), _jsx("select", { value: currentPageSize, onChange: (e) => setCurrentPageSize(parseInt(e.target.value)), className: "px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400", children: pageSizeOptions.map((size) => (_jsx("option", { value: size, children: size }, size))) }), _jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "entries" })] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [enableSearch && (_jsx("div", { className: "w-64", children: _jsx(SearchInput, { value: searchTerm, onChange: setSearchTerm, placeholder: "Search...", disabled: loading, className: theme.searchInput }) })), enableRefresh && (_jsx("button", { onClick: handleRefresh, disabled: loading, title: loading ? 'Loading...' : 'Refresh data', className: "px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 transition-colors duration-150 flex items-center justify-center", children: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }) })), enableDelete && enableSelection && (_jsxs("button", { onClick: handleDelete, disabled: selectedRows.size === 0, title: selectedRows.size === 0
144
+ return (_jsxs("div", { className: `${theme.container} ${className} ${hasFixedLayout ? 'flex flex-col' : ''}`, style: hasFixedLayout && maxHeight
145
+ ? { maxHeight: typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight }
146
+ : undefined, ...rest, children: [enableFilters && (_jsx("div", { className: "p-4 pb-2 flex-shrink-0", children: _jsx(FilterControls, { columns: columns, activeFilters: activeFilters, onApplyFilter: addFilter, onRemoveFilter: removeFilter, onClearFilters: clearFilters, disabled: isDataLoading, filterLoading: isFilterLoading }) })), _jsx("div", { className: "px-4 pb-4 flex-shrink-0", children: _jsxs("div", { className: "flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "Show" }), _jsx("select", { value: currentPageSize, onChange: (e) => setCurrentPageSize(parseInt(e.target.value)), disabled: isAnyLoading, className: "px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50", children: pageSizeOptions.map((size) => (_jsx("option", { value: size, children: size }, size))) }), _jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "entries" })] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [enableSearch && (_jsxs("div", { className: "w-64 relative", children: [_jsx(SearchInput, { value: searchTerm, onChange: setSearchTerm, placeholder: "Search...", disabled: isAnyLoading, className: theme.searchInput }), isSearchLoading && (_jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: _jsx(Spinner, { className: "w-4 h-4 text-gray-400" }) }))] })), enableRefresh && (_jsx("button", { onClick: handleRefresh, disabled: isAnyLoading, title: isRefreshLoading ? 'Refreshing...' : 'Refresh data', className: "px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 transition-colors duration-150 flex items-center justify-center", children: _jsx("svg", { className: `w-4 h-4 ${isRefreshLoading ? 'animate-spin' : ''}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }) })), enableDelete && enableSelection && (_jsxs("button", { onClick: handleDelete, disabled: selectedRows.size === 0 || isAnyLoading, title: selectedRows.size === 0
99
147
  ? 'Select rows to delete'
100
- : `Delete ${selectedRows.size} selected item${selectedRows.size === 1 ? '' : 's'}`, className: `px-3 py-2 rounded focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors duration-150 flex items-center gap-1 ${selectedRows.size === 0
148
+ : isDeleteLoading
149
+ ? 'Deleting...'
150
+ : `Delete ${selectedRows.size} selected item${selectedRows.size === 1 ? '' : 's'}`, className: `px-3 py-2 rounded focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors duration-150 flex items-center gap-1 ${selectedRows.size === 0 || isAnyLoading
101
151
  ? 'bg-gray-100 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
102
- : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 cursor-pointer'}`, children: [_jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }), selectedRows.size > 0 && (_jsxs("span", { className: "text-sm", children: ["(", selectedRows.size, " selected)"] }))] }))] })] }) }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: theme.table, children: [_jsx(TableHeader, { columns: columns, sortConfig: sortConfig, onSort: enableSorting ? setSort : undefined, enableSelection: enableSelection, selectedCount: selectedRows.size, totalCount: paginatedData.length, onSelectAll: enableSelection ? selectAll : undefined, theme: theme }), _jsx(TableBody, { columns: columns, data: paginatedData, selectedRows: selectedRows, onSelectRow: enableSelection ? handleRowSelect : undefined, onRowClick: onTableRowClick, onRowDoubleClick: onTableRowDoubleClick, onRowHover: onTableRowHover, onCellClick: onCellClick, enableSelection: enableSelection, loading: loading, theme: theme, getRowId: getRowId })] }) }), _jsxs("div", { className: theme.pagination, children: [_jsxs("div", { className: "text-sm text-gray-700 dark:text-gray-300 flex-shrink-0", children: ["Showing ", paginationInfo.start, "-", paginationInfo.end, " of", ' ', paginationInfo.totalRecords.toLocaleString(), " records"] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("button", { onClick: navigatePrevious, disabled: !paginationInfo.hasPrevious, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Previous" }), _jsxs("span", { className: "text-sm text-gray-700 dark:text-gray-300 px-2", children: ["Page ", currentPage, " ", paginationInfo.totalPages > 0 && `of ${paginationInfo.totalPages}`] }), _jsx("button", { onClick: navigateNext, disabled: !paginationInfo.hasNext, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Next" })] })] })] }));
152
+ : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 cursor-pointer'}`, children: [isDeleteLoading ? (_jsx(Spinner, { className: "w-4 h-4" })) : (_jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) })), selectedRows.size > 0 && (_jsx("span", { className: "text-sm", children: isDeleteLoading ? 'Deleting...' : `(${selectedRows.size} selected)` }))] }))] })] }) }), _jsx("div", { className: `
153
+ ${hasFixedLayout ? 'flex-1 overflow-auto min-h-0' : 'overflow-x-auto'}
154
+ `, children: _jsxs("table", { className: theme.table, children: [_jsx(TableHeader, { columns: columns, sortConfig: sortConfig, onSort: enableSorting ? setSort : undefined, enableSelection: enableSelection, selectedCount: selectedRows.size, totalCount: paginatedData.length, onSelectAll: enableSelection ? selectAll : undefined, theme: theme, sticky: hasFixedLayout }), _jsx(TableBody, { columns: columns, data: paginatedData, selectedRows: selectedRows, onSelectRow: enableSelection ? handleRowSelect : undefined, onRowClick: onTableRowClick, onRowDoubleClick: onTableRowDoubleClick, onRowHover: onTableRowHover, onCellClick: onCellClick, enableSelection: enableSelection, loading: isDataLoading, theme: theme, getRowId: getRowId })] }) }), _jsxs("div", { className: `${theme.pagination} flex-shrink-0`, children: [_jsxs("div", { className: "text-sm text-gray-700 dark:text-gray-300 flex-shrink-0", children: ["Showing ", paginationInfo.start, "-", paginationInfo.end, " of", ' ', paginationInfo.totalRecords.toLocaleString(), " records"] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("button", { onClick: navigatePrevious, disabled: !paginationInfo.hasPrevious || isAnyLoading, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Previous" }), _jsxs("span", { className: "text-sm text-gray-700 dark:text-gray-300 px-2", children: ["Page ", currentPage, " ", paginationInfo.totalPages > 0 && `of ${paginationInfo.totalPages}`] }), _jsx("button", { onClick: navigateNext, disabled: !paginationInfo.hasNext || isAnyLoading, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Next" })] })] })] }));
103
155
  };
104
- // Helper function to infer data type
105
- function inferDataType(value) {
106
- if (value == null)
107
- return 'string';
108
- if (typeof value === 'boolean')
109
- return 'boolean';
110
- if (typeof value === 'number')
111
- return 'number';
112
- if (typeof value === 'string') {
113
- // Try to detect dates
114
- const dateValue = new Date(value);
115
- if (!isNaN(dateValue.getTime()) && dateValue.getFullYear() > 1900) {
116
- return value.includes('T') || value.includes(' ') ? 'datetime' : 'date';
117
- }
118
- }
119
- return 'string';
120
- }
@@ -2,10 +2,14 @@ import { Column, ActiveFilter } from '../../types';
2
2
  interface FilterControlsProps<T> {
3
3
  columns: Column<T>[];
4
4
  activeFilters: ActiveFilter[];
5
- onAddFilter: (filter: Omit<ActiveFilter, 'label'>) => void;
5
+ onApplyFilter: (filter: Omit<ActiveFilter, 'label'>) => void;
6
6
  onRemoveFilter: (index: number) => void;
7
7
  onClearFilters: () => void;
8
+ /** Disables all controls (e.g., when data is loading) */
9
+ disabled?: boolean;
10
+ /** Shows spinner specifically on Apply Filter button */
11
+ filterLoading?: boolean;
8
12
  }
9
- export declare const FilterControls: <T>({ columns, activeFilters, onAddFilter, onRemoveFilter, onClearFilters, }: FilterControlsProps<T>) => import("react/jsx-runtime").JSX.Element;
13
+ export declare const FilterControls: <T>({ columns, activeFilters, onApplyFilter, onRemoveFilter, onClearFilters, disabled, filterLoading, }: FilterControlsProps<T>) => import("react/jsx-runtime").JSX.Element;
10
14
  export {};
11
15
  //# sourceMappingURL=FilterControls.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FilterControls.d.ts","sourceRoot":"","sources":["../../../src/components/Filter/FilterControls.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEnD,UAAU,mBAAmB,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC3D,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,GAAI,CAAC,EAAG,0EAMhC,mBAAmB,CAAC,CAAC,CAAC,4CAgMxB,CAAC"}
1
+ {"version":3,"file":"FilterControls.d.ts","sourceRoot":"","sources":["../../../src/components/Filter/FilterControls.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAkB,MAAM,aAAa,CAAC;AAMnE,UAAU,mBAAmB,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA4FD,eAAO,MAAM,cAAc,GAAI,CAAC,EAAG,qGAQhC,mBAAmB,CAAC,CAAC,CAAC,4CAkMxB,CAAC"}
@@ -1,81 +1,133 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- export const FilterControls = ({ columns, activeFilters, onAddFilter, onRemoveFilter, onClearFilters, }) => {
2
+ // File: src/components/Filter/FilterControls.tsx
3
+ import { useState, useCallback, useMemo } from 'react';
4
+ // ============================================================================
5
+ // Operator Configuration
6
+ // ============================================================================
7
+ const OPERATORS = {
8
+ string: [
9
+ { value: 'eq', label: 'equals' },
10
+ { value: 'neq', label: 'not equals' },
11
+ { value: 'contains', label: 'contains' },
12
+ { value: 'startsWith', label: 'starts with' },
13
+ { value: 'endsWith', label: 'ends with' },
14
+ ],
15
+ number: [
16
+ { value: 'eq', label: 'equals' },
17
+ { value: 'neq', label: 'not equals' },
18
+ { value: 'gt', label: 'greater than' },
19
+ { value: 'gte', label: 'greater or equal' },
20
+ { value: 'lt', label: 'less than' },
21
+ { value: 'lte', label: 'less or equal' },
22
+ ],
23
+ date: [
24
+ { value: 'eq', label: 'equals' },
25
+ { value: 'gt', label: 'after' },
26
+ { value: 'gte', label: 'on or after' },
27
+ { value: 'lt', label: 'before' },
28
+ { value: 'lte', label: 'on or before' },
29
+ ],
30
+ datetime: [
31
+ { value: 'eq', label: 'equals' },
32
+ { value: 'gt', label: 'after' },
33
+ { value: 'gte', label: 'on or after' },
34
+ { value: 'lt', label: 'before' },
35
+ { value: 'lte', label: 'on or before' },
36
+ ],
37
+ boolean: [{ value: 'eq', label: 'equals' }],
38
+ };
39
+ const DEFAULT_OPERATORS = [{ value: 'eq', label: 'equals' }];
40
+ // ============================================================================
41
+ // Spinner Component
42
+ // ============================================================================
43
+ const Spinner = ({ className = 'w-4 h-4' }) => (_jsxs("svg", { className: `animate-spin ${className}`, xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }));
44
+ // ============================================================================
45
+ // Styles (centralized for DRY)
46
+ // ============================================================================
47
+ const styles = {
48
+ select: 'w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed',
49
+ input: 'px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:cursor-not-allowed',
50
+ inputDisabled: 'px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-gray-50 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed',
51
+ buttonPrimary: 'px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white text-sm rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-300 dark:disabled:bg-gray-600 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed transition-colors duration-150 inline-flex items-center gap-2',
52
+ buttonSecondary: 'px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-sm rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150',
53
+ filterTag: 'inline-flex items-center gap-1 px-3 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200 rounded-full text-sm',
54
+ filterTagRemove: 'ml-1 text-blue-600 dark:text-blue-300 hover:text-blue-800 dark:hover:text-blue-100 focus:outline-none transition-colors duration-150',
55
+ label: 'block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1',
56
+ };
57
+ // ============================================================================
58
+ // Component
59
+ // ============================================================================
60
+ export const FilterControls = ({ columns, activeFilters, onApplyFilter, onRemoveFilter, onClearFilters, disabled = false, filterLoading = false, }) => {
61
+ // Form state
4
62
  const [filterColumn, setFilterColumn] = useState('');
5
63
  const [filterOperator, setFilterOperator] = useState('eq');
6
64
  const [filterValue, setFilterValue] = useState('');
7
- const filterableColumns = columns.filter((col) => col.filterable !== false);
8
- const selectedColumn = filterableColumns.find((col) => col.key === filterColumn);
9
- const getOperatorOptions = () => {
10
- if (!selectedColumn)
11
- return [{ value: 'eq', label: 'equals' }];
12
- switch (selectedColumn.dataType) {
13
- case 'number':
14
- case 'date':
15
- case 'datetime':
16
- return [
17
- { value: 'eq', label: 'equals' },
18
- { value: 'gt', label: 'greater than' },
19
- { value: 'gte', label: 'greater than or equal' },
20
- { value: 'lt', label: 'less than' },
21
- { value: 'lte', label: 'less than or equal' },
22
- ];
23
- case 'string':
24
- return [
25
- { value: 'eq', label: 'equals' },
26
- { value: 'contains', label: 'contains' },
27
- { value: 'startsWith', label: 'starts with' },
28
- { value: 'endsWith', label: 'ends with' },
29
- ];
30
- case 'boolean':
31
- return [{ value: 'eq', label: 'equals' }];
32
- default:
33
- return [{ value: 'eq', label: 'equals' }];
65
+ // Combined disabled state - disabled OR filterLoading
66
+ const isDisabled = disabled || filterLoading;
67
+ // Derived state
68
+ const filterableColumns = useMemo(() => columns.filter((col) => col.filterable !== false), [columns]);
69
+ const selectedColumn = useMemo(() => filterableColumns.find((col) => col.key === filterColumn), [filterableColumns, filterColumn]);
70
+ const operatorOptions = useMemo(() => OPERATORS[selectedColumn?.dataType ?? 'string'] ?? DEFAULT_OPERATORS, [selectedColumn]);
71
+ const canApply = filterColumn && filterValue.trim();
72
+ // Handlers
73
+ const handleColumnChange = useCallback((columnKey) => {
74
+ setFilterColumn(columnKey);
75
+ setFilterOperator('eq');
76
+ setFilterValue('');
77
+ }, []);
78
+ const handleApply = useCallback(() => {
79
+ if (!canApply || !selectedColumn || isDisabled)
80
+ return;
81
+ onApplyFilter({
82
+ column: filterColumn,
83
+ operator: filterOperator,
84
+ value: filterValue.trim(),
85
+ dataType: selectedColumn.dataType ?? 'string',
86
+ });
87
+ }, [
88
+ canApply,
89
+ filterColumn,
90
+ filterOperator,
91
+ filterValue,
92
+ selectedColumn,
93
+ onApplyFilter,
94
+ isDisabled,
95
+ ]);
96
+ const handleClear = useCallback(() => {
97
+ setFilterColumn('');
98
+ setFilterOperator('eq');
99
+ setFilterValue('');
100
+ onClearFilters();
101
+ }, [onClearFilters]);
102
+ const handleKeyDown = useCallback((e) => {
103
+ if (e.key === 'Enter' && canApply && !isDisabled) {
104
+ handleApply();
34
105
  }
35
- };
36
- const renderFilterInput = () => {
106
+ }, [canApply, handleApply, isDisabled]);
107
+ // Render value input based on data type
108
+ const renderValueInput = () => {
37
109
  if (!selectedColumn) {
38
- return (_jsx("input", { type: "text", disabled: true, placeholder: "Select column first", className: "px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-gray-50 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed" }));
110
+ return (_jsx("input", { type: "text", disabled: true, value: "", placeholder: "Select column first", className: styles.inputDisabled }));
39
111
  }
40
- const baseProps = {
112
+ const commonProps = {
41
113
  value: filterValue,
42
114
  onChange: (e) => setFilterValue(e.target.value),
43
- className: 'px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400',
115
+ onKeyDown: handleKeyDown,
116
+ disabled: isDisabled,
117
+ className: styles.input,
44
118
  };
45
119
  switch (selectedColumn.dataType) {
46
120
  case 'boolean':
47
- return (_jsxs("select", { ...baseProps, children: [_jsx("option", { value: "", children: "Select value" }), _jsx("option", { value: "true", children: "True" }), _jsx("option", { value: "false", children: "False" })] }));
121
+ return (_jsxs("select", { ...commonProps, className: styles.select, children: [_jsx("option", { value: "", children: "Select value" }), _jsx("option", { value: "true", children: "True" }), _jsx("option", { value: "false", children: "False" })] }));
48
122
  case 'date':
49
- return _jsx("input", { ...baseProps, type: "date" });
123
+ return _jsx("input", { ...commonProps, type: "date" });
50
124
  case 'datetime':
51
- return _jsx("input", { ...baseProps, type: "datetime-local" });
125
+ return _jsx("input", { ...commonProps, type: "datetime-local" });
52
126
  case 'number':
53
- return _jsx("input", { ...baseProps, type: "number", placeholder: "Enter number" });
127
+ return _jsx("input", { ...commonProps, type: "number", placeholder: "Enter number" });
54
128
  default:
55
- return _jsx("input", { ...baseProps, type: "text", placeholder: "Enter value" });
129
+ return _jsx("input", { ...commonProps, type: "text", placeholder: "Enter value" });
56
130
  }
57
131
  };
58
- const handleApplyFilter = () => {
59
- if (!filterColumn || !filterValue.trim())
60
- return;
61
- onAddFilter({
62
- column: filterColumn,
63
- operator: filterOperator,
64
- value: filterValue.trim(),
65
- dataType: selectedColumn?.dataType || 'string',
66
- });
67
- // DON'T reset the form - keep the values so user can modify and reapply
68
- };
69
- const handleClearfilter = () => {
70
- setFilterColumn('');
71
- setFilterOperator('eq');
72
- setFilterValue('');
73
- onClearFilters();
74
- };
75
- const canApplyFilter = filterColumn && filterValue.trim();
76
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap gap-4 items-end", children: [_jsxs("div", { className: "min-w-40", children: [_jsx("label", { className: "block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Column" }), _jsxs("select", { value: filterColumn, onChange: (e) => {
77
- setFilterColumn(e.target.value);
78
- setFilterOperator('eq');
79
- setFilterValue('');
80
- }, className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400", children: [_jsx("option", { value: "", children: "Select Column" }), filterableColumns.map((col) => (_jsx("option", { value: String(col.key), children: col.label }, String(col.key))))] })] }), _jsxs("div", { className: "min-w-32", children: [_jsx("label", { className: "block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Operator" }), _jsx("select", { value: filterOperator, onChange: (e) => setFilterOperator(e.target.value), disabled: !filterColumn, className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed", children: getOperatorOptions().map((op) => (_jsx("option", { value: op.value, children: op.label }, op.value))) })] }), _jsxs("div", { className: "min-w-40", children: [_jsx("label", { className: "block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Value" }), renderFilterInput()] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { onClick: handleApplyFilter, disabled: !canApplyFilter, className: "px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white text-sm rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-300 dark:disabled:bg-gray-600 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed transition-colors duration-150", children: "Apply Filter" }), _jsx("button", { onClick: handleClearfilter, className: "px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-sm rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors duration-150", children: "Clear All" })] })] }), activeFilters.length > 0 && (_jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [_jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Active filters:" }), activeFilters.map((filter, index) => (_jsxs("span", { className: "inline-flex items-center gap-1 px-3 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200 rounded-full text-sm", children: [filter.label, _jsx("button", { onClick: () => onRemoveFilter(index), className: "ml-1 text-blue-600 dark:text-blue-300 hover:text-blue-800 dark:hover:text-blue-100 focus:outline-none transition-colors duration-150", children: "\u00D7" })] }, index)))] }))] }));
132
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap gap-4 items-end", children: [_jsxs("div", { className: "min-w-40", children: [_jsx("label", { className: styles.label, children: "Column" }), _jsxs("select", { value: filterColumn, onChange: (e) => handleColumnChange(e.target.value), disabled: isDisabled, className: styles.select, children: [_jsx("option", { value: "", children: "Select Column" }), filterableColumns.map((col) => (_jsx("option", { value: String(col.key), children: col.label }, String(col.key))))] })] }), _jsxs("div", { className: "min-w-32", children: [_jsx("label", { className: styles.label, children: "Operator" }), _jsx("select", { value: filterOperator, onChange: (e) => setFilterOperator(e.target.value), disabled: isDisabled || !filterColumn, className: styles.select, children: operatorOptions.map((op) => (_jsx("option", { value: op.value, children: op.label }, op.value))) })] }), _jsxs("div", { className: "min-w-40", children: [_jsx("label", { className: styles.label, children: "Value" }), renderValueInput()] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("button", { onClick: handleApply, disabled: isDisabled || !canApply, className: styles.buttonPrimary, children: [filterLoading && _jsx(Spinner, { className: "w-4 h-4" }), filterLoading ? 'Applying...' : 'Apply Filter'] }), _jsx("button", { onClick: handleClear, disabled: isDisabled, className: styles.buttonSecondary, children: "Clear All" })] })] }), activeFilters.length > 0 && (_jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [_jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Active filters:" }), activeFilters.map((filter, index) => (_jsxs("span", { className: styles.filterTag, children: [filter.label, _jsx("button", { onClick: () => onRemoveFilter(index), disabled: isDisabled, className: styles.filterTagRemove, "aria-label": `Remove filter: ${filter.label}`, children: "\u00D7" })] }, `${filter.column}-${index}`)))] }))] }));
81
133
  };
@@ -1 +1 @@
1
- {"version":3,"file":"TableBody.d.ts","sourceRoot":"","sources":["../../../src/components/Table/TableBody.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvD,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC9D,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvF,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,+JActD,cAAc,CAAC,CAAC,CAAC,4CAsJnB,CAAC"}
1
+ {"version":3,"file":"TableBody.d.ts","sourceRoot":"","sources":["../../../src/components/Table/TableBody.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvD,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC9D,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvF,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,+JActD,cAAc,CAAC,CAAC,CAAC,4CAsKnB,CAAC"}
@@ -49,10 +49,10 @@ export const TableBody = ({ columns, data, selectedRows, onSelectRow, onRowClick
49
49
  onCellClick(value, row, column, event);
50
50
  }
51
51
  };
52
- if (loading && data.length === 0) {
53
- return (_jsx("tbody", { className: "bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-600", children: Array.from({ length: 5 }).map((_, index) => (_jsxs("tr", { className: "animate-pulse", children: [enableSelection && (_jsx("td", { className: theme.cell, children: _jsx("div", { className: "w-4 h-4 bg-gray-200 dark:bg-gray-600 rounded" }) })), columns.map((column) => (_jsx("td", { className: theme.cell, children: _jsx("div", { className: "h-4 bg-gray-200 dark:bg-gray-600 rounded" }) }, String(column.key))))] }, index))) }));
52
+ if (loading) {
53
+ return (_jsx("tbody", { className: "bg-white dark:bg-gray-800", children: _jsx("tr", { children: _jsx("td", { colSpan: columns.length + (enableSelection ? 1 : 0), className: "px-4 py-12 text-center text-gray-500 dark:text-gray-400", children: _jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsxs("svg", { className: "animate-spin h-5 w-5 text-blue-600 dark:text-blue-400", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), _jsx("span", { children: "Loading..." })] }) }) }) }));
54
54
  }
55
- if (data.length === 0 && !loading) {
55
+ if (data.length === 0) {
56
56
  return (_jsx("tbody", { className: "bg-white dark:bg-gray-800", children: _jsx("tr", { children: _jsx("td", { colSpan: columns.length + (enableSelection ? 1 : 0), className: "px-4 py-8 text-center text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800", children: emptyMessage }) }) }));
57
57
  }
58
58
  return (_jsx("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-600", children: data.map((row, index) => {
@@ -9,7 +9,8 @@ interface TableHeaderProps<T> {
9
9
  totalCount: number;
10
10
  onSelectAll?: (selected: boolean) => void;
11
11
  theme: Theme;
12
+ sticky?: boolean;
12
13
  }
13
- export declare const TableHeader: <T>({ columns, sortConfig, onSort, enableSelection, selectedCount, totalCount, onSelectAll, theme, }: TableHeaderProps<T>) => import("react/jsx-runtime").JSX.Element;
14
+ export declare const TableHeader: <T>({ columns, sortConfig, onSort, enableSelection, selectedCount, totalCount, onSelectAll, theme, sticky, }: TableHeaderProps<T>) => import("react/jsx-runtime").JSX.Element;
14
15
  export {};
15
16
  //# sourceMappingURL=TableHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TableHeader.d.ts","sourceRoot":"","sources":["../../../src/components/Table/TableHeader.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,UAAU,gBAAgB,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,KAAK,EAAE,KAAK,CAAC;CACd;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,EAAG,kGAS7B,gBAAgB,CAAC,CAAC,CAAC,4CAsGrB,CAAC"}
1
+ {"version":3,"file":"TableHeader.d.ts","sourceRoot":"","sources":["../../../src/components/Table/TableHeader.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,UAAU,gBAAgB,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,EAAG,0GAU7B,gBAAgB,CAAC,CAAC,CAAC,4CAyGrB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export const TableHeader = ({ columns, sortConfig, onSort, enableSelection, selectedCount, totalCount, onSelectAll, theme, }) => {
2
+ export const TableHeader = ({ columns, sortConfig, onSort, enableSelection, selectedCount, totalCount, onSelectAll, theme, sticky = false, }) => {
3
3
  const getSortIcon = (columnKey) => {
4
4
  if (sortConfig.column !== columnKey) {
5
5
  return (_jsx("svg", { className: "w-4 h-4 text-gray-400 dark:text-gray-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) }));
@@ -9,7 +9,9 @@ export const TableHeader = ({ columns, sortConfig, onSort, enableSelection, sele
9
9
  }
10
10
  return (_jsx("svg", { className: "w-4 h-4 text-blue-500 dark:text-blue-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }));
11
11
  };
12
- return (_jsx("thead", { className: theme.header, children: _jsxs("tr", { children: [enableSelection && (_jsx("th", { className: `w-12 ${theme.headerCell}`, role: "columnheader", "aria-label": "Select all rows", children: _jsx("input", { type: "checkbox", checked: selectedCount > 0 && selectedCount === totalCount, ref: (el) => {
12
+ // Sticky styles
13
+ const stickyClass = sticky ? 'sticky top-0 z-10' : '';
14
+ return (_jsx("thead", { className: `${theme.header} ${stickyClass}`, children: _jsxs("tr", { children: [enableSelection && (_jsx("th", { className: `w-12 ${theme.headerCell}`, role: "columnheader", "aria-label": "Select all rows", children: _jsx("input", { type: "checkbox", checked: selectedCount > 0 && selectedCount === totalCount, ref: (el) => {
13
15
  if (el) {
14
16
  el.indeterminate = selectedCount > 0 && selectedCount < totalCount;
15
17
  }
@@ -1,38 +1,34 @@
1
- import { BaseRowData, ServerResponse, ActiveFilter, SortConfig, HttpConfig, PaginationInfo } from '../types';
1
+ import { BaseRowData, ActiveFilter, SortConfig, PaginationInfo } from '../types';
2
2
  interface UseDataGridProps<T> {
3
- data?: T[];
4
- endpoint?: string;
5
- httpConfig?: HttpConfig;
3
+ data: T[];
6
4
  pageSize?: number;
7
- serverPageSize?: number;
8
- onDataLoad?: (data: ServerResponse<T>) => void;
9
- onDataError?: (error: Error, context: string) => void;
10
- onLoadingStateChange?: (loading: boolean, context: string) => void;
11
- onPageChange?: (page: number, paginationInfo: PaginationInfo) => void;
12
- onPageSizeChange?: (pageSize: number, paginationInfo: PaginationInfo) => void;
13
- onSortChange?: (sortConfig: SortConfig) => void;
5
+ totalRecords?: number;
6
+ currentPage?: number;
7
+ loading?: boolean;
8
+ onPageChange?: (page: number, info: PaginationInfo) => void;
9
+ onPageSizeChange?: (size: number) => void;
10
+ onSortChange?: (config: SortConfig) => void;
11
+ onSearchChange?: (term: string) => void;
12
+ onApplyFilter?: (filter: ActiveFilter, all: ActiveFilter[]) => void;
13
+ onRemoveFilter?: (removed: ActiveFilter, remaining: ActiveFilter[]) => void;
14
+ onClearFilters?: () => void;
14
15
  onFilterChange?: (filters: ActiveFilter[]) => void;
15
- onSearchChange?: (searchTerm: string) => void;
16
16
  }
17
- export declare const useDataGrid: <T extends BaseRowData>({ data: staticData, endpoint, httpConfig, pageSize, serverPageSize, onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, onSortChange, onFilterChange, onSearchChange, }: UseDataGridProps<T>) => {
17
+ export declare const useDataGrid: <T extends BaseRowData>({ data, pageSize: initialPageSize, totalRecords: externalTotalRecords, currentPage: externalCurrentPage, loading: externalLoading, onPageChange, onPageSizeChange, onSortChange, onSearchChange, onApplyFilter, onRemoveFilter, onClearFilters, onFilterChange, }: UseDataGridProps<T>) => {
18
18
  data: T[];
19
19
  processedData: T[];
20
20
  paginatedData: T[];
21
21
  loading: boolean;
22
- error: string | null;
23
22
  searchTerm: string;
24
23
  activeFilters: ActiveFilter[];
25
24
  sortConfig: SortConfig;
26
25
  selectedRows: Set<string>;
27
26
  currentPage: number;
28
27
  currentPageSize: number;
29
- totalRecords: number;
30
- hasMore: boolean;
31
- continuationToken: string | undefined;
32
28
  setSearchTerm: (term: string) => void;
33
29
  setSort: (column: string) => void;
34
30
  setCurrentPage: (page: number) => void;
35
- setCurrentPageSize: (newPageSize: number) => void;
31
+ setCurrentPageSize: (size: number) => void;
36
32
  navigateNext: () => void;
37
33
  navigatePrevious: () => void;
38
34
  addFilter: (filter: Omit<ActiveFilter, "label">) => void;
@@ -40,11 +36,13 @@ export declare const useDataGrid: <T extends BaseRowData>({ data: staticData, en
40
36
  clearFilters: () => void;
41
37
  selectRow: (rowId: string, selected: boolean) => void;
42
38
  selectAll: (selected: boolean) => void;
39
+ clearSelection: () => void;
43
40
  refresh: () => void;
44
41
  paginationInfo: PaginationInfo;
45
42
  selectedData: T[];
46
43
  hasSelection: boolean;
47
44
  getRowId: (row: T) => string;
45
+ isControlled: boolean;
48
46
  };
49
47
  export {};
50
48
  //# sourceMappingURL=useDataGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDataGrid.d.ts","sourceRoot":"","sources":["../../src/hooks/useDataGrid.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EAEX,cAAc,EACd,YAAY,EACZ,UAAU,EACV,UAAU,EACV,cAAc,EACf,MAAM,UAAU,CAAC;AAGlB,UAAU,gBAAgB,CAAC,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,KAAK,IAAI,CAAC;IACtE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9E,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACnD,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,EAAE,oMAchD,gBAAgB,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;0BAoUX,MAAM;sBArFJ,MAAM;2BAkBR,MAAM;sCA2CC,MAAM;;;wBAuCX,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;0BAqB5B,MAAM;;uBA2BsB,MAAM,YAAY,OAAO;0BAalD,OAAO;;;;;oBA1WZ,CAAC,KAAG,MAAM;CAwanB,CAAC"}
1
+ {"version":3,"file":"useDataGrid.d.ts","sourceRoot":"","sources":["../../src/hooks/useDataGrid.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAOjF,UAAU,gBAAgB,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC5D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACpE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAC5E,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;CACpD;AAMD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,EAAE,mQAchD,gBAAgB,CAAC,CAAC,CAAC;;;;;;;;;;;0BAsGX,MAAM;sBASJ,MAAM;2BAaR,MAAM;+BAQN,MAAM;;;wBAsBJ,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;0BAkB5B,MAAM;;uBAwBsB,MAAM,YAAY,OAAO;0BASlD,OAAO;;;;;;oBA/KZ,CAAC,KAAG,MAAM;;CA+OnB,CAAC"}