@forgedevstack/grid-table 1.0.0

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/dist/index.mjs ADDED
@@ -0,0 +1,2754 @@
1
+ // src/components/GridTable/GridTable.tsx
2
+ import { useMemo as useMemo12, useCallback as useCallback13 } from "react";
3
+
4
+ // src/context/TableContext.tsx
5
+ import {
6
+ createContext,
7
+ useContext,
8
+ useReducer,
9
+ useCallback,
10
+ useMemo,
11
+ useEffect
12
+ } from "react";
13
+
14
+ // src/constants/numbers.const.ts
15
+ var ZERO = 0;
16
+ var ONE = 1;
17
+ var FIVE = 5;
18
+ var TEN = 10;
19
+ var TWENTY = 20;
20
+ var FIFTY = 50;
21
+ var HUNDRED = 100;
22
+ var DEFAULT_PAGE_SIZE = TEN;
23
+ var DEFAULT_PAGE_SIZES = [TEN, TWENTY, FIFTY, HUNDRED];
24
+ var MIN_COLUMN_WIDTH = 50;
25
+ var DEFAULT_COLUMN_WIDTH = 150;
26
+ var MAX_COLUMN_WIDTH = 500;
27
+ var SKELETON_ROWS = FIVE;
28
+ var DEBOUNCE_DELAY = 300;
29
+ var DRAG_THRESHOLD = FIVE;
30
+ var MOBILE_BREAKPOINT = 640;
31
+ var TABLET_BREAKPOINT = 768;
32
+ var DESKTOP_BREAKPOINT = 1024;
33
+ var DRAWER_ANIMATION_DURATION = 300;
34
+ var DRAWER_OVERLAY_OPACITY = 0.5;
35
+
36
+ // src/constants/breakpoints.const.ts
37
+ var BREAKPOINTS = {
38
+ mobile: MOBILE_BREAKPOINT,
39
+ tablet: TABLET_BREAKPOINT,
40
+ desktop: DESKTOP_BREAKPOINT
41
+ };
42
+ var BREAKPOINT_KEYS = ["mobile", "tablet", "desktop"];
43
+ var RESPONSIVE_MODES = {
44
+ stack: "stack",
45
+ scroll: "scroll",
46
+ hide: "hide"
47
+ };
48
+ var DEFAULT_MOBILE_BREAKPOINT = "tablet";
49
+
50
+ // src/constants/defaults.const.ts
51
+ var DEFAULT_TRANSLATIONS = {
52
+ empty: "No data available",
53
+ loading: "Loading...",
54
+ search: "Search...",
55
+ filter: "Filter",
56
+ sort: "Sort",
57
+ sortAsc: "Sort ascending",
58
+ sortDesc: "Sort descending",
59
+ clearSort: "Clear sort",
60
+ clearFilter: "Clear filter",
61
+ clearAll: "Clear all",
62
+ apply: "Apply",
63
+ cancel: "Cancel",
64
+ columns: "Columns",
65
+ showColumns: "Show columns",
66
+ hideColumn: "Hide column",
67
+ resetColumns: "Reset columns",
68
+ rowsPerPage: "Rows per page",
69
+ of: "of",
70
+ page: "Page",
71
+ first: "First",
72
+ previous: "Previous",
73
+ next: "Next",
74
+ last: "Last",
75
+ selected: "selected",
76
+ dragToReorder: "Drag to reorder",
77
+ noResults: "No results found",
78
+ errorLoading: "Error loading data",
79
+ retry: "Retry"
80
+ };
81
+ var DEFAULT_THEME = {
82
+ mode: "dark",
83
+ colors: {
84
+ background: {
85
+ primary: "#1e1e1e",
86
+ secondary: "#2b2b2b",
87
+ tertiary: "#3c3c3c",
88
+ hover: "#4e4e4e"
89
+ },
90
+ text: {
91
+ primary: "#a9b7c6",
92
+ secondary: "#808080",
93
+ muted: "#606060"
94
+ },
95
+ border: {
96
+ default: "rgba(255, 255, 255, 0.08)",
97
+ hover: "rgba(255, 255, 255, 0.15)"
98
+ },
99
+ accent: {
100
+ primary: "#1890ff",
101
+ success: "#52c41a",
102
+ warning: "#faad14",
103
+ error: "#ff4d4f"
104
+ }
105
+ }
106
+ };
107
+ var DEFAULT_LIGHT_THEME = {
108
+ mode: "light",
109
+ colors: {
110
+ background: {
111
+ primary: "#ffffff",
112
+ secondary: "#f5f5f5",
113
+ tertiary: "#ebebeb",
114
+ hover: "#e0e0e0"
115
+ },
116
+ text: {
117
+ primary: "#262626",
118
+ secondary: "#595959",
119
+ muted: "#8c8c8c"
120
+ },
121
+ border: {
122
+ default: "rgba(0, 0, 0, 0.06)",
123
+ hover: "rgba(0, 0, 0, 0.1)"
124
+ },
125
+ accent: {
126
+ primary: "#1890ff",
127
+ success: "#52c41a",
128
+ warning: "#faad14",
129
+ error: "#ff4d4f"
130
+ }
131
+ }
132
+ };
133
+ var DEFAULT_TABLE_CONFIG = {
134
+ pageSize: DEFAULT_PAGE_SIZE,
135
+ pageSizes: DEFAULT_PAGE_SIZES,
136
+ columnWidth: DEFAULT_COLUMN_WIDTH,
137
+ mobileBreakpoint: DEFAULT_MOBILE_BREAKPOINT,
138
+ stickyHeader: true,
139
+ showPagination: true,
140
+ showFilter: true,
141
+ showSort: true,
142
+ enableDragDrop: true,
143
+ enableColumnResize: true,
144
+ enableRowSelection: false,
145
+ enableMultiSelect: false,
146
+ showColumnToggle: true,
147
+ showEmptyState: true,
148
+ showSkeleton: true,
149
+ skeletonRows: 5,
150
+ virtualize: false,
151
+ virtualizeThreshold: 100
152
+ };
153
+
154
+ // src/context/TableContext.tsx
155
+ import { jsx } from "react/jsx-runtime";
156
+ function reducer(state, action) {
157
+ switch (action.type) {
158
+ case "SET_DATA":
159
+ return { ...state, data: action.payload, originalData: action.payload };
160
+ case "SET_LOADING":
161
+ return { ...state, loading: action.payload };
162
+ case "SET_ERROR":
163
+ return { ...state, error: action.payload };
164
+ case "SET_SORTING":
165
+ return { ...state, sorting: action.payload };
166
+ case "SET_FILTERS":
167
+ return { ...state, filters: action.payload };
168
+ case "SET_GLOBAL_FILTER":
169
+ return { ...state, globalFilter: action.payload };
170
+ case "SET_PAGE":
171
+ return { ...state, page: action.payload };
172
+ case "SET_PAGE_SIZE":
173
+ return { ...state, pageSize: action.payload, page: ONE };
174
+ case "SET_SELECTED_IDS":
175
+ return { ...state, selectedIds: action.payload };
176
+ case "SET_EXPANDED_IDS":
177
+ return { ...state, expandedIds: action.payload };
178
+ case "SET_COLUMN_STATES":
179
+ return { ...state, columnStates: action.payload };
180
+ case "SET_DRAGGING_COLUMN":
181
+ return { ...state, draggingColumnId: action.payload };
182
+ case "SET_RESIZING_COLUMN":
183
+ return { ...state, resizingColumnId: action.payload };
184
+ case "SET_ACTIVE_FILTER_COLUMN":
185
+ return { ...state, activeFilterColumnId: action.payload };
186
+ case "SET_CURRENT_BREAKPOINT":
187
+ return { ...state, currentBreakpoint: action.payload };
188
+ case "SET_MOBILE_DRAWER":
189
+ return {
190
+ ...state,
191
+ showMobileDrawer: action.payload.show,
192
+ mobileDrawerContent: action.payload.content
193
+ };
194
+ case "RESET":
195
+ return { ...state, ...action.payload };
196
+ default:
197
+ return state;
198
+ }
199
+ }
200
+ var TableContext = createContext(null);
201
+ function TableProvider({
202
+ children,
203
+ data,
204
+ columns,
205
+ loading = false,
206
+ error = null,
207
+ theme,
208
+ translations,
209
+ mobileBreakpoint = "tablet",
210
+ paginationConfig,
211
+ filterConfig: _filterConfig,
212
+ sortConfig: _sortConfig,
213
+ enableMultiSort = false,
214
+ getRowId,
215
+ onStateChange
216
+ }) {
217
+ var _a, _b;
218
+ const initialColumnStates = columns.map((col, index) => ({
219
+ id: col.id,
220
+ visible: !col.hidden,
221
+ width: typeof col.width === "number" ? col.width : DEFAULT_TABLE_CONFIG.columnWidth,
222
+ order: index,
223
+ pinned: col.sticky || null
224
+ }));
225
+ const mergedTheme = useMemo(
226
+ () => ({ ...DEFAULT_THEME, ...theme, colors: { ...DEFAULT_THEME.colors, ...theme == null ? void 0 : theme.colors } }),
227
+ [theme]
228
+ );
229
+ const mergedTranslations = useMemo(
230
+ () => ({ ...DEFAULT_TRANSLATIONS, ...translations }),
231
+ [translations]
232
+ );
233
+ const initialState = {
234
+ data,
235
+ originalData: data,
236
+ columns,
237
+ columnStates: initialColumnStates,
238
+ sorting: [],
239
+ filters: [],
240
+ globalFilter: "",
241
+ page: (_a = paginationConfig == null ? void 0 : paginationConfig.initialPage) != null ? _a : ONE,
242
+ pageSize: (_b = paginationConfig == null ? void 0 : paginationConfig.initialPageSize) != null ? _b : DEFAULT_TABLE_CONFIG.pageSize,
243
+ totalItems: data.length,
244
+ selectedIds: /* @__PURE__ */ new Set(),
245
+ expandedIds: /* @__PURE__ */ new Set(),
246
+ loading,
247
+ error,
248
+ theme: mergedTheme,
249
+ translations: mergedTranslations,
250
+ currentBreakpoint: "desktop",
251
+ mobileBreakpoint,
252
+ draggingColumnId: null,
253
+ resizingColumnId: null,
254
+ activeFilterColumnId: null,
255
+ showMobileDrawer: false,
256
+ mobileDrawerContent: null
257
+ };
258
+ const [state, dispatch] = useReducer(reducer, initialState);
259
+ useEffect(() => {
260
+ dispatch({ type: "SET_DATA", payload: data });
261
+ }, [data]);
262
+ useEffect(() => {
263
+ dispatch({ type: "SET_LOADING", payload: loading });
264
+ }, [loading]);
265
+ useEffect(() => {
266
+ dispatch({ type: "SET_ERROR", payload: error });
267
+ }, [error]);
268
+ useEffect(() => {
269
+ const handleResize = () => {
270
+ const width = window.innerWidth;
271
+ let breakpoint = "desktop";
272
+ if (width < MOBILE_BREAKPOINT) {
273
+ breakpoint = "mobile";
274
+ } else if (width < TABLET_BREAKPOINT) {
275
+ breakpoint = "tablet";
276
+ }
277
+ dispatch({ type: "SET_CURRENT_BREAKPOINT", payload: breakpoint });
278
+ };
279
+ handleResize();
280
+ window.addEventListener("resize", handleResize);
281
+ return () => window.removeEventListener("resize", handleResize);
282
+ }, []);
283
+ useEffect(() => {
284
+ onStateChange == null ? void 0 : onStateChange(state);
285
+ }, [state, onStateChange]);
286
+ const getRowIdFn = useCallback(
287
+ (row) => {
288
+ if (getRowId) return getRowId(row);
289
+ if ("id" in row) return row.id;
290
+ return data.indexOf(row);
291
+ },
292
+ [getRowId, data]
293
+ );
294
+ const actions = useMemo(
295
+ () => ({
296
+ setData: (newData) => dispatch({ type: "SET_DATA", payload: newData }),
297
+ setLoading: (value) => dispatch({ type: "SET_LOADING", payload: value }),
298
+ setError: (value) => dispatch({ type: "SET_ERROR", payload: value }),
299
+ setSorting: (columnId, direction) => {
300
+ const newSorting = enableMultiSort ? [...state.sorting.filter((s) => s.columnId !== columnId), { columnId, direction }] : direction ? [{ columnId, direction }] : [];
301
+ dispatch({ type: "SET_SORTING", payload: newSorting.filter((s) => s.direction !== null) });
302
+ },
303
+ toggleSorting: (columnId) => {
304
+ const current = state.sorting.find((s) => s.columnId === columnId);
305
+ let newDirection = "asc";
306
+ if ((current == null ? void 0 : current.direction) === "asc") newDirection = "desc";
307
+ else if ((current == null ? void 0 : current.direction) === "desc") newDirection = null;
308
+ actions.setSorting(columnId, newDirection);
309
+ },
310
+ clearSorting: () => dispatch({ type: "SET_SORTING", payload: [] }),
311
+ setFilter: (columnId, value, operator = "contains") => {
312
+ const newFilters = [
313
+ ...state.filters.filter((f) => f.columnId !== columnId),
314
+ { columnId, value, operator }
315
+ ];
316
+ dispatch({ type: "SET_FILTERS", payload: newFilters });
317
+ dispatch({ type: "SET_PAGE", payload: ONE });
318
+ },
319
+ removeFilter: (columnId) => {
320
+ const newFilters = state.filters.filter((f) => f.columnId !== columnId);
321
+ dispatch({ type: "SET_FILTERS", payload: newFilters });
322
+ },
323
+ clearFilters: () => {
324
+ dispatch({ type: "SET_FILTERS", payload: [] });
325
+ dispatch({ type: "SET_GLOBAL_FILTER", payload: "" });
326
+ },
327
+ setGlobalFilter: (value) => {
328
+ dispatch({ type: "SET_GLOBAL_FILTER", payload: value });
329
+ dispatch({ type: "SET_PAGE", payload: ONE });
330
+ },
331
+ setPage: (page) => dispatch({ type: "SET_PAGE", payload: page }),
332
+ setPageSize: (pageSize) => dispatch({ type: "SET_PAGE_SIZE", payload: pageSize }),
333
+ selectRow: (id) => {
334
+ const newSet = new Set(state.selectedIds);
335
+ newSet.add(id);
336
+ dispatch({ type: "SET_SELECTED_IDS", payload: newSet });
337
+ },
338
+ deselectRow: (id) => {
339
+ const newSet = new Set(state.selectedIds);
340
+ newSet.delete(id);
341
+ dispatch({ type: "SET_SELECTED_IDS", payload: newSet });
342
+ },
343
+ toggleRow: (id) => {
344
+ if (state.selectedIds.has(id)) {
345
+ actions.deselectRow(id);
346
+ } else {
347
+ actions.selectRow(id);
348
+ }
349
+ },
350
+ selectAll: () => {
351
+ const allIds = new Set(state.data.map(getRowIdFn));
352
+ dispatch({ type: "SET_SELECTED_IDS", payload: allIds });
353
+ },
354
+ deselectAll: () => dispatch({ type: "SET_SELECTED_IDS", payload: /* @__PURE__ */ new Set() }),
355
+ expandRow: (id) => {
356
+ const newSet = new Set(state.expandedIds);
357
+ newSet.add(id);
358
+ dispatch({ type: "SET_EXPANDED_IDS", payload: newSet });
359
+ },
360
+ collapseRow: (id) => {
361
+ const newSet = new Set(state.expandedIds);
362
+ newSet.delete(id);
363
+ dispatch({ type: "SET_EXPANDED_IDS", payload: newSet });
364
+ },
365
+ toggleRowExpansion: (id) => {
366
+ if (state.expandedIds.has(id)) {
367
+ actions.collapseRow(id);
368
+ } else {
369
+ actions.expandRow(id);
370
+ }
371
+ },
372
+ reorderColumn: (sourceId, targetId) => {
373
+ const newStates = [...state.columnStates];
374
+ const sourceIndex = newStates.findIndex((c) => c.id === sourceId);
375
+ const targetIndex = newStates.findIndex((c) => c.id === targetId);
376
+ if (sourceIndex === -ONE || targetIndex === -ONE) return;
377
+ const [removed] = newStates.splice(sourceIndex, ONE);
378
+ newStates.splice(targetIndex, ZERO, removed);
379
+ newStates.forEach((col, i) => col.order = i);
380
+ dispatch({ type: "SET_COLUMN_STATES", payload: newStates });
381
+ },
382
+ resizeColumn: (columnId, width) => {
383
+ const newStates = state.columnStates.map(
384
+ (col) => col.id === columnId ? { ...col, width } : col
385
+ );
386
+ dispatch({ type: "SET_COLUMN_STATES", payload: newStates });
387
+ },
388
+ toggleColumnVisibility: (columnId) => {
389
+ const newStates = state.columnStates.map(
390
+ (col) => col.id === columnId ? { ...col, visible: !col.visible } : col
391
+ );
392
+ dispatch({ type: "SET_COLUMN_STATES", payload: newStates });
393
+ },
394
+ resetColumns: () => dispatch({ type: "SET_COLUMN_STATES", payload: initialColumnStates }),
395
+ setDraggingColumn: (columnId) => dispatch({ type: "SET_DRAGGING_COLUMN", payload: columnId }),
396
+ setResizingColumn: (columnId) => dispatch({ type: "SET_RESIZING_COLUMN", payload: columnId }),
397
+ setActiveFilterColumn: (columnId) => dispatch({ type: "SET_ACTIVE_FILTER_COLUMN", payload: columnId }),
398
+ openMobileDrawer: (content) => dispatch({ type: "SET_MOBILE_DRAWER", payload: { show: true, content } }),
399
+ closeMobileDrawer: () => dispatch({ type: "SET_MOBILE_DRAWER", payload: { show: false, content: null } }),
400
+ refresh: () => dispatch({ type: "SET_DATA", payload: state.originalData }),
401
+ reset: () => dispatch({
402
+ type: "RESET",
403
+ payload: {
404
+ sorting: [],
405
+ filters: [],
406
+ globalFilter: "",
407
+ page: ONE,
408
+ selectedIds: /* @__PURE__ */ new Set(),
409
+ expandedIds: /* @__PURE__ */ new Set(),
410
+ columnStates: initialColumnStates
411
+ }
412
+ })
413
+ }),
414
+ [state, enableMultiSort, getRowIdFn, initialColumnStates]
415
+ );
416
+ const computed = useMemo(() => {
417
+ let filteredData = [...state.data];
418
+ if (state.globalFilter) {
419
+ const searchLower = state.globalFilter.toLowerCase();
420
+ filteredData = filteredData.filter(
421
+ (row) => Object.values(row).some(
422
+ (val) => String(val).toLowerCase().includes(searchLower)
423
+ )
424
+ );
425
+ }
426
+ state.filters.forEach((filter) => {
427
+ const column = columns.find((c) => c.id === filter.columnId);
428
+ if (!column) return;
429
+ filteredData = filteredData.filter((row) => {
430
+ const accessor = column.accessor;
431
+ const value = typeof accessor === "function" ? accessor(row) : row[accessor];
432
+ const filterValue = filter.value;
433
+ if (column.filterFn) {
434
+ return column.filterFn(value, filterValue, filter.operator);
435
+ }
436
+ const strValue = String(value != null ? value : "").toLowerCase();
437
+ const strFilter = String(filterValue != null ? filterValue : "").toLowerCase();
438
+ switch (filter.operator) {
439
+ case "equals":
440
+ return strValue === strFilter;
441
+ case "notEquals":
442
+ return strValue !== strFilter;
443
+ case "contains":
444
+ return strValue.includes(strFilter);
445
+ case "notContains":
446
+ return !strValue.includes(strFilter);
447
+ case "startsWith":
448
+ return strValue.startsWith(strFilter);
449
+ case "endsWith":
450
+ return strValue.endsWith(strFilter);
451
+ case "isEmpty":
452
+ return !value || strValue === "";
453
+ case "isNotEmpty":
454
+ return value && strValue !== "";
455
+ default:
456
+ return strValue.includes(strFilter);
457
+ }
458
+ });
459
+ });
460
+ let sortedData = [...filteredData];
461
+ if (state.sorting.length > ZERO) {
462
+ sortedData.sort((a, b) => {
463
+ for (const sort of state.sorting) {
464
+ const column = columns.find((c) => c.id === sort.columnId);
465
+ if (!column) continue;
466
+ const accessor = column.accessor;
467
+ const aVal = typeof accessor === "function" ? accessor(a) : a[accessor];
468
+ const bVal = typeof accessor === "function" ? accessor(b) : b[accessor];
469
+ if (column.sortFn) {
470
+ const result2 = column.sortFn(aVal, bVal, sort.direction);
471
+ if (result2 !== ZERO) return result2;
472
+ continue;
473
+ }
474
+ if (aVal === bVal) continue;
475
+ if (aVal == null) return ONE;
476
+ if (bVal == null) return -ONE;
477
+ const comparison = aVal < bVal ? -ONE : ONE;
478
+ const result = sort.direction === "desc" ? -comparison : comparison;
479
+ if (result !== ZERO) return result;
480
+ }
481
+ return ZERO;
482
+ });
483
+ }
484
+ const totalPages = Math.ceil(sortedData.length / state.pageSize) || ONE;
485
+ const startIndex = (state.page - ONE) * state.pageSize;
486
+ const paginatedData = sortedData.slice(startIndex, startIndex + state.pageSize);
487
+ const visibleColumns = columns.filter((col) => {
488
+ const colState = state.columnStates.find((cs) => cs.id === col.id);
489
+ return (colState == null ? void 0 : colState.visible) !== false;
490
+ });
491
+ const allSelected = state.data.length > ZERO && state.selectedIds.size === state.data.length;
492
+ const someSelected = state.selectedIds.size > ZERO && !allSelected;
493
+ const isMobile = state.currentBreakpoint === "mobile";
494
+ const isTablet = state.currentBreakpoint === "tablet";
495
+ const isDesktop = state.currentBreakpoint === "desktop";
496
+ return {
497
+ filteredData,
498
+ sortedData,
499
+ paginatedData,
500
+ visibleColumns,
501
+ totalPages,
502
+ canGoNext: state.page < totalPages,
503
+ canGoPrevious: state.page > ONE,
504
+ allSelected,
505
+ someSelected,
506
+ isMobile,
507
+ isTablet,
508
+ isDesktop
509
+ };
510
+ }, [state, columns]);
511
+ const contextValue = {
512
+ state,
513
+ actions,
514
+ computed
515
+ };
516
+ return /* @__PURE__ */ jsx(TableContext.Provider, { value: contextValue, children });
517
+ }
518
+ function useTableContext() {
519
+ const context = useContext(TableContext);
520
+ if (!context) {
521
+ throw new Error("useTableContext must be used within a TableProvider");
522
+ }
523
+ return context;
524
+ }
525
+
526
+ // src/hooks/useTable.ts
527
+ import { useMemo as useMemo7 } from "react";
528
+
529
+ // src/hooks/useSort.ts
530
+ import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
531
+ function useSort() {
532
+ const { state, actions } = useTableContext();
533
+ const getSortDirection = useCallback2(
534
+ (columnId) => {
535
+ var _a;
536
+ const sort = state.sorting.find((s) => s.columnId === columnId);
537
+ return (_a = sort == null ? void 0 : sort.direction) != null ? _a : null;
538
+ },
539
+ [state.sorting]
540
+ );
541
+ const getSortIndex = useCallback2(
542
+ (columnId) => {
543
+ return state.sorting.findIndex((s) => s.columnId === columnId);
544
+ },
545
+ [state.sorting]
546
+ );
547
+ const isSorted = useCallback2(
548
+ (columnId) => {
549
+ return state.sorting.some((s) => s.columnId === columnId);
550
+ },
551
+ [state.sorting]
552
+ );
553
+ const clearColumnSorting = useCallback2(
554
+ (columnId) => {
555
+ actions.setSorting(columnId, null);
556
+ },
557
+ [actions]
558
+ );
559
+ return useMemo2(
560
+ () => ({
561
+ sorting: state.sorting,
562
+ getSortDirection,
563
+ getSortIndex,
564
+ isSorted,
565
+ setSorting: actions.setSorting,
566
+ toggleSorting: actions.toggleSorting,
567
+ clearSorting: actions.clearSorting,
568
+ clearColumnSorting
569
+ }),
570
+ [state.sorting, getSortDirection, getSortIndex, isSorted, actions, clearColumnSorting]
571
+ );
572
+ }
573
+
574
+ // src/hooks/useFilter.ts
575
+ import { useCallback as useCallback3, useMemo as useMemo3, useState, useEffect as useEffect2 } from "react";
576
+ function useFilter(options = {}) {
577
+ const { debounceMs = DEBOUNCE_DELAY } = options;
578
+ const { state, actions } = useTableContext();
579
+ const [debouncedGlobalFilter, setDebouncedGlobalFilter] = useState(state.globalFilter);
580
+ useEffect2(() => {
581
+ const timer = setTimeout(() => {
582
+ if (debouncedGlobalFilter !== state.globalFilter) {
583
+ actions.setGlobalFilter(debouncedGlobalFilter);
584
+ }
585
+ }, debounceMs);
586
+ return () => clearTimeout(timer);
587
+ }, [debouncedGlobalFilter, debounceMs, actions, state.globalFilter]);
588
+ const hasAnyFilter = useCallback3(() => {
589
+ return state.filters.length > 0 || state.globalFilter.length > 0;
590
+ }, [state.filters, state.globalFilter]);
591
+ const hasFilter = useCallback3(
592
+ (columnId) => {
593
+ return state.filters.some((f) => f.columnId === columnId);
594
+ },
595
+ [state.filters]
596
+ );
597
+ const getFilterValue = useCallback3(
598
+ (columnId) => {
599
+ return state.filters.find((f) => f.columnId === columnId);
600
+ },
601
+ [state.filters]
602
+ );
603
+ const setFilter = useCallback3(
604
+ (columnId, value, operator = "contains") => {
605
+ actions.setFilter(columnId, value, operator);
606
+ },
607
+ [actions]
608
+ );
609
+ const clearGlobalFilter = useCallback3(() => {
610
+ setDebouncedGlobalFilter("");
611
+ actions.setGlobalFilter("");
612
+ }, [actions]);
613
+ const setGlobalFilterDebounced = useCallback3((value) => {
614
+ setDebouncedGlobalFilter(value);
615
+ }, []);
616
+ const openFilterPanel = useCallback3(
617
+ (columnId) => {
618
+ actions.setActiveFilterColumn(columnId);
619
+ },
620
+ [actions]
621
+ );
622
+ const closeFilterPanel = useCallback3(() => {
623
+ actions.setActiveFilterColumn(null);
624
+ }, [actions]);
625
+ return useMemo3(
626
+ () => ({
627
+ filters: state.filters,
628
+ globalFilter: debouncedGlobalFilter,
629
+ activeFilterColumnId: state.activeFilterColumnId,
630
+ hasAnyFilter,
631
+ hasFilter,
632
+ getFilterValue,
633
+ setFilter,
634
+ removeFilter: actions.removeFilter,
635
+ clearFilters: actions.clearFilters,
636
+ setGlobalFilter: setGlobalFilterDebounced,
637
+ clearGlobalFilter,
638
+ openFilterPanel,
639
+ closeFilterPanel
640
+ }),
641
+ [
642
+ state.filters,
643
+ state.activeFilterColumnId,
644
+ debouncedGlobalFilter,
645
+ hasAnyFilter,
646
+ hasFilter,
647
+ getFilterValue,
648
+ setFilter,
649
+ actions,
650
+ setGlobalFilterDebounced,
651
+ clearGlobalFilter,
652
+ openFilterPanel,
653
+ closeFilterPanel
654
+ ]
655
+ );
656
+ }
657
+
658
+ // src/hooks/usePagination.ts
659
+ import { useCallback as useCallback4, useMemo as useMemo4 } from "react";
660
+ function usePagination(options = {}) {
661
+ const { pageSizeOptions = [10, 20, 50, 100] } = options;
662
+ const { state, actions, computed } = useTableContext();
663
+ const setPage = useCallback4(
664
+ (page) => {
665
+ const validPage = Math.max(ONE, Math.min(page, computed.totalPages));
666
+ actions.setPage(validPage);
667
+ },
668
+ [actions, computed.totalPages]
669
+ );
670
+ const setPageSize = useCallback4(
671
+ (pageSize) => {
672
+ actions.setPageSize(pageSize);
673
+ },
674
+ [actions]
675
+ );
676
+ const goToFirstPage = useCallback4(() => {
677
+ actions.setPage(ONE);
678
+ }, [actions]);
679
+ const goToLastPage = useCallback4(() => {
680
+ actions.setPage(computed.totalPages);
681
+ }, [actions, computed.totalPages]);
682
+ const goToNextPage = useCallback4(() => {
683
+ if (computed.canGoNext) {
684
+ actions.setPage(state.page + ONE);
685
+ }
686
+ }, [actions, computed.canGoNext, state.page]);
687
+ const goToPreviousPage = useCallback4(() => {
688
+ if (computed.canGoPrevious) {
689
+ actions.setPage(state.page - ONE);
690
+ }
691
+ }, [actions, computed.canGoPrevious, state.page]);
692
+ const canGoToNextPage = useCallback4(() => {
693
+ return computed.canGoNext;
694
+ }, [computed.canGoNext]);
695
+ const canGoToPreviousPage = useCallback4(() => {
696
+ return computed.canGoPrevious;
697
+ }, [computed.canGoPrevious]);
698
+ const getPageRange = useCallback4(() => {
699
+ const start = (state.page - ONE) * state.pageSize + ONE;
700
+ const end = Math.min(state.page * state.pageSize, computed.sortedData.length);
701
+ return { start, end };
702
+ }, [state.page, state.pageSize, computed.sortedData.length]);
703
+ const paginationInfo = useMemo4(() => {
704
+ const { start, end } = getPageRange();
705
+ return {
706
+ page: state.page,
707
+ pageSize: state.pageSize,
708
+ totalItems: computed.sortedData.length,
709
+ totalPages: computed.totalPages,
710
+ startIndex: start,
711
+ endIndex: end,
712
+ isFirstPage: state.page === ONE,
713
+ isLastPage: state.page === computed.totalPages
714
+ };
715
+ }, [state.page, state.pageSize, computed.sortedData.length, computed.totalPages, getPageRange]);
716
+ return useMemo4(
717
+ () => ({
718
+ ...paginationInfo,
719
+ pageSizeOptions,
720
+ setPage,
721
+ setPageSize,
722
+ goToFirstPage,
723
+ goToLastPage,
724
+ goToNextPage,
725
+ goToPreviousPage,
726
+ canGoToNextPage,
727
+ canGoToPreviousPage,
728
+ getPageRange
729
+ }),
730
+ [
731
+ paginationInfo,
732
+ pageSizeOptions,
733
+ setPage,
734
+ setPageSize,
735
+ goToFirstPage,
736
+ goToLastPage,
737
+ goToNextPage,
738
+ goToPreviousPage,
739
+ canGoToNextPage,
740
+ canGoToPreviousPage,
741
+ getPageRange
742
+ ]
743
+ );
744
+ }
745
+
746
+ // src/hooks/useDragDrop.ts
747
+ import { useCallback as useCallback5, useRef, useState as useState2, useMemo as useMemo5 } from "react";
748
+ function useDragDrop(options = {}) {
749
+ const { onReorder, enabled = true } = options;
750
+ const { state, actions } = useTableContext();
751
+ const [dragOverColumnId, setDragOverColumnId] = useState2(null);
752
+ const dragStartPosition = useRef(null);
753
+ const isDragValid = useRef(false);
754
+ const handleDragStart = useCallback5(
755
+ (columnId) => (event) => {
756
+ if (!enabled) {
757
+ event.preventDefault();
758
+ return;
759
+ }
760
+ dragStartPosition.current = { x: event.clientX, y: event.clientY };
761
+ isDragValid.current = false;
762
+ event.dataTransfer.effectAllowed = "move";
763
+ event.dataTransfer.setData("text/plain", columnId);
764
+ requestAnimationFrame(() => {
765
+ actions.setDraggingColumn(columnId);
766
+ });
767
+ },
768
+ [enabled, actions]
769
+ );
770
+ const handleDragOver = useCallback5(
771
+ (columnId) => (event) => {
772
+ if (!enabled || !state.draggingColumnId) return;
773
+ event.preventDefault();
774
+ event.dataTransfer.dropEffect = "move";
775
+ if (dragStartPosition.current) {
776
+ const dx = Math.abs(event.clientX - dragStartPosition.current.x);
777
+ const dy = Math.abs(event.clientY - dragStartPosition.current.y);
778
+ if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
779
+ isDragValid.current = true;
780
+ }
781
+ }
782
+ if (columnId !== state.draggingColumnId && columnId !== dragOverColumnId) {
783
+ setDragOverColumnId(columnId);
784
+ }
785
+ },
786
+ [enabled, state.draggingColumnId, dragOverColumnId]
787
+ );
788
+ const handleDragLeave = useCallback5(() => {
789
+ setDragOverColumnId(null);
790
+ }, []);
791
+ const handleDrop = useCallback5(
792
+ (targetColumnId) => (event) => {
793
+ event.preventDefault();
794
+ if (!enabled || !state.draggingColumnId || !isDragValid.current) return;
795
+ if (state.draggingColumnId === targetColumnId) return;
796
+ const sourceIndex = state.columnStates.findIndex((c) => c.id === state.draggingColumnId);
797
+ const targetIndex = state.columnStates.findIndex((c) => c.id === targetColumnId);
798
+ if (sourceIndex !== -1 && targetIndex !== -1) {
799
+ actions.reorderColumn(state.draggingColumnId, targetColumnId);
800
+ onReorder == null ? void 0 : onReorder({
801
+ sourceId: state.draggingColumnId,
802
+ targetId: targetColumnId,
803
+ sourceIndex,
804
+ targetIndex
805
+ });
806
+ }
807
+ setDragOverColumnId(null);
808
+ },
809
+ [enabled, state.draggingColumnId, state.columnStates, actions, onReorder]
810
+ );
811
+ const handleDragEnd = useCallback5(() => {
812
+ actions.setDraggingColumn(null);
813
+ setDragOverColumnId(null);
814
+ dragStartPosition.current = null;
815
+ isDragValid.current = false;
816
+ }, [actions]);
817
+ const getDragHandleProps = useCallback5(
818
+ (columnId) => ({
819
+ draggable: enabled,
820
+ onDragStart: handleDragStart(columnId),
821
+ onDragEnd: handleDragEnd
822
+ }),
823
+ [enabled, handleDragStart, handleDragEnd]
824
+ );
825
+ const getDropTargetProps = useCallback5(
826
+ (columnId) => ({
827
+ onDragOver: handleDragOver(columnId),
828
+ onDragLeave: handleDragLeave,
829
+ onDrop: handleDrop(columnId)
830
+ }),
831
+ [handleDragOver, handleDragLeave, handleDrop]
832
+ );
833
+ return useMemo5(
834
+ () => ({
835
+ isDragging: !!state.draggingColumnId,
836
+ draggingColumnId: state.draggingColumnId,
837
+ dragOverColumnId,
838
+ handleDragStart,
839
+ handleDragOver,
840
+ handleDragEnd,
841
+ handleDrop,
842
+ handleDragLeave,
843
+ getDragHandleProps,
844
+ getDropTargetProps
845
+ }),
846
+ [
847
+ state.draggingColumnId,
848
+ dragOverColumnId,
849
+ handleDragStart,
850
+ handleDragOver,
851
+ handleDragEnd,
852
+ handleDrop,
853
+ handleDragLeave,
854
+ getDragHandleProps,
855
+ getDropTargetProps
856
+ ]
857
+ );
858
+ }
859
+
860
+ // src/hooks/useBreakpoint.ts
861
+ import { useMemo as useMemo6 } from "react";
862
+ function useBreakpoint() {
863
+ const { state, computed } = useTableContext();
864
+ const breakpointValue = useMemo6(
865
+ () => (value, fallback) => {
866
+ if (value === null || value === void 0) return fallback;
867
+ if (typeof value !== "object" || !("mobile" in value || "tablet" in value || "desktop" in value)) {
868
+ return value;
869
+ }
870
+ const responsiveValue = value;
871
+ const breakpointOrder = ["mobile", "tablet", "desktop"];
872
+ const currentIndex = breakpointOrder.indexOf(state.currentBreakpoint);
873
+ for (let i = currentIndex; i >= 0; i--) {
874
+ const bp = breakpointOrder[i];
875
+ if (responsiveValue[bp] !== void 0) {
876
+ return responsiveValue[bp];
877
+ }
878
+ }
879
+ for (let i = currentIndex + 1; i < breakpointOrder.length; i++) {
880
+ const bp = breakpointOrder[i];
881
+ if (responsiveValue[bp] !== void 0) {
882
+ return responsiveValue[bp];
883
+ }
884
+ }
885
+ return fallback;
886
+ },
887
+ [state.currentBreakpoint]
888
+ );
889
+ const shouldShowMobileView = useMemo6(() => {
890
+ const mobileBreakpoints = ["mobile"];
891
+ if (state.mobileBreakpoint === "tablet") {
892
+ mobileBreakpoints.push("tablet");
893
+ }
894
+ return mobileBreakpoints.includes(state.currentBreakpoint);
895
+ }, [state.currentBreakpoint, state.mobileBreakpoint]);
896
+ return useMemo6(
897
+ () => ({
898
+ currentBreakpoint: state.currentBreakpoint,
899
+ isMobile: computed.isMobile,
900
+ isTablet: computed.isTablet,
901
+ isDesktop: computed.isDesktop,
902
+ isMobileOrTablet: computed.isMobile || computed.isTablet,
903
+ isTabletOrDesktop: computed.isTablet || computed.isDesktop,
904
+ breakpointValue,
905
+ shouldShowMobileView
906
+ }),
907
+ [state.currentBreakpoint, computed, breakpointValue, shouldShowMobileView]
908
+ );
909
+ }
910
+
911
+ // src/hooks/useTable.ts
912
+ function useTable() {
913
+ const { state, actions, computed } = useTableContext();
914
+ const sort = useSort();
915
+ const filter = useFilter();
916
+ const pagination = usePagination();
917
+ const dragDrop = useDragDrop();
918
+ const breakpoint = useBreakpoint();
919
+ const selection = useMemo7(
920
+ () => ({
921
+ selectedIds: state.selectedIds,
922
+ allSelected: computed.allSelected,
923
+ someSelected: computed.someSelected,
924
+ selectRow: actions.selectRow,
925
+ deselectRow: actions.deselectRow,
926
+ toggleRow: actions.toggleRow,
927
+ selectAll: actions.selectAll,
928
+ deselectAll: actions.deselectAll,
929
+ isSelected: (id) => state.selectedIds.has(id)
930
+ }),
931
+ [state.selectedIds, computed.allSelected, computed.someSelected, actions]
932
+ );
933
+ const expansion = useMemo7(
934
+ () => ({
935
+ expandedIds: state.expandedIds,
936
+ expandRow: actions.expandRow,
937
+ collapseRow: actions.collapseRow,
938
+ toggleRow: actions.toggleRowExpansion,
939
+ isExpanded: (id) => state.expandedIds.has(id)
940
+ }),
941
+ [state.expandedIds, actions]
942
+ );
943
+ const columnsApi = useMemo7(
944
+ () => ({
945
+ states: state.columnStates,
946
+ reorder: actions.reorderColumn,
947
+ resize: actions.resizeColumn,
948
+ toggleVisibility: actions.toggleColumnVisibility,
949
+ reset: actions.resetColumns,
950
+ getWidth: (columnId) => {
951
+ var _a;
952
+ const colState = state.columnStates.find((c) => c.id === columnId);
953
+ return (_a = colState == null ? void 0 : colState.width) != null ? _a : 150;
954
+ },
955
+ isVisible: (columnId) => {
956
+ const colState = state.columnStates.find((c) => c.id === columnId);
957
+ return (colState == null ? void 0 : colState.visible) !== false;
958
+ }
959
+ }),
960
+ [state.columnStates, actions]
961
+ );
962
+ const mobile = useMemo7(
963
+ () => ({
964
+ showDrawer: state.showMobileDrawer,
965
+ drawerContent: state.mobileDrawerContent,
966
+ openDrawer: actions.openMobileDrawer,
967
+ closeDrawer: actions.closeMobileDrawer
968
+ }),
969
+ [state.showMobileDrawer, state.mobileDrawerContent, actions]
970
+ );
971
+ return useMemo7(
972
+ () => ({
973
+ data: state.data,
974
+ filteredData: computed.filteredData,
975
+ sortedData: computed.sortedData,
976
+ paginatedData: computed.paginatedData,
977
+ columns: state.columns,
978
+ visibleColumns: computed.visibleColumns,
979
+ loading: state.loading,
980
+ error: state.error,
981
+ isEmpty: computed.paginatedData.length === 0 && !state.loading,
982
+ sort,
983
+ filter,
984
+ pagination,
985
+ dragDrop,
986
+ breakpoint,
987
+ selection,
988
+ expansion,
989
+ columnApi: columnsApi,
990
+ mobile,
991
+ theme: state.theme,
992
+ translations: state.translations,
993
+ refresh: actions.refresh,
994
+ reset: actions.reset
995
+ }),
996
+ [state, computed, sort, filter, pagination, dragDrop, breakpoint, selection, expansion, columnsApi, mobile, actions]
997
+ );
998
+ }
999
+
1000
+ // src/components/GridHeader/GridHeader.tsx
1001
+ import { useMemo as useMemo8, useCallback as useCallback7, useState as useState4, useRef as useRef3 } from "react";
1002
+
1003
+ // src/components/FilterPopup/FilterPopup.tsx
1004
+ import { useState as useState3, useCallback as useCallback6, useEffect as useEffect3, useRef as useRef2 } from "react";
1005
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
1006
+ var TEXT_OPERATORS = ["contains", "equals", "startsWith", "endsWith", "notContains", "notEquals"];
1007
+ var NUMBER_OPERATORS = ["equals", "notEquals", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual"];
1008
+ var OPERATOR_LABELS = {
1009
+ equals: "Equals",
1010
+ notEquals: "Not equals",
1011
+ contains: "Contains",
1012
+ notContains: "Not contains",
1013
+ startsWith: "Starts with",
1014
+ endsWith: "Ends with",
1015
+ greaterThan: "Greater than",
1016
+ lessThan: "Less than",
1017
+ greaterThanOrEqual: "Greater or equal",
1018
+ lessThanOrEqual: "Less or equal",
1019
+ between: "Between",
1020
+ isEmpty: "Is empty",
1021
+ isNotEmpty: "Is not empty"
1022
+ };
1023
+ function FilterPopup({
1024
+ columnId,
1025
+ columnHeader,
1026
+ filterType = "text",
1027
+ filterOptions,
1028
+ currentValue,
1029
+ currentOperator = "contains",
1030
+ position,
1031
+ onApply,
1032
+ onClear,
1033
+ onClose,
1034
+ className = "",
1035
+ style
1036
+ }) {
1037
+ var _a, _b;
1038
+ const { state } = useTableContext();
1039
+ const { translations } = state;
1040
+ const popupRef = useRef2(null);
1041
+ const [value, setValue] = useState3(currentValue ? String(currentValue) : "");
1042
+ const [operator, setOperator] = useState3(currentOperator);
1043
+ const operators = filterType === "number" ? NUMBER_OPERATORS : TEXT_OPERATORS;
1044
+ useEffect3(() => {
1045
+ const handleClickOutside = (event) => {
1046
+ if (popupRef.current && !popupRef.current.contains(event.target)) {
1047
+ onClose();
1048
+ }
1049
+ };
1050
+ const handleEscape = (event) => {
1051
+ if (event.key === "Escape") {
1052
+ onClose();
1053
+ }
1054
+ };
1055
+ document.addEventListener("mousedown", handleClickOutside);
1056
+ document.addEventListener("keydown", handleEscape);
1057
+ return () => {
1058
+ document.removeEventListener("mousedown", handleClickOutside);
1059
+ document.removeEventListener("keydown", handleEscape);
1060
+ };
1061
+ }, [onClose]);
1062
+ const handleApply = useCallback6(() => {
1063
+ if (value.trim()) {
1064
+ onApply(filterType === "number" ? Number(value) : value, operator);
1065
+ }
1066
+ onClose();
1067
+ }, [value, operator, filterType, onApply, onClose]);
1068
+ const handleClear = useCallback6(() => {
1069
+ setValue("");
1070
+ onClear();
1071
+ onClose();
1072
+ }, [onClear, onClose]);
1073
+ const handleKeyDown = useCallback6(
1074
+ (e) => {
1075
+ if (e.key === "Enter") {
1076
+ handleApply();
1077
+ }
1078
+ },
1079
+ [handleApply]
1080
+ );
1081
+ return /* @__PURE__ */ jsxs(
1082
+ "div",
1083
+ {
1084
+ ref: popupRef,
1085
+ className: `filter-popup absolute z-50 bg-theme-primary border border-theme-border rounded-lg shadow-xl min-w-[280px] ${className}`,
1086
+ style: {
1087
+ top: (_a = position == null ? void 0 : position.top) != null ? _a : "100%",
1088
+ left: (_b = position == null ? void 0 : position.left) != null ? _b : 0,
1089
+ marginTop: 4,
1090
+ ...style
1091
+ },
1092
+ children: [
1093
+ /* @__PURE__ */ jsx2("div", { className: "filter-popup-header px-4 py-3 border-b border-theme-border", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1094
+ /* @__PURE__ */ jsx2("span", { className: "font-medium text-theme-primary", children: columnHeader }),
1095
+ /* @__PURE__ */ jsx2(
1096
+ "button",
1097
+ {
1098
+ onClick: onClose,
1099
+ className: "p-1 rounded hover:bg-theme-hover text-theme-muted",
1100
+ "aria-label": "Close",
1101
+ children: /* @__PURE__ */ jsx2("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
1102
+ }
1103
+ )
1104
+ ] }) }),
1105
+ /* @__PURE__ */ jsx2("div", { className: "filter-popup-body p-4 space-y-4", children: filterType === "select" && filterOptions ? /* @__PURE__ */ jsxs(
1106
+ "select",
1107
+ {
1108
+ value: String(value),
1109
+ onChange: (e) => setValue(e.target.value),
1110
+ className: "w-full px-3 py-2 text-sm rounded border border-theme-border bg-theme-secondary text-theme-primary focus:outline-none focus:ring-2 focus:ring-accent-primary",
1111
+ children: [
1112
+ /* @__PURE__ */ jsx2("option", { value: "", children: "Select..." }),
1113
+ filterOptions.map((opt) => /* @__PURE__ */ jsx2("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
1114
+ ]
1115
+ }
1116
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1117
+ /* @__PURE__ */ jsxs("div", { children: [
1118
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs text-theme-muted mb-1", children: "Operator" }),
1119
+ /* @__PURE__ */ jsx2(
1120
+ "select",
1121
+ {
1122
+ value: operator,
1123
+ onChange: (e) => setOperator(e.target.value),
1124
+ className: "w-full px-3 py-2 text-sm rounded border border-theme-border bg-theme-secondary text-theme-primary focus:outline-none focus:ring-2 focus:ring-accent-primary",
1125
+ children: operators.map((op) => /* @__PURE__ */ jsx2("option", { value: op, children: OPERATOR_LABELS[op] }, op))
1126
+ }
1127
+ )
1128
+ ] }),
1129
+ /* @__PURE__ */ jsxs("div", { children: [
1130
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs text-theme-muted mb-1", children: "Value" }),
1131
+ /* @__PURE__ */ jsx2(
1132
+ "input",
1133
+ {
1134
+ type: filterType === "number" ? "number" : "text",
1135
+ value,
1136
+ onChange: (e) => setValue(e.target.value),
1137
+ onKeyDown: handleKeyDown,
1138
+ placeholder: `Filter ${columnHeader}...`,
1139
+ className: "w-full px-3 py-2 text-sm rounded border border-theme-border bg-theme-secondary text-theme-primary focus:outline-none focus:ring-2 focus:ring-accent-primary",
1140
+ autoFocus: true
1141
+ }
1142
+ )
1143
+ ] })
1144
+ ] }) }),
1145
+ /* @__PURE__ */ jsxs("div", { className: "filter-popup-footer px-4 py-3 border-t border-theme-border flex items-center justify-between", children: [
1146
+ /* @__PURE__ */ jsx2(
1147
+ "button",
1148
+ {
1149
+ onClick: handleClear,
1150
+ className: "px-3 py-1.5 text-sm text-theme-muted hover:text-theme-primary transition-colors",
1151
+ children: translations.clearFilter
1152
+ }
1153
+ ),
1154
+ /* @__PURE__ */ jsx2(
1155
+ "button",
1156
+ {
1157
+ onClick: handleApply,
1158
+ className: "px-4 py-1.5 text-sm bg-accent-primary text-white rounded hover:opacity-90 transition-opacity",
1159
+ children: translations.apply
1160
+ }
1161
+ )
1162
+ ] })
1163
+ ]
1164
+ }
1165
+ );
1166
+ }
1167
+
1168
+ // src/components/GridHeader/GridHeader.tsx
1169
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1170
+ var FilterIcon = ({ active = false }) => /* @__PURE__ */ jsx3(
1171
+ "svg",
1172
+ {
1173
+ className: `w-3.5 h-3.5 ${active ? "text-accent-primary" : "text-theme-muted"}`,
1174
+ fill: "none",
1175
+ viewBox: "0 0 24 24",
1176
+ stroke: "currentColor",
1177
+ strokeWidth: 2,
1178
+ children: /* @__PURE__ */ jsx3(
1179
+ "path",
1180
+ {
1181
+ strokeLinecap: "round",
1182
+ strokeLinejoin: "round",
1183
+ d: "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
1184
+ }
1185
+ )
1186
+ }
1187
+ );
1188
+ var SortIcon = ({ direction }) => /* @__PURE__ */ jsx3(
1189
+ "svg",
1190
+ {
1191
+ className: `w-3.5 h-3.5 ${direction ? "text-accent-primary" : "text-theme-muted"}`,
1192
+ fill: "none",
1193
+ viewBox: "0 0 24 24",
1194
+ stroke: "currentColor",
1195
+ strokeWidth: 2,
1196
+ children: direction === "asc" ? /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 15l7-7 7 7" }) : direction === "desc" ? /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) : /* @__PURE__ */ jsx3("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" })
1197
+ }
1198
+ );
1199
+ var ALIGN_CLASSES = {
1200
+ left: "text-left justify-start",
1201
+ center: "text-center justify-center",
1202
+ right: "text-right justify-end"
1203
+ };
1204
+ function HeaderCell({
1205
+ column,
1206
+ columnState,
1207
+ sortDirection,
1208
+ sortIndex,
1209
+ isMultiSort = false,
1210
+ enableSort = true,
1211
+ enableFilter = true,
1212
+ enableDragDrop = true,
1213
+ enableResize = true,
1214
+ hasFilter = false,
1215
+ isDragging = false,
1216
+ isDragOver = false,
1217
+ onSort,
1218
+ onFilterOpen,
1219
+ onResizeStart,
1220
+ dragHandleProps,
1221
+ dropTargetProps
1222
+ }) {
1223
+ const isSortable = enableSort && column.sortable !== false;
1224
+ const isFilterable = enableFilter && column.filterable !== false;
1225
+ const isDraggable = enableDragDrop && column.draggable !== false;
1226
+ const isResizable = enableResize && column.resizable !== false;
1227
+ const headerContent = useMemo8(() => {
1228
+ if (typeof column.header === "function") {
1229
+ return column.header();
1230
+ }
1231
+ return column.header;
1232
+ }, [column.header]);
1233
+ const handleClick = useCallback7(() => {
1234
+ if (isSortable) {
1235
+ onSort == null ? void 0 : onSort();
1236
+ }
1237
+ }, [isSortable, onSort]);
1238
+ const handleFilterClick = useCallback7(
1239
+ (e) => {
1240
+ e.stopPropagation();
1241
+ onFilterOpen == null ? void 0 : onFilterOpen();
1242
+ },
1243
+ [onFilterOpen]
1244
+ );
1245
+ const cellClasses = useMemo8(() => {
1246
+ const classes = [
1247
+ "grid-header-cell",
1248
+ "relative",
1249
+ "flex",
1250
+ "items-center",
1251
+ "gap-2",
1252
+ "px-4",
1253
+ "py-3",
1254
+ "font-medium",
1255
+ "text-sm",
1256
+ "text-theme-secondary",
1257
+ "select-none",
1258
+ "border-b",
1259
+ "border-theme-border",
1260
+ "transition-colors",
1261
+ ALIGN_CLASSES[column.align || "left"]
1262
+ ];
1263
+ if (isSortable) {
1264
+ classes.push("cursor-pointer", "hover:text-theme-primary", "hover:bg-theme-hover");
1265
+ }
1266
+ if (isDragging) {
1267
+ classes.push("opacity-50");
1268
+ }
1269
+ if (isDragOver) {
1270
+ classes.push("bg-accent-primary/10", "border-l-2", "border-l-accent-primary");
1271
+ }
1272
+ return classes.join(" ");
1273
+ }, [column.align, isSortable, isDragging, isDragOver]);
1274
+ return /* @__PURE__ */ jsxs2(
1275
+ "div",
1276
+ {
1277
+ className: `${cellClasses} ${column.headerClassName || ""} ${column.sticky ? "shadow-[2px_0_4px_-2px_rgba(0,0,0,0.2)]" : ""}`,
1278
+ style: {
1279
+ width: columnState.width,
1280
+ minWidth: column.minWidth || MIN_COLUMN_WIDTH,
1281
+ maxWidth: column.maxWidth || MAX_COLUMN_WIDTH,
1282
+ flexShrink: 0,
1283
+ ...column.sticky && {
1284
+ position: "sticky",
1285
+ [column.sticky]: 0,
1286
+ zIndex: 2,
1287
+ backgroundColor: "var(--bg-secondary, #2b2b2b)"
1288
+ },
1289
+ ...column.headerStyle
1290
+ },
1291
+ role: "columnheader",
1292
+ "aria-sort": sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : "none",
1293
+ onClick: handleClick,
1294
+ ...isDraggable ? { ...dragHandleProps, ...dropTargetProps } : {},
1295
+ children: [
1296
+ /* @__PURE__ */ jsx3("span", { className: "grid-header-content truncate flex-1", children: headerContent }),
1297
+ isSortable && /* @__PURE__ */ jsxs2("span", { className: "grid-header-sort flex items-center gap-0.5", children: [
1298
+ /* @__PURE__ */ jsx3(SortIcon, { direction: sortDirection != null ? sortDirection : null }),
1299
+ isMultiSort && sortIndex !== void 0 && sortIndex >= 0 && sortDirection && /* @__PURE__ */ jsx3("span", { className: "text-xs text-theme-muted", children: sortIndex + 1 })
1300
+ ] }),
1301
+ isFilterable && /* @__PURE__ */ jsx3(
1302
+ "button",
1303
+ {
1304
+ onClick: handleFilterClick,
1305
+ className: "grid-header-filter p-1 rounded hover:bg-theme-tertiary",
1306
+ "aria-label": "Filter column",
1307
+ children: /* @__PURE__ */ jsx3(FilterIcon, { active: hasFilter })
1308
+ }
1309
+ ),
1310
+ isResizable && /* @__PURE__ */ jsx3(
1311
+ "div",
1312
+ {
1313
+ className: "grid-header-resize absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-accent-primary",
1314
+ onMouseDown: onResizeStart,
1315
+ onClick: (e) => e.stopPropagation()
1316
+ }
1317
+ )
1318
+ ]
1319
+ }
1320
+ );
1321
+ }
1322
+ function GridHeader({
1323
+ columns,
1324
+ columnStates,
1325
+ className = "",
1326
+ style,
1327
+ sticky = true,
1328
+ enableSort = true,
1329
+ enableFilter = true,
1330
+ enableDragDrop = true,
1331
+ enableResize = true,
1332
+ enableSelection = false,
1333
+ allSelected = false,
1334
+ someSelected = false,
1335
+ onSelectAll,
1336
+ onSort,
1337
+ onFilterOpen,
1338
+ getSortDirection
1339
+ }) {
1340
+ const { state, actions } = useTableContext();
1341
+ const dragDrop = useDragDrop();
1342
+ const [resizingColumn, setResizingColumn] = useState4(null);
1343
+ const [activeFilterColumn, setActiveFilterColumn] = useState4(null);
1344
+ const filterButtonRefs = useRef3(/* @__PURE__ */ new Map());
1345
+ const resizeStartX = useRef3(0);
1346
+ const resizeStartWidth = useRef3(0);
1347
+ const handleFilterClick = useCallback7((columnId) => {
1348
+ setActiveFilterColumn(activeFilterColumn === columnId ? null : columnId);
1349
+ }, [activeFilterColumn]);
1350
+ const handleFilterApply = useCallback7((columnId, value, operator) => {
1351
+ actions.setFilter(columnId, value, operator);
1352
+ setActiveFilterColumn(null);
1353
+ }, [actions]);
1354
+ const handleFilterClear = useCallback7((columnId) => {
1355
+ actions.removeFilter(columnId);
1356
+ setActiveFilterColumn(null);
1357
+ }, [actions]);
1358
+ const handleFilterClose = useCallback7(() => {
1359
+ setActiveFilterColumn(null);
1360
+ }, []);
1361
+ const visibleColumns = useMemo8(() => {
1362
+ return columns.filter((col) => {
1363
+ const colState = columnStates.find((cs) => cs.id === col.id);
1364
+ return (colState == null ? void 0 : colState.visible) !== false;
1365
+ }).sort((a, b) => {
1366
+ var _a, _b;
1367
+ const aState = columnStates.find((cs) => cs.id === a.id);
1368
+ const bState = columnStates.find((cs) => cs.id === b.id);
1369
+ return ((_a = aState == null ? void 0 : aState.order) != null ? _a : 0) - ((_b = bState == null ? void 0 : bState.order) != null ? _b : 0);
1370
+ });
1371
+ }, [columns, columnStates]);
1372
+ const handleResizeStart = useCallback7(
1373
+ (columnId, currentWidth) => (event) => {
1374
+ event.preventDefault();
1375
+ setResizingColumn(columnId);
1376
+ resizeStartX.current = event.clientX;
1377
+ resizeStartWidth.current = currentWidth;
1378
+ const handleMouseMove = (e) => {
1379
+ const delta = e.clientX - resizeStartX.current;
1380
+ const newWidth = Math.max(
1381
+ MIN_COLUMN_WIDTH,
1382
+ Math.min(MAX_COLUMN_WIDTH, resizeStartWidth.current + delta)
1383
+ );
1384
+ actions.resizeColumn(columnId, newWidth);
1385
+ };
1386
+ const handleMouseUp = () => {
1387
+ setResizingColumn(null);
1388
+ document.removeEventListener("mousemove", handleMouseMove);
1389
+ document.removeEventListener("mouseup", handleMouseUp);
1390
+ };
1391
+ document.addEventListener("mousemove", handleMouseMove);
1392
+ document.addEventListener("mouseup", handleMouseUp);
1393
+ },
1394
+ [actions]
1395
+ );
1396
+ const headerClasses = useMemo8(() => {
1397
+ const classes = ["grid-header", "flex", "bg-theme-secondary"];
1398
+ if (sticky) {
1399
+ classes.push("sticky", "top-0", "z-10");
1400
+ }
1401
+ return classes.join(" ");
1402
+ }, [sticky]);
1403
+ return /* @__PURE__ */ jsxs2("div", { className: `${headerClasses} ${className}`, style, role: "row", children: [
1404
+ enableSelection && /* @__PURE__ */ jsx3("div", { className: "grid-header-select flex items-center px-2 border-b border-theme-border", children: /* @__PURE__ */ jsx3(
1405
+ "input",
1406
+ {
1407
+ type: "checkbox",
1408
+ checked: allSelected,
1409
+ ref: (el) => {
1410
+ if (el) el.indeterminate = someSelected && !allSelected;
1411
+ },
1412
+ onChange: onSelectAll,
1413
+ className: "w-4 h-4 rounded border-theme-border",
1414
+ "aria-label": "Select all rows"
1415
+ }
1416
+ ) }),
1417
+ visibleColumns.map((col, index) => {
1418
+ const colState = columnStates.find((cs) => cs.id === col.id) || {
1419
+ id: col.id,
1420
+ visible: true,
1421
+ width: 150,
1422
+ order: index,
1423
+ pinned: null
1424
+ };
1425
+ const sortDir = getSortDirection ? getSortDirection(col.id) : null;
1426
+ const sortIdx = state.sorting.findIndex((s) => s.columnId === col.id);
1427
+ const hasFilter = state.filters.some((f) => f.columnId === col.id);
1428
+ const existingFilter = state.filters.find((f) => f.columnId === col.id);
1429
+ const headerText = typeof col.header === "string" ? col.header : col.id;
1430
+ return /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
1431
+ /* @__PURE__ */ jsx3(
1432
+ HeaderCell,
1433
+ {
1434
+ column: col,
1435
+ columnState: colState,
1436
+ sortDirection: sortDir,
1437
+ sortIndex: sortIdx,
1438
+ isMultiSort: state.sorting.length > 1,
1439
+ enableSort,
1440
+ enableFilter,
1441
+ enableDragDrop,
1442
+ enableResize,
1443
+ hasFilter,
1444
+ isDragging: dragDrop.draggingColumnId === col.id,
1445
+ isDragOver: dragDrop.dragOverColumnId === col.id,
1446
+ onSort: () => actions.toggleSorting(col.id),
1447
+ onFilterOpen: () => handleFilterClick(col.id),
1448
+ onResizeStart: handleResizeStart(col.id, colState.width),
1449
+ dragHandleProps: dragDrop.getDragHandleProps(col.id),
1450
+ dropTargetProps: dragDrop.getDropTargetProps(col.id)
1451
+ }
1452
+ ),
1453
+ activeFilterColumn === col.id && /* @__PURE__ */ jsx3(
1454
+ FilterPopup,
1455
+ {
1456
+ columnId: col.id,
1457
+ columnHeader: headerText,
1458
+ filterType: col.filterType || "text",
1459
+ filterOptions: col.filterOptions,
1460
+ currentValue: existingFilter == null ? void 0 : existingFilter.value,
1461
+ currentOperator: existingFilter == null ? void 0 : existingFilter.operator,
1462
+ onApply: (value, operator) => handleFilterApply(col.id, value, operator),
1463
+ onClear: () => handleFilterClear(col.id),
1464
+ onClose: handleFilterClose
1465
+ }
1466
+ )
1467
+ ] }, col.id);
1468
+ })
1469
+ ] });
1470
+ }
1471
+
1472
+ // src/components/GridBody/GridBody.tsx
1473
+ import { useCallback as useCallback10 } from "react";
1474
+
1475
+ // src/components/GridRow/GridRow.tsx
1476
+ import { useCallback as useCallback9, useMemo as useMemo10, useState as useState5 } from "react";
1477
+
1478
+ // src/components/GridCell/GridCell.tsx
1479
+ import { useMemo as useMemo9, useCallback as useCallback8 } from "react";
1480
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1481
+ var ALIGN_CLASSES2 = {
1482
+ left: "text-left justify-start",
1483
+ center: "text-center justify-center",
1484
+ right: "text-right justify-end"
1485
+ };
1486
+ function GridCell({
1487
+ column,
1488
+ row,
1489
+ rowIndex,
1490
+ value,
1491
+ width,
1492
+ align = "left",
1493
+ className = "",
1494
+ style,
1495
+ showLabel = false,
1496
+ labelText,
1497
+ sticky,
1498
+ stickyOffset = 0,
1499
+ onClick
1500
+ }) {
1501
+ const handleClick = useCallback8(
1502
+ (e) => {
1503
+ if (onClick) {
1504
+ e.stopPropagation();
1505
+ onClick({
1506
+ row,
1507
+ rowIndex,
1508
+ columnId: column.id,
1509
+ value
1510
+ });
1511
+ }
1512
+ },
1513
+ [onClick, row, rowIndex, column.id, value]
1514
+ );
1515
+ const formattedValue = useMemo9(() => {
1516
+ if (column.render) {
1517
+ return column.render(value, row, rowIndex);
1518
+ }
1519
+ if (value === null || value === void 0) {
1520
+ return "-";
1521
+ }
1522
+ if (typeof value === "boolean") {
1523
+ return value ? "Yes" : "No";
1524
+ }
1525
+ if (value instanceof Date) {
1526
+ return value.toLocaleDateString();
1527
+ }
1528
+ return String(value);
1529
+ }, [column, row, rowIndex, value]);
1530
+ const cellStyle = useMemo9(() => {
1531
+ const baseStyle = { ...style };
1532
+ if (width !== void 0) {
1533
+ baseStyle.width = typeof width === "number" ? `${width}px` : width;
1534
+ baseStyle.minWidth = baseStyle.width;
1535
+ baseStyle.maxWidth = baseStyle.width;
1536
+ }
1537
+ if (sticky) {
1538
+ baseStyle.position = "sticky";
1539
+ baseStyle.zIndex = 1;
1540
+ baseStyle.backgroundColor = "var(--bg-primary, #1e1e1e)";
1541
+ if (sticky === "left") {
1542
+ baseStyle.left = stickyOffset;
1543
+ } else if (sticky === "right") {
1544
+ baseStyle.right = stickyOffset;
1545
+ }
1546
+ }
1547
+ return baseStyle;
1548
+ }, [style, width, sticky, stickyOffset]);
1549
+ const alignClass = ALIGN_CLASSES2[align];
1550
+ const stickyClasses = sticky ? "shadow-[2px_0_4px_-2px_rgba(0,0,0,0.2)]" : "";
1551
+ return /* @__PURE__ */ jsxs3(
1552
+ "div",
1553
+ {
1554
+ className: `
1555
+ grid-cell
1556
+ px-4 py-3
1557
+ flex items-center
1558
+ ${alignClass}
1559
+ ${stickyClasses}
1560
+ ${onClick ? "cursor-pointer hover:bg-theme-hover" : ""}
1561
+ ${column.cellClassName || ""}
1562
+ ${className}
1563
+ `.trim(),
1564
+ style: { ...cellStyle, ...column.cellStyle },
1565
+ role: "cell",
1566
+ onClick: onClick ? handleClick : void 0,
1567
+ children: [
1568
+ showLabel && labelText && /* @__PURE__ */ jsxs3("span", { className: "grid-cell-label font-medium text-theme-muted mr-2 text-sm", children: [
1569
+ labelText,
1570
+ ":"
1571
+ ] }),
1572
+ /* @__PURE__ */ jsx4("span", { className: "grid-cell-value truncate", children: formattedValue })
1573
+ ]
1574
+ }
1575
+ );
1576
+ }
1577
+
1578
+ // src/components/GridRow/GridRow.tsx
1579
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1580
+ function GridRow({
1581
+ row,
1582
+ rowIndex,
1583
+ columns,
1584
+ columnStates,
1585
+ isSelected = false,
1586
+ isExpanded = false,
1587
+ isDisabled = false,
1588
+ isMobile = false,
1589
+ showMobileLabels = true,
1590
+ className = "",
1591
+ style,
1592
+ onClick,
1593
+ onDoubleClick,
1594
+ onContextMenu,
1595
+ onCellClick,
1596
+ onSelect,
1597
+ onExpand,
1598
+ enableSelection = false,
1599
+ enableExpansion = false,
1600
+ renderExpansion,
1601
+ getRowId
1602
+ }) {
1603
+ const [isHovered, setIsHovered] = useState5(false);
1604
+ const handleClick = useCallback9(() => {
1605
+ if (isDisabled) return;
1606
+ onClick == null ? void 0 : onClick(row, rowIndex);
1607
+ }, [onClick, row, rowIndex, isDisabled]);
1608
+ const handleDoubleClick = useCallback9(() => {
1609
+ if (isDisabled) return;
1610
+ onDoubleClick == null ? void 0 : onDoubleClick(row, rowIndex);
1611
+ }, [onDoubleClick, row, rowIndex, isDisabled]);
1612
+ const handleContextMenu = useCallback9(
1613
+ (event) => {
1614
+ if (isDisabled) return;
1615
+ onContextMenu == null ? void 0 : onContextMenu(row, rowIndex, event);
1616
+ },
1617
+ [onContextMenu, row, rowIndex, isDisabled]
1618
+ );
1619
+ const handleSelectChange = useCallback9(() => {
1620
+ if (isDisabled) return;
1621
+ onSelect == null ? void 0 : onSelect(!isSelected);
1622
+ }, [onSelect, isSelected, isDisabled]);
1623
+ const handleExpandToggle = useCallback9(() => {
1624
+ if (isDisabled) return;
1625
+ onExpand == null ? void 0 : onExpand(!isExpanded);
1626
+ }, [onExpand, isExpanded, isDisabled]);
1627
+ const visibleColumns = useMemo10(() => {
1628
+ return columns.filter((col) => {
1629
+ const state = columnStates.find((cs) => cs.id === col.id);
1630
+ if ((state == null ? void 0 : state.visible) === false) return false;
1631
+ if (isMobile && col.hiddenOnMobile) return false;
1632
+ return true;
1633
+ }).sort((a, b) => {
1634
+ var _a, _b;
1635
+ const aState = columnStates.find((cs) => cs.id === a.id);
1636
+ const bState = columnStates.find((cs) => cs.id === b.id);
1637
+ return ((_a = aState == null ? void 0 : aState.order) != null ? _a : 0) - ((_b = bState == null ? void 0 : bState.order) != null ? _b : 0);
1638
+ });
1639
+ }, [columns, columnStates, isMobile]);
1640
+ const getCellValue = useCallback9(
1641
+ (col) => {
1642
+ const accessor = col.accessor;
1643
+ if (typeof accessor === "function") {
1644
+ return accessor(row);
1645
+ }
1646
+ return row[accessor];
1647
+ },
1648
+ [row]
1649
+ );
1650
+ const rowClasses = useMemo10(() => {
1651
+ const classes = [
1652
+ "grid-row",
1653
+ "border-b",
1654
+ "border-theme-border",
1655
+ "transition-colors",
1656
+ "duration-150"
1657
+ ];
1658
+ if (isHovered && !isDisabled) {
1659
+ classes.push("bg-theme-hover");
1660
+ }
1661
+ if (isSelected) {
1662
+ classes.push("bg-accent-primary/10");
1663
+ }
1664
+ if (isDisabled) {
1665
+ classes.push("opacity-50", "cursor-not-allowed");
1666
+ } else if (onClick) {
1667
+ classes.push("cursor-pointer");
1668
+ }
1669
+ if (isMobile) {
1670
+ classes.push("flex", "flex-wrap", "gap-2", "p-4");
1671
+ } else {
1672
+ classes.push("flex", "items-stretch");
1673
+ }
1674
+ return classes.join(" ");
1675
+ }, [isHovered, isSelected, isDisabled, onClick, isMobile]);
1676
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1677
+ /* @__PURE__ */ jsxs4(
1678
+ "div",
1679
+ {
1680
+ className: `${rowClasses} ${className}`,
1681
+ style,
1682
+ role: "row",
1683
+ "aria-selected": isSelected,
1684
+ "aria-disabled": isDisabled,
1685
+ onClick: handleClick,
1686
+ onDoubleClick: handleDoubleClick,
1687
+ onContextMenu: handleContextMenu,
1688
+ onMouseEnter: () => setIsHovered(true),
1689
+ onMouseLeave: () => setIsHovered(false),
1690
+ children: [
1691
+ enableSelection && /* @__PURE__ */ jsx5("div", { className: "grid-row-select flex items-center px-2", children: /* @__PURE__ */ jsx5(
1692
+ "input",
1693
+ {
1694
+ type: "checkbox",
1695
+ checked: isSelected,
1696
+ onChange: handleSelectChange,
1697
+ disabled: isDisabled,
1698
+ className: "w-4 h-4 rounded border-theme-border",
1699
+ "aria-label": "Select row"
1700
+ }
1701
+ ) }),
1702
+ enableExpansion && renderExpansion && /* @__PURE__ */ jsx5("div", { className: "grid-row-expand flex items-center px-2", children: /* @__PURE__ */ jsx5(
1703
+ "button",
1704
+ {
1705
+ onClick: handleExpandToggle,
1706
+ disabled: isDisabled,
1707
+ className: "w-6 h-6 flex items-center justify-center rounded hover:bg-theme-tertiary",
1708
+ "aria-label": isExpanded ? "Collapse row" : "Expand row",
1709
+ "aria-expanded": isExpanded,
1710
+ children: /* @__PURE__ */ jsx5(
1711
+ "span",
1712
+ {
1713
+ className: `transform transition-transform duration-200 ${isExpanded ? "rotate-90" : ""}`,
1714
+ children: ">"
1715
+ }
1716
+ )
1717
+ }
1718
+ ) }),
1719
+ visibleColumns.map((col, colIndex) => {
1720
+ var _a, _b;
1721
+ const colState = columnStates.find((cs) => cs.id === col.id);
1722
+ const width = isMobile ? "100%" : colState == null ? void 0 : colState.width;
1723
+ let stickyOffset = 0;
1724
+ if (col.sticky === "left") {
1725
+ for (let i = 0; i < colIndex; i++) {
1726
+ const prevCol = visibleColumns[i];
1727
+ if (prevCol.sticky === "left") {
1728
+ const prevState = columnStates.find((cs) => cs.id === prevCol.id);
1729
+ stickyOffset += (_a = prevState == null ? void 0 : prevState.width) != null ? _a : 150;
1730
+ }
1731
+ }
1732
+ }
1733
+ if (col.sticky === "right") {
1734
+ for (let i = visibleColumns.length - 1; i > colIndex; i--) {
1735
+ const nextCol = visibleColumns[i];
1736
+ if (nextCol.sticky === "right") {
1737
+ const nextState = columnStates.find((cs) => cs.id === nextCol.id);
1738
+ stickyOffset += (_b = nextState == null ? void 0 : nextState.width) != null ? _b : 150;
1739
+ }
1740
+ }
1741
+ }
1742
+ return /* @__PURE__ */ jsx5(
1743
+ GridCell,
1744
+ {
1745
+ column: col,
1746
+ row,
1747
+ rowIndex,
1748
+ value: getCellValue(col),
1749
+ width,
1750
+ align: col.align,
1751
+ showLabel: isMobile && showMobileLabels && col.showLabelOnMobile !== false,
1752
+ labelText: typeof col.header === "string" ? col.header : col.id,
1753
+ className: isMobile ? "w-full sm:w-auto flex-shrink-0" : "flex-shrink-0",
1754
+ sticky: col.sticky,
1755
+ stickyOffset,
1756
+ onClick: onCellClick
1757
+ },
1758
+ col.id
1759
+ );
1760
+ })
1761
+ ]
1762
+ }
1763
+ ),
1764
+ isExpanded && renderExpansion && /* @__PURE__ */ jsx5("div", { className: "grid-row-expansion border-b border-theme-border bg-theme-secondary p-4", children: renderExpansion(row) })
1765
+ ] });
1766
+ }
1767
+
1768
+ // src/components/GridBody/GridBody.tsx
1769
+ import { jsx as jsx6 } from "react/jsx-runtime";
1770
+ function GridBody({
1771
+ data,
1772
+ columns,
1773
+ columnStates,
1774
+ className = "",
1775
+ style,
1776
+ isMobile = false,
1777
+ showMobileLabels = true,
1778
+ enableSelection = false,
1779
+ enableExpansion = false,
1780
+ selectedIds = /* @__PURE__ */ new Set(),
1781
+ expandedIds = /* @__PURE__ */ new Set(),
1782
+ onRowClick,
1783
+ onRowDoubleClick,
1784
+ onCellClick,
1785
+ onRowSelect,
1786
+ onRowExpand,
1787
+ getRowId,
1788
+ getRowClassName,
1789
+ getRowStyle,
1790
+ isRowDisabled,
1791
+ renderRowExpansion
1792
+ }) {
1793
+ const handleRowSelect = useCallback10(
1794
+ (row) => (selected) => {
1795
+ const id = getRowId(row);
1796
+ onRowSelect == null ? void 0 : onRowSelect(id, selected);
1797
+ },
1798
+ [getRowId, onRowSelect]
1799
+ );
1800
+ const handleRowExpand = useCallback10(
1801
+ (row) => (expanded) => {
1802
+ const id = getRowId(row);
1803
+ onRowExpand == null ? void 0 : onRowExpand(id, expanded);
1804
+ },
1805
+ [getRowId, onRowExpand]
1806
+ );
1807
+ if (data.length === 0) {
1808
+ return null;
1809
+ }
1810
+ return /* @__PURE__ */ jsx6("div", { className: `grid-body ${className}`, style, role: "rowgroup", children: data.map((row, index) => {
1811
+ var _a, _b;
1812
+ const rowId = getRowId(row);
1813
+ const isSelected = selectedIds.has(rowId);
1814
+ const isExpanded = expandedIds.has(rowId);
1815
+ const isDisabled = (_a = isRowDisabled == null ? void 0 : isRowDisabled(row)) != null ? _a : false;
1816
+ const rowClassName = (_b = getRowClassName == null ? void 0 : getRowClassName(row, index)) != null ? _b : "";
1817
+ const rowStyle = getRowStyle == null ? void 0 : getRowStyle(row, index);
1818
+ return /* @__PURE__ */ jsx6(
1819
+ GridRow,
1820
+ {
1821
+ row,
1822
+ rowIndex: index,
1823
+ columns,
1824
+ columnStates,
1825
+ isSelected,
1826
+ isExpanded,
1827
+ isDisabled,
1828
+ isMobile,
1829
+ showMobileLabels,
1830
+ className: rowClassName,
1831
+ style: rowStyle,
1832
+ onClick: onRowClick,
1833
+ onDoubleClick: onRowDoubleClick,
1834
+ onCellClick,
1835
+ onSelect: handleRowSelect(row),
1836
+ onExpand: handleRowExpand(row),
1837
+ enableSelection,
1838
+ enableExpansion: enableExpansion && !!renderRowExpansion,
1839
+ renderExpansion: renderRowExpansion,
1840
+ getRowId
1841
+ },
1842
+ rowId
1843
+ );
1844
+ }) });
1845
+ }
1846
+
1847
+ // src/components/Pagination/Pagination.tsx
1848
+ import { useMemo as useMemo11, useCallback as useCallback11 } from "react";
1849
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1850
+ function Pagination({
1851
+ page,
1852
+ pageSize,
1853
+ totalItems,
1854
+ totalPages,
1855
+ pageSizeOptions = [10, 20, 50, 100],
1856
+ showFirstLast = true,
1857
+ showPageNumbers = true,
1858
+ maxPageButtons = FIVE,
1859
+ className = "",
1860
+ style,
1861
+ onPageChange,
1862
+ onPageSizeChange
1863
+ }) {
1864
+ const { state } = useTableContext();
1865
+ const { translations } = state;
1866
+ const canGoPrevious = page > ONE;
1867
+ const canGoNext = page < totalPages;
1868
+ const startItem = (page - ONE) * pageSize + ONE;
1869
+ const endItem = Math.min(page * pageSize, totalItems);
1870
+ const handleFirstPage = useCallback11(() => {
1871
+ onPageChange(ONE);
1872
+ }, [onPageChange]);
1873
+ const handlePreviousPage = useCallback11(() => {
1874
+ if (canGoPrevious) {
1875
+ onPageChange(page - ONE);
1876
+ }
1877
+ }, [canGoPrevious, page, onPageChange]);
1878
+ const handleNextPage = useCallback11(() => {
1879
+ if (canGoNext) {
1880
+ onPageChange(page + ONE);
1881
+ }
1882
+ }, [canGoNext, page, onPageChange]);
1883
+ const handleLastPage = useCallback11(() => {
1884
+ onPageChange(totalPages);
1885
+ }, [onPageChange, totalPages]);
1886
+ const handlePageSizeChange = useCallback11(
1887
+ (event) => {
1888
+ onPageSizeChange(Number(event.target.value));
1889
+ },
1890
+ [onPageSizeChange]
1891
+ );
1892
+ const pageNumbers = useMemo11(() => {
1893
+ if (!showPageNumbers || totalPages <= ONE) return [];
1894
+ const pages = [];
1895
+ const halfMax = Math.floor(maxPageButtons / 2);
1896
+ let start = Math.max(ONE, page - halfMax);
1897
+ let end = Math.min(totalPages, page + halfMax);
1898
+ if (page <= halfMax) {
1899
+ end = Math.min(totalPages, maxPageButtons);
1900
+ }
1901
+ if (page > totalPages - halfMax) {
1902
+ start = Math.max(ONE, totalPages - maxPageButtons + ONE);
1903
+ }
1904
+ if (start > ONE) {
1905
+ pages.push(ONE);
1906
+ if (start > 2) {
1907
+ pages.push("ellipsis");
1908
+ }
1909
+ }
1910
+ for (let i = start; i <= end; i++) {
1911
+ if (!pages.includes(i)) {
1912
+ pages.push(i);
1913
+ }
1914
+ }
1915
+ if (end < totalPages) {
1916
+ if (end < totalPages - ONE) {
1917
+ pages.push("ellipsis");
1918
+ }
1919
+ pages.push(totalPages);
1920
+ }
1921
+ return pages;
1922
+ }, [page, totalPages, showPageNumbers, maxPageButtons]);
1923
+ if (totalItems === 0) {
1924
+ return null;
1925
+ }
1926
+ return /* @__PURE__ */ jsxs5(
1927
+ "div",
1928
+ {
1929
+ className: `grid-pagination flex flex-wrap items-center justify-between gap-4 px-4 py-3 border-t border-theme-border ${className}`,
1930
+ style,
1931
+ role: "navigation",
1932
+ "aria-label": "Pagination",
1933
+ children: [
1934
+ /* @__PURE__ */ jsxs5("div", { className: "grid-pagination-info flex items-center gap-4", children: [
1935
+ /* @__PURE__ */ jsxs5("div", { className: "grid-pagination-range text-sm text-theme-secondary", children: [
1936
+ startItem,
1937
+ "-",
1938
+ endItem,
1939
+ " ",
1940
+ translations.of,
1941
+ " ",
1942
+ totalItems
1943
+ ] }),
1944
+ /* @__PURE__ */ jsxs5("div", { className: "grid-pagination-size flex items-center gap-2", children: [
1945
+ /* @__PURE__ */ jsxs5("label", { htmlFor: "page-size", className: "text-sm text-theme-muted", children: [
1946
+ translations.rowsPerPage,
1947
+ ":"
1948
+ ] }),
1949
+ /* @__PURE__ */ jsx7(
1950
+ "select",
1951
+ {
1952
+ id: "page-size",
1953
+ value: pageSize,
1954
+ onChange: handlePageSizeChange,
1955
+ className: "px-2 py-1 text-sm rounded border border-theme-border bg-theme-primary text-theme-primary focus:outline-none focus:ring-2 focus:ring-accent-primary",
1956
+ children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx7("option", { value: size, children: size }, size))
1957
+ }
1958
+ )
1959
+ ] })
1960
+ ] }),
1961
+ /* @__PURE__ */ jsxs5("div", { className: "grid-pagination-controls flex items-center gap-1", children: [
1962
+ showFirstLast && /* @__PURE__ */ jsx7(
1963
+ "button",
1964
+ {
1965
+ onClick: handleFirstPage,
1966
+ disabled: !canGoPrevious,
1967
+ className: "p-2 rounded hover:bg-theme-hover disabled:opacity-50 disabled:cursor-not-allowed text-theme-secondary",
1968
+ "aria-label": translations.first,
1969
+ children: "<<"
1970
+ }
1971
+ ),
1972
+ /* @__PURE__ */ jsx7(
1973
+ "button",
1974
+ {
1975
+ onClick: handlePreviousPage,
1976
+ disabled: !canGoPrevious,
1977
+ className: "p-2 rounded hover:bg-theme-hover disabled:opacity-50 disabled:cursor-not-allowed text-theme-secondary",
1978
+ "aria-label": translations.previous,
1979
+ children: "<"
1980
+ }
1981
+ ),
1982
+ showPageNumbers && /* @__PURE__ */ jsx7("div", { className: "grid-pagination-pages flex items-center gap-1", children: pageNumbers.map(
1983
+ (pageNum, index) => pageNum === "ellipsis" ? /* @__PURE__ */ jsx7("span", { className: "px-2 text-theme-muted", children: "..." }, `ellipsis-${index}`) : /* @__PURE__ */ jsx7(
1984
+ "button",
1985
+ {
1986
+ onClick: () => onPageChange(pageNum),
1987
+ className: `min-w-[32px] h-8 px-2 rounded text-sm transition-colors ${pageNum === page ? "bg-accent-primary text-white" : "hover:bg-theme-hover text-theme-secondary"}`,
1988
+ "aria-current": pageNum === page ? "page" : void 0,
1989
+ children: pageNum
1990
+ },
1991
+ pageNum
1992
+ )
1993
+ ) }),
1994
+ /* @__PURE__ */ jsx7(
1995
+ "button",
1996
+ {
1997
+ onClick: handleNextPage,
1998
+ disabled: !canGoNext,
1999
+ className: "p-2 rounded hover:bg-theme-hover disabled:opacity-50 disabled:cursor-not-allowed text-theme-secondary",
2000
+ "aria-label": translations.next,
2001
+ children: ">"
2002
+ }
2003
+ ),
2004
+ showFirstLast && /* @__PURE__ */ jsx7(
2005
+ "button",
2006
+ {
2007
+ onClick: handleLastPage,
2008
+ disabled: !canGoNext,
2009
+ className: "p-2 rounded hover:bg-theme-hover disabled:opacity-50 disabled:cursor-not-allowed text-theme-secondary",
2010
+ "aria-label": translations.last,
2011
+ children: ">>"
2012
+ }
2013
+ )
2014
+ ] })
2015
+ ]
2016
+ }
2017
+ );
2018
+ }
2019
+
2020
+ // src/components/Skeleton/Skeleton.tsx
2021
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2022
+ function SkeletonCell({ width = DEFAULT_COLUMN_WIDTH, height = 16, animate = true }) {
2023
+ const widthStyle = typeof width === "number" ? `${width}px` : width;
2024
+ return /* @__PURE__ */ jsx8(
2025
+ "div",
2026
+ {
2027
+ className: "grid-skeleton-cell px-4 py-3 flex-shrink-0",
2028
+ style: { width: widthStyle },
2029
+ children: /* @__PURE__ */ jsx8(
2030
+ "div",
2031
+ {
2032
+ className: `rounded bg-theme-tertiary ${animate ? "animate-pulse" : ""}`,
2033
+ style: { height: `${height}px`, width: "80%" }
2034
+ }
2035
+ )
2036
+ }
2037
+ );
2038
+ }
2039
+ function SkeletonRow({ columns, columnWidths, height = 16, animate = true }) {
2040
+ return /* @__PURE__ */ jsx8("div", { className: "grid-skeleton-row flex border-b border-theme-border", children: Array.from({ length: columns }).map((_, index) => {
2041
+ var _a;
2042
+ return /* @__PURE__ */ jsx8(
2043
+ SkeletonCell,
2044
+ {
2045
+ width: (_a = columnWidths == null ? void 0 : columnWidths[index]) != null ? _a : DEFAULT_COLUMN_WIDTH,
2046
+ height,
2047
+ animate
2048
+ },
2049
+ index
2050
+ );
2051
+ }) });
2052
+ }
2053
+ function Skeleton({
2054
+ rows = SKELETON_ROWS,
2055
+ columns = 4,
2056
+ columnWidths,
2057
+ rowHeight = 16,
2058
+ className = "",
2059
+ style,
2060
+ showHeader = true,
2061
+ animate = true
2062
+ }) {
2063
+ return /* @__PURE__ */ jsxs6("div", { className: `grid-skeleton ${className}`, style, role: "status", "aria-label": "Loading", children: [
2064
+ showHeader && /* @__PURE__ */ jsx8("div", { className: "grid-skeleton-header flex border-b border-theme-border bg-theme-secondary", children: Array.from({ length: columns }).map((_, index) => {
2065
+ var _a;
2066
+ return /* @__PURE__ */ jsx8(
2067
+ "div",
2068
+ {
2069
+ className: "px-4 py-3 flex-shrink-0",
2070
+ style: { width: (_a = columnWidths == null ? void 0 : columnWidths[index]) != null ? _a : DEFAULT_COLUMN_WIDTH },
2071
+ children: /* @__PURE__ */ jsx8(
2072
+ "div",
2073
+ {
2074
+ className: `rounded bg-theme-tertiary ${animate ? "animate-pulse" : ""}`,
2075
+ style: { height: "12px", width: "60%" }
2076
+ }
2077
+ )
2078
+ },
2079
+ index
2080
+ );
2081
+ }) }),
2082
+ /* @__PURE__ */ jsx8("div", { className: "grid-skeleton-body", children: Array.from({ length: rows }).map((_, index) => /* @__PURE__ */ jsx8(
2083
+ SkeletonRow,
2084
+ {
2085
+ columns,
2086
+ columnWidths,
2087
+ height: rowHeight,
2088
+ animate
2089
+ },
2090
+ index
2091
+ )) })
2092
+ ] });
2093
+ }
2094
+
2095
+ // src/components/EmptyState/EmptyState.tsx
2096
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2097
+ function EmptyState({
2098
+ title,
2099
+ description,
2100
+ icon,
2101
+ action,
2102
+ className = "",
2103
+ style
2104
+ }) {
2105
+ const { state } = useTableContext();
2106
+ const { translations } = state;
2107
+ const displayTitle = title != null ? title : translations.empty;
2108
+ const displayDescription = description != null ? description : translations.noResults;
2109
+ return /* @__PURE__ */ jsxs7(
2110
+ "div",
2111
+ {
2112
+ className: `grid-empty-state flex flex-col items-center justify-center py-16 px-8 text-center ${className}`,
2113
+ style,
2114
+ role: "status",
2115
+ children: [
2116
+ icon && /* @__PURE__ */ jsx9("div", { className: "grid-empty-icon mb-4 text-theme-muted text-4xl", children: icon }),
2117
+ !icon && /* @__PURE__ */ jsx9("div", { className: "grid-empty-icon mb-4 text-theme-muted", children: /* @__PURE__ */ jsx9(
2118
+ "svg",
2119
+ {
2120
+ className: "w-16 h-16",
2121
+ fill: "none",
2122
+ viewBox: "0 0 24 24",
2123
+ stroke: "currentColor",
2124
+ children: /* @__PURE__ */ jsx9(
2125
+ "path",
2126
+ {
2127
+ strokeLinecap: "round",
2128
+ strokeLinejoin: "round",
2129
+ strokeWidth: 1,
2130
+ d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
2131
+ }
2132
+ )
2133
+ }
2134
+ ) }),
2135
+ /* @__PURE__ */ jsx9("h3", { className: "grid-empty-title text-lg font-medium text-theme-primary mb-2", children: displayTitle }),
2136
+ displayDescription && /* @__PURE__ */ jsx9("p", { className: "grid-empty-description text-sm text-theme-muted max-w-sm mb-4", children: displayDescription }),
2137
+ action && /* @__PURE__ */ jsx9("div", { className: "grid-empty-action", children: action })
2138
+ ]
2139
+ }
2140
+ );
2141
+ }
2142
+
2143
+ // src/components/MobileDrawer/MobileDrawer.tsx
2144
+ import { useEffect as useEffect4, useCallback as useCallback12 } from "react";
2145
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2146
+ function DrawerHeader({ title, onClose }) {
2147
+ return /* @__PURE__ */ jsxs8("div", { className: "drawer-header flex items-center justify-between px-4 py-3 border-b border-theme-border", children: [
2148
+ /* @__PURE__ */ jsx10("h3", { className: "text-lg font-medium text-theme-primary", children: title }),
2149
+ /* @__PURE__ */ jsx10(
2150
+ "button",
2151
+ {
2152
+ onClick: onClose,
2153
+ className: "p-2 rounded hover:bg-theme-hover text-theme-secondary",
2154
+ "aria-label": "Close",
2155
+ children: "X"
2156
+ }
2157
+ )
2158
+ ] });
2159
+ }
2160
+ function FilterContent() {
2161
+ const { state, actions } = useTableContext();
2162
+ const { translations, columns, filters } = state;
2163
+ const handleClearAll = useCallback12(() => {
2164
+ actions.clearFilters();
2165
+ }, [actions]);
2166
+ return /* @__PURE__ */ jsxs8("div", { className: "drawer-filter-content p-4 space-y-4", children: [
2167
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between mb-4", children: [
2168
+ /* @__PURE__ */ jsxs8("span", { className: "text-sm text-theme-muted", children: [
2169
+ filters.length,
2170
+ " ",
2171
+ translations.filter,
2172
+ "(s) active"
2173
+ ] }),
2174
+ filters.length > 0 && /* @__PURE__ */ jsx10(
2175
+ "button",
2176
+ {
2177
+ onClick: handleClearAll,
2178
+ className: "text-sm text-accent-primary hover:underline",
2179
+ children: translations.clearAll
2180
+ }
2181
+ )
2182
+ ] }),
2183
+ /* @__PURE__ */ jsx10("div", { className: "space-y-3", children: columns.filter((col) => col.filterable !== false).map((col) => {
2184
+ var _a;
2185
+ const existingFilter = filters.find((f) => f.columnId === col.id);
2186
+ const headerText = typeof col.header === "string" ? col.header : col.id;
2187
+ return /* @__PURE__ */ jsxs8("div", { className: "filter-item", children: [
2188
+ /* @__PURE__ */ jsx10("label", { className: "block text-sm font-medium text-theme-secondary mb-1", children: headerText }),
2189
+ /* @__PURE__ */ jsx10(
2190
+ "input",
2191
+ {
2192
+ type: "text",
2193
+ value: (_a = existingFilter == null ? void 0 : existingFilter.value) != null ? _a : "",
2194
+ onChange: (e) => {
2195
+ if (e.target.value) {
2196
+ actions.setFilter(col.id, e.target.value);
2197
+ } else {
2198
+ actions.removeFilter(col.id);
2199
+ }
2200
+ },
2201
+ placeholder: `${translations.filter} ${headerText}...`,
2202
+ className: "w-full px-3 py-2 text-sm rounded border border-theme-border bg-theme-primary text-theme-primary focus:outline-none focus:ring-2 focus:ring-accent-primary"
2203
+ }
2204
+ )
2205
+ ] }, col.id);
2206
+ }) })
2207
+ ] });
2208
+ }
2209
+ function SortContent() {
2210
+ const { state, actions } = useTableContext();
2211
+ const { translations, columns, sorting } = state;
2212
+ const handleClearAll = useCallback12(() => {
2213
+ actions.clearSorting();
2214
+ }, [actions]);
2215
+ return /* @__PURE__ */ jsxs8("div", { className: "drawer-sort-content p-4 space-y-4", children: [
2216
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between mb-4", children: [
2217
+ /* @__PURE__ */ jsxs8("span", { className: "text-sm text-theme-muted", children: [
2218
+ sorting.length,
2219
+ " ",
2220
+ translations.sort,
2221
+ "(s) active"
2222
+ ] }),
2223
+ sorting.length > 0 && /* @__PURE__ */ jsx10(
2224
+ "button",
2225
+ {
2226
+ onClick: handleClearAll,
2227
+ className: "text-sm text-accent-primary hover:underline",
2228
+ children: translations.clearAll
2229
+ }
2230
+ )
2231
+ ] }),
2232
+ /* @__PURE__ */ jsx10("div", { className: "space-y-2", children: columns.filter((col) => col.sortable !== false).map((col) => {
2233
+ const sortItem = sorting.find((s) => s.columnId === col.id);
2234
+ const headerText = typeof col.header === "string" ? col.header : col.id;
2235
+ return /* @__PURE__ */ jsxs8(
2236
+ "button",
2237
+ {
2238
+ onClick: () => actions.toggleSorting(col.id),
2239
+ className: `w-full flex items-center justify-between px-3 py-2 rounded border transition-colors ${sortItem ? "border-accent-primary bg-accent-primary/10" : "border-theme-border hover:bg-theme-hover"}`,
2240
+ children: [
2241
+ /* @__PURE__ */ jsx10("span", { className: "text-sm text-theme-primary", children: headerText }),
2242
+ /* @__PURE__ */ jsx10("span", { className: "text-xs text-theme-muted", children: (sortItem == null ? void 0 : sortItem.direction) === "asc" ? translations.sortAsc : (sortItem == null ? void 0 : sortItem.direction) === "desc" ? translations.sortDesc : "-" })
2243
+ ]
2244
+ },
2245
+ col.id
2246
+ );
2247
+ }) })
2248
+ ] });
2249
+ }
2250
+ function ColumnsContent() {
2251
+ const { state, actions } = useTableContext();
2252
+ const { translations, columns, columnStates } = state;
2253
+ return /* @__PURE__ */ jsxs8("div", { className: "drawer-columns-content p-4 space-y-4", children: [
2254
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between mb-4", children: [
2255
+ /* @__PURE__ */ jsx10("span", { className: "text-sm text-theme-muted", children: translations.showColumns }),
2256
+ /* @__PURE__ */ jsx10(
2257
+ "button",
2258
+ {
2259
+ onClick: actions.resetColumns,
2260
+ className: "text-sm text-accent-primary hover:underline",
2261
+ children: translations.resetColumns
2262
+ }
2263
+ )
2264
+ ] }),
2265
+ /* @__PURE__ */ jsx10("div", { className: "space-y-2", children: columns.map((col) => {
2266
+ const colState = columnStates.find((cs) => cs.id === col.id);
2267
+ const isVisible = (colState == null ? void 0 : colState.visible) !== false;
2268
+ const headerText = typeof col.header === "string" ? col.header : col.id;
2269
+ return /* @__PURE__ */ jsxs8(
2270
+ "label",
2271
+ {
2272
+ className: "flex items-center gap-3 px-3 py-2 rounded hover:bg-theme-hover cursor-pointer",
2273
+ children: [
2274
+ /* @__PURE__ */ jsx10(
2275
+ "input",
2276
+ {
2277
+ type: "checkbox",
2278
+ checked: isVisible,
2279
+ onChange: () => actions.toggleColumnVisibility(col.id),
2280
+ className: "w-4 h-4 rounded border-theme-border"
2281
+ }
2282
+ ),
2283
+ /* @__PURE__ */ jsx10("span", { className: "text-sm text-theme-primary", children: headerText })
2284
+ ]
2285
+ },
2286
+ col.id
2287
+ );
2288
+ }) })
2289
+ ] });
2290
+ }
2291
+ var DRAWER_TITLES = {
2292
+ filter: "Filters",
2293
+ sort: "Sort",
2294
+ columns: "Columns"
2295
+ };
2296
+ function MobileDrawer({
2297
+ isOpen,
2298
+ content,
2299
+ onClose,
2300
+ className = "",
2301
+ style
2302
+ }) {
2303
+ useEffect4(() => {
2304
+ if (isOpen) {
2305
+ document.body.style.overflow = "hidden";
2306
+ } else {
2307
+ document.body.style.overflow = "";
2308
+ }
2309
+ return () => {
2310
+ document.body.style.overflow = "";
2311
+ };
2312
+ }, [isOpen]);
2313
+ useEffect4(() => {
2314
+ const handleEscape = (e) => {
2315
+ if (e.key === "Escape" && isOpen) {
2316
+ onClose();
2317
+ }
2318
+ };
2319
+ document.addEventListener("keydown", handleEscape);
2320
+ return () => document.removeEventListener("keydown", handleEscape);
2321
+ }, [isOpen, onClose]);
2322
+ if (!isOpen || !content) {
2323
+ return null;
2324
+ }
2325
+ return /* @__PURE__ */ jsxs8("div", { className: `mobile-drawer-container fixed inset-0 z-50 ${className}`, style, children: [
2326
+ /* @__PURE__ */ jsx10(
2327
+ "div",
2328
+ {
2329
+ className: "mobile-drawer-overlay absolute inset-0 bg-black transition-opacity",
2330
+ style: {
2331
+ opacity: DRAWER_OVERLAY_OPACITY,
2332
+ transitionDuration: `${DRAWER_ANIMATION_DURATION}ms`
2333
+ },
2334
+ onClick: onClose,
2335
+ "aria-hidden": "true"
2336
+ }
2337
+ ),
2338
+ /* @__PURE__ */ jsxs8(
2339
+ "div",
2340
+ {
2341
+ className: "mobile-drawer absolute bottom-0 left-0 right-0 bg-theme-primary rounded-t-2xl shadow-2xl max-h-[80vh] overflow-hidden flex flex-col",
2342
+ style: {
2343
+ transitionDuration: `${DRAWER_ANIMATION_DURATION}ms`
2344
+ },
2345
+ role: "dialog",
2346
+ "aria-modal": "true",
2347
+ "aria-labelledby": "drawer-title",
2348
+ children: [
2349
+ /* @__PURE__ */ jsx10(DrawerHeader, { title: DRAWER_TITLES[content], onClose }),
2350
+ /* @__PURE__ */ jsxs8("div", { className: "drawer-content flex-1 overflow-y-auto", children: [
2351
+ content === "filter" && /* @__PURE__ */ jsx10(FilterContent, {}),
2352
+ content === "sort" && /* @__PURE__ */ jsx10(SortContent, {}),
2353
+ content === "columns" && /* @__PURE__ */ jsx10(ColumnsContent, {})
2354
+ ] })
2355
+ ]
2356
+ }
2357
+ )
2358
+ ] });
2359
+ }
2360
+
2361
+ // src/components/GridTable/GridTable.tsx
2362
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2363
+ function GridTableContent({
2364
+ data,
2365
+ columns,
2366
+ loading = false,
2367
+ error = null,
2368
+ emptyContent,
2369
+ loadingContent,
2370
+ errorContent,
2371
+ dimensions,
2372
+ classNames = {},
2373
+ styles = {},
2374
+ showMobileLabels = true,
2375
+ enableDragDrop = true,
2376
+ enableColumnResize = true,
2377
+ enableRowSelection = false,
2378
+ enableRowExpansion = false,
2379
+ stickyHeader = true,
2380
+ showPagination = true,
2381
+ showFilter = true,
2382
+ showGlobalFilter = true,
2383
+ onRowClick,
2384
+ onRowDoubleClick,
2385
+ onCellClick,
2386
+ onRowSelect,
2387
+ onSort,
2388
+ onFilter: _onFilter,
2389
+ onPageChange,
2390
+ onError: _onError,
2391
+ onRetry,
2392
+ getRowId,
2393
+ getRowClassName,
2394
+ getRowStyle,
2395
+ isRowDisabled,
2396
+ renderRowExpansion,
2397
+ renderHeader,
2398
+ renderFooter,
2399
+ className = "",
2400
+ style
2401
+ }) {
2402
+ const { state, actions, computed } = useTableContext();
2403
+ const { shouldShowMobileView, breakpointValue } = useBreakpoint();
2404
+ const getRowIdFn = useCallback13(
2405
+ (row) => {
2406
+ if (getRowId) return getRowId(row);
2407
+ if ("id" in row) return row.id;
2408
+ return data.indexOf(row);
2409
+ },
2410
+ [getRowId, data]
2411
+ );
2412
+ const handleRowSelect = useCallback13(
2413
+ (id, selected) => {
2414
+ if (selected) {
2415
+ actions.selectRow(id);
2416
+ } else {
2417
+ actions.deselectRow(id);
2418
+ }
2419
+ },
2420
+ [actions]
2421
+ );
2422
+ const handleRowExpand = useCallback13(
2423
+ (id, expanded) => {
2424
+ if (expanded) {
2425
+ actions.expandRow(id);
2426
+ } else {
2427
+ actions.collapseRow(id);
2428
+ }
2429
+ },
2430
+ [actions]
2431
+ );
2432
+ const handleFilterOpen = useCallback13(
2433
+ (columnId) => {
2434
+ if (shouldShowMobileView) {
2435
+ actions.openMobileDrawer("filter");
2436
+ } else {
2437
+ actions.setActiveFilterColumn(columnId);
2438
+ }
2439
+ },
2440
+ [shouldShowMobileView, actions]
2441
+ );
2442
+ const containerStyle = useMemo12(() => {
2443
+ const baseStyle = { ...style, ...styles.root };
2444
+ if (dimensions == null ? void 0 : dimensions.width) {
2445
+ baseStyle.width = breakpointValue(dimensions.width, "auto");
2446
+ }
2447
+ if (dimensions == null ? void 0 : dimensions.height) {
2448
+ baseStyle.height = breakpointValue(dimensions.height, "auto");
2449
+ }
2450
+ if (dimensions == null ? void 0 : dimensions.maxHeight) {
2451
+ baseStyle.maxHeight = breakpointValue(dimensions.maxHeight, "none");
2452
+ }
2453
+ return baseStyle;
2454
+ }, [style, styles.root, dimensions, breakpointValue]);
2455
+ const columnWidths = useMemo12(() => {
2456
+ return state.columnStates.map((cs) => cs.width);
2457
+ }, [state.columnStates]);
2458
+ if (error) {
2459
+ const errorMessage = typeof error === "string" ? error : error.message;
2460
+ if (errorContent) {
2461
+ return /* @__PURE__ */ jsx11("div", { className: `grid-table-error ${classNames.root || ""} ${className}`, style: containerStyle, children: typeof errorContent === "function" ? errorContent(error) : errorContent });
2462
+ }
2463
+ return /* @__PURE__ */ jsx11("div", { className: `grid-table-error ${classNames.root || ""} ${className}`, style: containerStyle, children: /* @__PURE__ */ jsx11(
2464
+ EmptyState,
2465
+ {
2466
+ title: state.translations.errorLoading,
2467
+ description: errorMessage,
2468
+ action: onRetry && /* @__PURE__ */ jsx11(
2469
+ "button",
2470
+ {
2471
+ onClick: onRetry,
2472
+ className: "px-4 py-2 bg-accent-primary text-white rounded hover:bg-accent-primary/90",
2473
+ children: state.translations.retry
2474
+ }
2475
+ )
2476
+ }
2477
+ ) });
2478
+ }
2479
+ if (loading) {
2480
+ if (loadingContent) {
2481
+ return /* @__PURE__ */ jsx11("div", { className: `grid-table-loading ${classNames.root || ""} ${className}`, style: containerStyle, children: loadingContent });
2482
+ }
2483
+ return /* @__PURE__ */ jsx11("div", { className: `grid-table-loading ${classNames.root || ""} ${className}`, style: containerStyle, children: /* @__PURE__ */ jsx11(
2484
+ Skeleton,
2485
+ {
2486
+ rows: 5,
2487
+ columns: columns.length,
2488
+ columnWidths,
2489
+ showHeader: true,
2490
+ animate: true,
2491
+ className: classNames.skeleton,
2492
+ style: styles.skeleton
2493
+ }
2494
+ ) });
2495
+ }
2496
+ const isEmpty = computed.paginatedData.length === 0;
2497
+ return /* @__PURE__ */ jsxs9(
2498
+ "div",
2499
+ {
2500
+ className: `grid-table bg-theme-primary rounded-lg border border-theme-border overflow-hidden ${classNames.root || ""} ${className}`,
2501
+ style: containerStyle,
2502
+ role: "table",
2503
+ children: [
2504
+ renderHeader && /* @__PURE__ */ jsx11("div", { className: "grid-table-custom-header", children: renderHeader() }),
2505
+ showGlobalFilter && /* @__PURE__ */ jsxs9("div", { className: "grid-table-toolbar flex items-center gap-4 px-4 py-3 border-b border-theme-border", children: [
2506
+ /* @__PURE__ */ jsxs9("div", { className: "flex-1 relative", children: [
2507
+ /* @__PURE__ */ jsx11(
2508
+ "svg",
2509
+ {
2510
+ className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-theme-muted",
2511
+ fill: "none",
2512
+ viewBox: "0 0 24 24",
2513
+ stroke: "currentColor",
2514
+ strokeWidth: 2,
2515
+ children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" })
2516
+ }
2517
+ ),
2518
+ /* @__PURE__ */ jsx11(
2519
+ "input",
2520
+ {
2521
+ type: "text",
2522
+ value: state.globalFilter,
2523
+ onChange: (e) => actions.setGlobalFilter(e.target.value),
2524
+ placeholder: state.translations.search,
2525
+ className: "w-full pl-10 pr-3 py-2 text-sm rounded border border-theme-border bg-theme-secondary text-theme-primary focus:outline-none focus:ring-2 focus:ring-accent-primary"
2526
+ }
2527
+ ),
2528
+ state.globalFilter && /* @__PURE__ */ jsx11(
2529
+ "button",
2530
+ {
2531
+ onClick: () => actions.setGlobalFilter(""),
2532
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-theme-muted hover:text-theme-primary",
2533
+ "aria-label": "Clear search",
2534
+ children: /* @__PURE__ */ jsx11("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
2535
+ }
2536
+ )
2537
+ ] }),
2538
+ state.filters.length > 0 && /* @__PURE__ */ jsxs9(
2539
+ "button",
2540
+ {
2541
+ onClick: () => actions.clearFilters(),
2542
+ className: "flex items-center gap-2 px-3 py-2 text-sm rounded border border-theme-border hover:bg-theme-hover text-theme-secondary",
2543
+ children: [
2544
+ /* @__PURE__ */ jsx11("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }),
2545
+ /* @__PURE__ */ jsxs9("span", { children: [
2546
+ state.filters.length,
2547
+ " filter",
2548
+ state.filters.length > 1 ? "s" : ""
2549
+ ] })
2550
+ ]
2551
+ }
2552
+ ),
2553
+ shouldShowMobileView && /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
2554
+ /* @__PURE__ */ jsx11(
2555
+ "button",
2556
+ {
2557
+ onClick: () => actions.openMobileDrawer("filter"),
2558
+ className: "p-2 rounded border border-theme-border hover:bg-theme-hover",
2559
+ "aria-label": state.translations.filter,
2560
+ children: /* @__PURE__ */ jsx11("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" }) })
2561
+ }
2562
+ ),
2563
+ /* @__PURE__ */ jsx11(
2564
+ "button",
2565
+ {
2566
+ onClick: () => actions.openMobileDrawer("sort"),
2567
+ className: "p-2 rounded border border-theme-border hover:bg-theme-hover",
2568
+ "aria-label": state.translations.sort,
2569
+ children: /* @__PURE__ */ jsx11("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) })
2570
+ }
2571
+ ),
2572
+ /* @__PURE__ */ jsx11(
2573
+ "button",
2574
+ {
2575
+ onClick: () => actions.openMobileDrawer("columns"),
2576
+ className: "p-2 rounded border border-theme-border hover:bg-theme-hover",
2577
+ "aria-label": state.translations.columns,
2578
+ children: /* @__PURE__ */ jsx11("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx11("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" }) })
2579
+ }
2580
+ )
2581
+ ] })
2582
+ ] }),
2583
+ /* @__PURE__ */ jsxs9("div", { className: "grid-table-container overflow-auto", children: [
2584
+ !shouldShowMobileView && /* @__PURE__ */ jsx11(
2585
+ GridHeader,
2586
+ {
2587
+ columns,
2588
+ columnStates: state.columnStates,
2589
+ className: classNames.header,
2590
+ style: styles.header,
2591
+ sticky: stickyHeader,
2592
+ enableSort: true,
2593
+ enableFilter: showFilter,
2594
+ enableDragDrop,
2595
+ enableResize: enableColumnResize,
2596
+ enableSelection: enableRowSelection,
2597
+ allSelected: computed.allSelected,
2598
+ someSelected: computed.someSelected,
2599
+ onSelectAll: actions.selectAll,
2600
+ onFilterOpen: handleFilterOpen,
2601
+ getSortDirection: (colId) => {
2602
+ var _a;
2603
+ const sort = state.sorting.find((s) => s.columnId === colId);
2604
+ return (_a = sort == null ? void 0 : sort.direction) != null ? _a : null;
2605
+ }
2606
+ }
2607
+ ),
2608
+ isEmpty ? emptyContent || /* @__PURE__ */ jsx11(
2609
+ EmptyState,
2610
+ {
2611
+ className: classNames.empty,
2612
+ style: styles.empty
2613
+ }
2614
+ ) : /* @__PURE__ */ jsx11(
2615
+ GridBody,
2616
+ {
2617
+ data: computed.paginatedData,
2618
+ columns,
2619
+ columnStates: state.columnStates,
2620
+ className: classNames.body,
2621
+ style: styles.body,
2622
+ isMobile: shouldShowMobileView,
2623
+ showMobileLabels,
2624
+ enableSelection: enableRowSelection,
2625
+ enableExpansion: enableRowExpansion,
2626
+ selectedIds: state.selectedIds,
2627
+ expandedIds: state.expandedIds,
2628
+ onRowClick,
2629
+ onRowDoubleClick,
2630
+ onCellClick,
2631
+ onRowSelect: handleRowSelect,
2632
+ onRowExpand: handleRowExpand,
2633
+ getRowId: getRowIdFn,
2634
+ getRowClassName,
2635
+ getRowStyle,
2636
+ isRowDisabled,
2637
+ renderRowExpansion
2638
+ }
2639
+ )
2640
+ ] }),
2641
+ showPagination && !isEmpty && /* @__PURE__ */ jsx11(
2642
+ Pagination,
2643
+ {
2644
+ page: state.page,
2645
+ pageSize: state.pageSize,
2646
+ totalItems: computed.sortedData.length,
2647
+ totalPages: computed.totalPages,
2648
+ className: classNames.pagination,
2649
+ style: styles.pagination,
2650
+ onPageChange: (page) => {
2651
+ actions.setPage(page);
2652
+ onPageChange == null ? void 0 : onPageChange(page, state.pageSize);
2653
+ },
2654
+ onPageSizeChange: (pageSize) => {
2655
+ actions.setPageSize(pageSize);
2656
+ onPageChange == null ? void 0 : onPageChange(1, pageSize);
2657
+ }
2658
+ }
2659
+ ),
2660
+ renderFooter && /* @__PURE__ */ jsx11("div", { className: "grid-table-custom-footer", children: renderFooter() }),
2661
+ /* @__PURE__ */ jsx11(
2662
+ MobileDrawer,
2663
+ {
2664
+ isOpen: state.showMobileDrawer,
2665
+ content: state.mobileDrawerContent,
2666
+ onClose: actions.closeMobileDrawer,
2667
+ className: classNames.drawer,
2668
+ style: styles.drawer
2669
+ }
2670
+ )
2671
+ ]
2672
+ }
2673
+ );
2674
+ }
2675
+ function GridTable({
2676
+ data,
2677
+ columns,
2678
+ loading = false,
2679
+ error = null,
2680
+ theme,
2681
+ translations,
2682
+ mobileBreakpoint = "tablet",
2683
+ paginationConfig,
2684
+ filterConfig,
2685
+ sortConfig,
2686
+ enableMultiSelect = false,
2687
+ getRowId,
2688
+ ...props
2689
+ }) {
2690
+ return /* @__PURE__ */ jsx11(
2691
+ TableProvider,
2692
+ {
2693
+ data,
2694
+ columns,
2695
+ loading,
2696
+ error,
2697
+ theme,
2698
+ translations,
2699
+ mobileBreakpoint,
2700
+ paginationConfig,
2701
+ filterConfig,
2702
+ sortConfig,
2703
+ enableMultiSort: sortConfig == null ? void 0 : sortConfig.multiSort,
2704
+ enableMultiSelect,
2705
+ getRowId,
2706
+ children: /* @__PURE__ */ jsx11(
2707
+ GridTableContent,
2708
+ {
2709
+ data,
2710
+ columns,
2711
+ loading,
2712
+ error,
2713
+ getRowId,
2714
+ ...props
2715
+ }
2716
+ )
2717
+ }
2718
+ );
2719
+ }
2720
+ export {
2721
+ BREAKPOINTS,
2722
+ BREAKPOINT_KEYS,
2723
+ DEFAULT_COLUMN_WIDTH,
2724
+ DEFAULT_LIGHT_THEME,
2725
+ DEFAULT_PAGE_SIZE,
2726
+ DEFAULT_PAGE_SIZES,
2727
+ DEFAULT_TABLE_CONFIG,
2728
+ DEFAULT_THEME,
2729
+ DEFAULT_TRANSLATIONS,
2730
+ DESKTOP_BREAKPOINT,
2731
+ EmptyState,
2732
+ GridBody,
2733
+ GridCell,
2734
+ GridHeader,
2735
+ GridRow,
2736
+ GridTable,
2737
+ MAX_COLUMN_WIDTH,
2738
+ MIN_COLUMN_WIDTH,
2739
+ MOBILE_BREAKPOINT,
2740
+ MobileDrawer,
2741
+ Pagination,
2742
+ RESPONSIVE_MODES,
2743
+ Skeleton,
2744
+ TABLET_BREAKPOINT,
2745
+ TableContext,
2746
+ TableProvider,
2747
+ useBreakpoint,
2748
+ useDragDrop,
2749
+ useFilter,
2750
+ usePagination,
2751
+ useSort,
2752
+ useTable,
2753
+ useTableContext
2754
+ };