@optilogic/core 1.0.0-beta.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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/index.cjs +6003 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2310 -0
- package/dist/index.d.ts +2310 -0
- package/dist/index.js +5828 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +96 -0
- package/dist/tailwind-preset.cjs +106 -0
- package/dist/tailwind-preset.cjs.map +1 -0
- package/dist/tailwind-preset.d.cts +23 -0
- package/dist/tailwind-preset.d.ts +23 -0
- package/dist/tailwind-preset.js +101 -0
- package/dist/tailwind-preset.js.map +1 -0
- package/package.json +154 -0
- package/src/components/accordion.tsx +187 -0
- package/src/components/alert-dialog.tsx +143 -0
- package/src/components/autocomplete.tsx +271 -0
- package/src/components/badge.tsx +62 -0
- package/src/components/button.tsx +85 -0
- package/src/components/calendar.tsx +235 -0
- package/src/components/card.tsx +94 -0
- package/src/components/checkbox.tsx +77 -0
- package/src/components/chip.tsx +77 -0
- package/src/components/confirmation-modal.tsx +195 -0
- package/src/components/context-menu.tsx +406 -0
- package/src/components/copy-button.tsx +84 -0
- package/src/components/data-grid/DataGrid.tsx +1027 -0
- package/src/components/data-grid/components/CellEditor.tsx +346 -0
- package/src/components/data-grid/components/FilterPopover.tsx +459 -0
- package/src/components/data-grid/components/HeaderCell.tsx +207 -0
- package/src/components/data-grid/components/index.ts +14 -0
- package/src/components/data-grid/hooks/index.ts +28 -0
- package/src/components/data-grid/hooks/useColumnResize.ts +378 -0
- package/src/components/data-grid/hooks/useDataGridState.ts +346 -0
- package/src/components/data-grid/hooks/useKeyboardNavigation.ts +361 -0
- package/src/components/data-grid/index.ts +71 -0
- package/src/components/data-grid/types.ts +478 -0
- package/src/components/data-grid/utils/dataProcessing.ts +277 -0
- package/src/components/data-grid/utils/index.ts +12 -0
- package/src/components/date-picker.tsx +366 -0
- package/src/components/dropdown-menu.tsx +230 -0
- package/src/components/icon-button.tsx +157 -0
- package/src/components/input.tsx +40 -0
- package/src/components/label.tsx +37 -0
- package/src/components/loading-spinner.tsx +113 -0
- package/src/components/modal.tsx +207 -0
- package/src/components/popover.tsx +62 -0
- package/src/components/progress.tsx +41 -0
- package/src/components/resizable-panel.tsx +434 -0
- package/src/components/resize-handle.tsx +187 -0
- package/src/components/select.tsx +160 -0
- package/src/components/separator.tsx +50 -0
- package/src/components/skeleton.tsx +37 -0
- package/src/components/switch.tsx +59 -0
- package/src/components/table.tsx +136 -0
- package/src/components/tabs.tsx +102 -0
- package/src/components/textarea.tsx +36 -0
- package/src/components/theme-picker.tsx +245 -0
- package/src/components/toaster.tsx +84 -0
- package/src/components/tooltip.tsx +199 -0
- package/src/index.ts +318 -0
- package/src/styles.css +96 -0
- package/src/tailwind-preset.ts +129 -0
- package/src/theme/index.ts +41 -0
- package/src/theme/presets.ts +502 -0
- package/src/theme/types.ts +164 -0
- package/src/theme/utils.ts +309 -0
- package/src/utils/cn.ts +14 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useKeyboardNavigation Hook
|
|
3
|
+
*
|
|
4
|
+
* Handles keyboard navigation for the DataGrid component including:
|
|
5
|
+
* - Arrow keys: Move between cells
|
|
6
|
+
* - Enter: Start editing / commit edit
|
|
7
|
+
* - Escape: Cancel edit
|
|
8
|
+
* - Tab/Shift+Tab: Move to next/previous cell
|
|
9
|
+
* - Home/End: Jump to first/last cell in row
|
|
10
|
+
* - Ctrl+Home/End: Jump to first/last cell in grid
|
|
11
|
+
* - Page Up/Down: Scroll by visible page
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useCallback, useRef } from "react";
|
|
15
|
+
import type { CellPosition, ColumnDef, EditingCell } from "../types";
|
|
16
|
+
|
|
17
|
+
export interface UseKeyboardNavigationOptions<T = any> {
|
|
18
|
+
/** Whether keyboard navigation is enabled */
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
/** Current focused cell */
|
|
21
|
+
focusedCell: CellPosition | null;
|
|
22
|
+
/** Current editing cell */
|
|
23
|
+
editingCell: EditingCell | null;
|
|
24
|
+
/** Column definitions (for determining visible columns) */
|
|
25
|
+
columns: ColumnDef<T>[];
|
|
26
|
+
/** Total number of rows */
|
|
27
|
+
rowCount: number;
|
|
28
|
+
/** Number of visible rows (for page up/down) */
|
|
29
|
+
visibleRowCount?: number;
|
|
30
|
+
/** Callback to set focused cell */
|
|
31
|
+
onFocusedCellChange: (cell: CellPosition | null) => void;
|
|
32
|
+
/** Callback to start editing */
|
|
33
|
+
onStartEditing: (rowIndex: number, columnKey: string) => void;
|
|
34
|
+
/** Callback to commit edit */
|
|
35
|
+
onCommitEdit: () => void;
|
|
36
|
+
/** Callback to cancel edit */
|
|
37
|
+
onCancelEdit: () => void;
|
|
38
|
+
/** Callback to scroll a row into view */
|
|
39
|
+
onScrollToRow?: (rowIndex: number) => void;
|
|
40
|
+
/** Callback to scroll a column into view */
|
|
41
|
+
onScrollToColumn?: (columnKey: string) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface UseKeyboardNavigationReturn {
|
|
45
|
+
/** Handler to attach to the grid container */
|
|
46
|
+
handleKeyDown: (event: React.KeyboardEvent) => void;
|
|
47
|
+
/** Ref to attach to the grid container for focus management */
|
|
48
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
49
|
+
/** Focus the container (useful after clicking a cell) */
|
|
50
|
+
focusContainer: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get visible (non-hidden) columns
|
|
55
|
+
*/
|
|
56
|
+
function getVisibleColumns<T>(columns: ColumnDef<T>[]): ColumnDef<T>[] {
|
|
57
|
+
return columns.filter((col) => !col.hidden);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Hook to handle keyboard navigation in the DataGrid
|
|
62
|
+
*/
|
|
63
|
+
export function useKeyboardNavigation<T = any>(
|
|
64
|
+
options: UseKeyboardNavigationOptions<T>
|
|
65
|
+
): UseKeyboardNavigationReturn {
|
|
66
|
+
const {
|
|
67
|
+
enabled,
|
|
68
|
+
focusedCell,
|
|
69
|
+
editingCell,
|
|
70
|
+
columns,
|
|
71
|
+
rowCount,
|
|
72
|
+
visibleRowCount = 10,
|
|
73
|
+
onFocusedCellChange,
|
|
74
|
+
onStartEditing,
|
|
75
|
+
onCommitEdit,
|
|
76
|
+
onCancelEdit,
|
|
77
|
+
onScrollToRow,
|
|
78
|
+
onScrollToColumn,
|
|
79
|
+
} = options;
|
|
80
|
+
|
|
81
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
82
|
+
|
|
83
|
+
// Get visible columns for navigation
|
|
84
|
+
const visibleColumns = getVisibleColumns(columns);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get column index from column key
|
|
88
|
+
*/
|
|
89
|
+
const getColumnIndex = useCallback(
|
|
90
|
+
(columnKey: string): number => {
|
|
91
|
+
return visibleColumns.findIndex((col) => col.key === columnKey);
|
|
92
|
+
},
|
|
93
|
+
[visibleColumns]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get column key from index
|
|
98
|
+
*/
|
|
99
|
+
const getColumnKey = useCallback(
|
|
100
|
+
(index: number): string | null => {
|
|
101
|
+
const column = visibleColumns[index];
|
|
102
|
+
return column ? column.key : null;
|
|
103
|
+
},
|
|
104
|
+
[visibleColumns]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Navigate to a new cell position
|
|
109
|
+
*/
|
|
110
|
+
const navigateToCell = useCallback(
|
|
111
|
+
(rowIndex: number, columnKey: string) => {
|
|
112
|
+
// Clamp row index
|
|
113
|
+
const clampedRow = Math.max(0, Math.min(rowIndex, rowCount - 1));
|
|
114
|
+
|
|
115
|
+
onFocusedCellChange({ rowIndex: clampedRow, columnKey });
|
|
116
|
+
onScrollToRow?.(clampedRow);
|
|
117
|
+
onScrollToColumn?.(columnKey);
|
|
118
|
+
},
|
|
119
|
+
[rowCount, onFocusedCellChange, onScrollToRow, onScrollToColumn]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Move focus by delta
|
|
124
|
+
*/
|
|
125
|
+
const moveFocus = useCallback(
|
|
126
|
+
(rowDelta: number, colDelta: number) => {
|
|
127
|
+
if (!focusedCell || visibleColumns.length === 0 || rowCount === 0) return;
|
|
128
|
+
|
|
129
|
+
const currentColIndex = getColumnIndex(focusedCell.columnKey);
|
|
130
|
+
if (currentColIndex === -1) return;
|
|
131
|
+
|
|
132
|
+
let newRowIndex = focusedCell.rowIndex + rowDelta;
|
|
133
|
+
let newColIndex = currentColIndex + colDelta;
|
|
134
|
+
|
|
135
|
+
// Handle wrapping for Tab navigation
|
|
136
|
+
if (colDelta !== 0) {
|
|
137
|
+
if (newColIndex < 0) {
|
|
138
|
+
// Wrap to previous row, last column
|
|
139
|
+
if (newRowIndex > 0) {
|
|
140
|
+
newRowIndex--;
|
|
141
|
+
newColIndex = visibleColumns.length - 1;
|
|
142
|
+
} else {
|
|
143
|
+
newColIndex = 0;
|
|
144
|
+
}
|
|
145
|
+
} else if (newColIndex >= visibleColumns.length) {
|
|
146
|
+
// Wrap to next row, first column
|
|
147
|
+
if (newRowIndex < rowCount - 1) {
|
|
148
|
+
newRowIndex++;
|
|
149
|
+
newColIndex = 0;
|
|
150
|
+
} else {
|
|
151
|
+
newColIndex = visibleColumns.length - 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Clamp values
|
|
157
|
+
newRowIndex = Math.max(0, Math.min(newRowIndex, rowCount - 1));
|
|
158
|
+
newColIndex = Math.max(0, Math.min(newColIndex, visibleColumns.length - 1));
|
|
159
|
+
|
|
160
|
+
const newColumnKey = getColumnKey(newColIndex);
|
|
161
|
+
if (newColumnKey) {
|
|
162
|
+
navigateToCell(newRowIndex, newColumnKey);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
[
|
|
166
|
+
focusedCell,
|
|
167
|
+
visibleColumns,
|
|
168
|
+
rowCount,
|
|
169
|
+
getColumnIndex,
|
|
170
|
+
getColumnKey,
|
|
171
|
+
navigateToCell,
|
|
172
|
+
]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Jump to first cell in row
|
|
177
|
+
*/
|
|
178
|
+
const jumpToRowStart = useCallback(() => {
|
|
179
|
+
if (!focusedCell || visibleColumns.length === 0) return;
|
|
180
|
+
const firstColumnKey = getColumnKey(0);
|
|
181
|
+
if (firstColumnKey) {
|
|
182
|
+
navigateToCell(focusedCell.rowIndex, firstColumnKey);
|
|
183
|
+
}
|
|
184
|
+
}, [focusedCell, visibleColumns.length, getColumnKey, navigateToCell]);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Jump to last cell in row
|
|
188
|
+
*/
|
|
189
|
+
const jumpToRowEnd = useCallback(() => {
|
|
190
|
+
if (!focusedCell || visibleColumns.length === 0) return;
|
|
191
|
+
const lastColumnKey = getColumnKey(visibleColumns.length - 1);
|
|
192
|
+
if (lastColumnKey) {
|
|
193
|
+
navigateToCell(focusedCell.rowIndex, lastColumnKey);
|
|
194
|
+
}
|
|
195
|
+
}, [focusedCell, visibleColumns.length, getColumnKey, navigateToCell]);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Jump to first cell in grid
|
|
199
|
+
*/
|
|
200
|
+
const jumpToGridStart = useCallback(() => {
|
|
201
|
+
if (visibleColumns.length === 0 || rowCount === 0) return;
|
|
202
|
+
const firstColumnKey = getColumnKey(0);
|
|
203
|
+
if (firstColumnKey) {
|
|
204
|
+
navigateToCell(0, firstColumnKey);
|
|
205
|
+
}
|
|
206
|
+
}, [visibleColumns.length, rowCount, getColumnKey, navigateToCell]);
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Jump to last cell in grid
|
|
210
|
+
*/
|
|
211
|
+
const jumpToGridEnd = useCallback(() => {
|
|
212
|
+
if (visibleColumns.length === 0 || rowCount === 0) return;
|
|
213
|
+
const lastColumnKey = getColumnKey(visibleColumns.length - 1);
|
|
214
|
+
if (lastColumnKey) {
|
|
215
|
+
navigateToCell(rowCount - 1, lastColumnKey);
|
|
216
|
+
}
|
|
217
|
+
}, [visibleColumns.length, rowCount, getColumnKey, navigateToCell]);
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Handle key down events
|
|
221
|
+
*/
|
|
222
|
+
const handleKeyDown = useCallback(
|
|
223
|
+
(event: React.KeyboardEvent) => {
|
|
224
|
+
if (!enabled) return;
|
|
225
|
+
|
|
226
|
+
const { key, ctrlKey, metaKey, shiftKey } = event;
|
|
227
|
+
const isModifierKey = ctrlKey || metaKey;
|
|
228
|
+
|
|
229
|
+
// If editing, only handle Enter and Escape
|
|
230
|
+
if (editingCell) {
|
|
231
|
+
switch (key) {
|
|
232
|
+
case "Enter":
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
onCommitEdit();
|
|
235
|
+
break;
|
|
236
|
+
case "Escape":
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
onCancelEdit();
|
|
239
|
+
break;
|
|
240
|
+
case "Tab":
|
|
241
|
+
// Commit and move to next cell
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
onCommitEdit();
|
|
244
|
+
moveFocus(0, shiftKey ? -1 : 1);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Navigation when not editing
|
|
251
|
+
switch (key) {
|
|
252
|
+
case "ArrowUp":
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
moveFocus(-1, 0);
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case "ArrowDown":
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
moveFocus(1, 0);
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case "ArrowLeft":
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
moveFocus(0, -1);
|
|
265
|
+
break;
|
|
266
|
+
|
|
267
|
+
case "ArrowRight":
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
moveFocus(0, 1);
|
|
270
|
+
break;
|
|
271
|
+
|
|
272
|
+
case "Tab":
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
moveFocus(0, shiftKey ? -1 : 1);
|
|
275
|
+
break;
|
|
276
|
+
|
|
277
|
+
case "Home":
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
if (isModifierKey) {
|
|
280
|
+
jumpToGridStart();
|
|
281
|
+
} else {
|
|
282
|
+
jumpToRowStart();
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
285
|
+
|
|
286
|
+
case "End":
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
if (isModifierKey) {
|
|
289
|
+
jumpToGridEnd();
|
|
290
|
+
} else {
|
|
291
|
+
jumpToRowEnd();
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
case "PageUp":
|
|
296
|
+
event.preventDefault();
|
|
297
|
+
moveFocus(-visibleRowCount, 0);
|
|
298
|
+
break;
|
|
299
|
+
|
|
300
|
+
case "PageDown":
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
moveFocus(visibleRowCount, 0);
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case "Enter":
|
|
306
|
+
case "F2":
|
|
307
|
+
// Start editing the focused cell
|
|
308
|
+
if (focusedCell) {
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
const column = columns.find(
|
|
311
|
+
(c) => c.key === focusedCell.columnKey
|
|
312
|
+
);
|
|
313
|
+
if (column?.editable) {
|
|
314
|
+
onStartEditing(focusedCell.rowIndex, focusedCell.columnKey);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
|
|
319
|
+
case "Escape":
|
|
320
|
+
// Clear focus
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
onFocusedCellChange(null);
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
[
|
|
327
|
+
enabled,
|
|
328
|
+
editingCell,
|
|
329
|
+
focusedCell,
|
|
330
|
+
columns,
|
|
331
|
+
visibleRowCount,
|
|
332
|
+
moveFocus,
|
|
333
|
+
jumpToRowStart,
|
|
334
|
+
jumpToRowEnd,
|
|
335
|
+
jumpToGridStart,
|
|
336
|
+
jumpToGridEnd,
|
|
337
|
+
onCommitEdit,
|
|
338
|
+
onCancelEdit,
|
|
339
|
+
onStartEditing,
|
|
340
|
+
onFocusedCellChange,
|
|
341
|
+
]
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Focus the container element
|
|
346
|
+
*/
|
|
347
|
+
const focusContainer = useCallback(() => {
|
|
348
|
+
containerRef.current?.focus();
|
|
349
|
+
}, []);
|
|
350
|
+
|
|
351
|
+
// Note: We intentionally don't auto-focus the container when focusedCell changes.
|
|
352
|
+
// This allows focus to naturally move to other elements (like search inputs) when
|
|
353
|
+
// the user clicks outside the grid. The container is focused explicitly via
|
|
354
|
+
// focusContainer() when the user clicks on a cell.
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
handleKeyDown,
|
|
358
|
+
containerRef,
|
|
359
|
+
focusContainer,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataGrid Module
|
|
3
|
+
*
|
|
4
|
+
* Export all DataGrid components, hooks, and types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Main component
|
|
8
|
+
export { DataGrid } from "./DataGrid";
|
|
9
|
+
|
|
10
|
+
// Types
|
|
11
|
+
export type {
|
|
12
|
+
// Column and data types
|
|
13
|
+
ColumnDef,
|
|
14
|
+
SelectOption,
|
|
15
|
+
FilterType,
|
|
16
|
+
EditorType,
|
|
17
|
+
|
|
18
|
+
// Value types
|
|
19
|
+
CellValue,
|
|
20
|
+
FilterValue,
|
|
21
|
+
|
|
22
|
+
// Filter types
|
|
23
|
+
FilterConfig,
|
|
24
|
+
FilterOperator,
|
|
25
|
+
TextFilterOperator,
|
|
26
|
+
NumberFilterOperator,
|
|
27
|
+
DateFilterOperator,
|
|
28
|
+
|
|
29
|
+
// Sort types
|
|
30
|
+
SortConfig,
|
|
31
|
+
|
|
32
|
+
// Cell types
|
|
33
|
+
CellPosition,
|
|
34
|
+
EditingCell,
|
|
35
|
+
CellEditEvent,
|
|
36
|
+
|
|
37
|
+
// Configuration types
|
|
38
|
+
PaginationConfig,
|
|
39
|
+
SearchConfig,
|
|
40
|
+
DataGridState,
|
|
41
|
+
|
|
42
|
+
// Props types
|
|
43
|
+
DataGridProps,
|
|
44
|
+
DataGridInternalState,
|
|
45
|
+
DataGridContextValue,
|
|
46
|
+
HeaderCellProps,
|
|
47
|
+
FilterPopoverProps,
|
|
48
|
+
CellEditorProps,
|
|
49
|
+
GridCellProps,
|
|
50
|
+
} from "./types";
|
|
51
|
+
|
|
52
|
+
// Sub-components (for advanced customization)
|
|
53
|
+
export { HeaderCell } from "./components/HeaderCell";
|
|
54
|
+
export { FilterPopover } from "./components/FilterPopover";
|
|
55
|
+
export { CellEditor } from "./components/CellEditor";
|
|
56
|
+
|
|
57
|
+
// Hooks (for building custom grid implementations)
|
|
58
|
+
export {
|
|
59
|
+
useDataGridState,
|
|
60
|
+
useKeyboardNavigation,
|
|
61
|
+
useColumnResize,
|
|
62
|
+
useColumnResizeManager,
|
|
63
|
+
} from "./hooks";
|
|
64
|
+
|
|
65
|
+
// Utilities (for custom data processing)
|
|
66
|
+
export {
|
|
67
|
+
getCellValue,
|
|
68
|
+
applySorting,
|
|
69
|
+
applyFilters,
|
|
70
|
+
applyFilterOperator,
|
|
71
|
+
} from "./utils";
|