@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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -0
  3. package/dist/index.cjs +6003 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +2310 -0
  6. package/dist/index.d.ts +2310 -0
  7. package/dist/index.js +5828 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/styles.css +96 -0
  10. package/dist/tailwind-preset.cjs +106 -0
  11. package/dist/tailwind-preset.cjs.map +1 -0
  12. package/dist/tailwind-preset.d.cts +23 -0
  13. package/dist/tailwind-preset.d.ts +23 -0
  14. package/dist/tailwind-preset.js +101 -0
  15. package/dist/tailwind-preset.js.map +1 -0
  16. package/package.json +154 -0
  17. package/src/components/accordion.tsx +187 -0
  18. package/src/components/alert-dialog.tsx +143 -0
  19. package/src/components/autocomplete.tsx +271 -0
  20. package/src/components/badge.tsx +62 -0
  21. package/src/components/button.tsx +85 -0
  22. package/src/components/calendar.tsx +235 -0
  23. package/src/components/card.tsx +94 -0
  24. package/src/components/checkbox.tsx +77 -0
  25. package/src/components/chip.tsx +77 -0
  26. package/src/components/confirmation-modal.tsx +195 -0
  27. package/src/components/context-menu.tsx +406 -0
  28. package/src/components/copy-button.tsx +84 -0
  29. package/src/components/data-grid/DataGrid.tsx +1027 -0
  30. package/src/components/data-grid/components/CellEditor.tsx +346 -0
  31. package/src/components/data-grid/components/FilterPopover.tsx +459 -0
  32. package/src/components/data-grid/components/HeaderCell.tsx +207 -0
  33. package/src/components/data-grid/components/index.ts +14 -0
  34. package/src/components/data-grid/hooks/index.ts +28 -0
  35. package/src/components/data-grid/hooks/useColumnResize.ts +378 -0
  36. package/src/components/data-grid/hooks/useDataGridState.ts +346 -0
  37. package/src/components/data-grid/hooks/useKeyboardNavigation.ts +361 -0
  38. package/src/components/data-grid/index.ts +71 -0
  39. package/src/components/data-grid/types.ts +478 -0
  40. package/src/components/data-grid/utils/dataProcessing.ts +277 -0
  41. package/src/components/data-grid/utils/index.ts +12 -0
  42. package/src/components/date-picker.tsx +366 -0
  43. package/src/components/dropdown-menu.tsx +230 -0
  44. package/src/components/icon-button.tsx +157 -0
  45. package/src/components/input.tsx +40 -0
  46. package/src/components/label.tsx +37 -0
  47. package/src/components/loading-spinner.tsx +113 -0
  48. package/src/components/modal.tsx +207 -0
  49. package/src/components/popover.tsx +62 -0
  50. package/src/components/progress.tsx +41 -0
  51. package/src/components/resizable-panel.tsx +434 -0
  52. package/src/components/resize-handle.tsx +187 -0
  53. package/src/components/select.tsx +160 -0
  54. package/src/components/separator.tsx +50 -0
  55. package/src/components/skeleton.tsx +37 -0
  56. package/src/components/switch.tsx +59 -0
  57. package/src/components/table.tsx +136 -0
  58. package/src/components/tabs.tsx +102 -0
  59. package/src/components/textarea.tsx +36 -0
  60. package/src/components/theme-picker.tsx +245 -0
  61. package/src/components/toaster.tsx +84 -0
  62. package/src/components/tooltip.tsx +199 -0
  63. package/src/index.ts +318 -0
  64. package/src/styles.css +96 -0
  65. package/src/tailwind-preset.ts +129 -0
  66. package/src/theme/index.ts +41 -0
  67. package/src/theme/presets.ts +502 -0
  68. package/src/theme/types.ts +164 -0
  69. package/src/theme/utils.ts +309 -0
  70. 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";