gp-grid-react 0.1.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/src/styles.ts ADDED
@@ -0,0 +1,436 @@
1
+ // packages/react/src/styles.ts
2
+ // Dynamic CSS injection for gp-grid-react
3
+
4
+ const STYLE_ID = "gp-grid-styles";
5
+
6
+ export const gridStyles = `
7
+ /* =============================================================================
8
+ GP Grid - CSS Variables for Theming
9
+ ============================================================================= */
10
+
11
+ .gp-grid-container {
12
+ /* Colors - Light Mode (default) */
13
+ --gp-grid-bg: #ffffff;
14
+ --gp-grid-bg-alt: #f8f9fa;
15
+ --gp-grid-text: #212529;
16
+ --gp-grid-text-secondary: #6c757d;
17
+ --gp-grid-text-muted: #adb5bd;
18
+ --gp-grid-border: #dee2e6;
19
+ --gp-grid-border-light: #e9ecef;
20
+
21
+ /* Header */
22
+ --gp-grid-header-bg: #f1f3f5;
23
+ --gp-grid-header-text: #212529;
24
+
25
+ /* Selection */
26
+ --gp-grid-primary: #228be6;
27
+ --gp-grid-primary-light: #e7f5ff;
28
+ --gp-grid-primary-border: #74c0fc;
29
+ --gp-grid-hover: #f1f3f5;
30
+
31
+ /* Filter */
32
+ --gp-grid-filter-bg: #f8f9fa;
33
+ --gp-grid-input-bg: #ffffff;
34
+ --gp-grid-input-border: #ced4da;
35
+
36
+ /* Error */
37
+ --gp-grid-error-bg: #fff5f5;
38
+ --gp-grid-error-text: #c92a2a;
39
+
40
+ /* Loading */
41
+ --gp-grid-loading-bg: rgba(255, 255, 255, 0.95);
42
+ --gp-grid-loading-text: #495057;
43
+
44
+ /* Scrollbar */
45
+ --gp-grid-scrollbar-track: #f1f3f5;
46
+ --gp-grid-scrollbar-thumb: #ced4da;
47
+ --gp-grid-scrollbar-thumb-hover: #adb5bd;
48
+ }
49
+
50
+ /* Dark Mode */
51
+ .gp-grid-container--dark {
52
+ --gp-grid-bg: #1a1b1e;
53
+ --gp-grid-bg-alt: #25262b;
54
+ --gp-grid-text: #c1c2c5;
55
+ --gp-grid-text-secondary: #909296;
56
+ --gp-grid-text-muted: #5c5f66;
57
+ --gp-grid-border: #373a40;
58
+ --gp-grid-border-light: #2c2e33;
59
+
60
+ /* Header */
61
+ --gp-grid-header-bg: #25262b;
62
+ --gp-grid-header-text: #c1c2c5;
63
+
64
+ /* Selection */
65
+ --gp-grid-primary: #339af0;
66
+ --gp-grid-primary-light: #1c3d5a;
67
+ --gp-grid-primary-border: #1c7ed6;
68
+ --gp-grid-hover: #2c2e33;
69
+
70
+ /* Filter */
71
+ --gp-grid-filter-bg: #25262b;
72
+ --gp-grid-input-bg: #1a1b1e;
73
+ --gp-grid-input-border: #373a40;
74
+
75
+ /* Error */
76
+ --gp-grid-error-bg: #2c1a1a;
77
+ --gp-grid-error-text: #ff6b6b;
78
+
79
+ /* Loading */
80
+ --gp-grid-loading-bg: rgba(26, 27, 30, 0.95);
81
+ --gp-grid-loading-text: #c1c2c5;
82
+
83
+ /* Scrollbar */
84
+ --gp-grid-scrollbar-track: #25262b;
85
+ --gp-grid-scrollbar-thumb: #373a40;
86
+ --gp-grid-scrollbar-thumb-hover: #4a4d52;
87
+ }
88
+
89
+ /* =============================================================================
90
+ GP Grid - Clean Flat Design
91
+ ============================================================================= */
92
+
93
+ /* Grid Container */
94
+ .gp-grid-container {
95
+ outline: none;
96
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
97
+ font-size: 13px;
98
+ line-height: 1.5;
99
+ color: var(--gp-grid-text);
100
+ background-color: var(--gp-grid-bg);
101
+ border: 1px solid var(--gp-grid-border);
102
+ border-radius: 6px;
103
+ }
104
+
105
+ .gp-grid-container:focus {
106
+ outline: none;
107
+ border-color: var(--gp-grid-primary);
108
+ }
109
+
110
+ /* =============================================================================
111
+ Header
112
+ ============================================================================= */
113
+
114
+ .gp-grid-header {
115
+ position: sticky;
116
+ top: 0;
117
+ left: 0;
118
+ z-index: 100;
119
+ background-color: var(--gp-grid-header-bg);
120
+ border-bottom: 1px solid var(--gp-grid-border);
121
+ }
122
+
123
+ .gp-grid-container .gp-grid-header-cell {
124
+ position: absolute;
125
+ box-sizing: border-box;
126
+ border-right: 1px solid var(--gp-grid-border);
127
+ font-weight: 600;
128
+ font-size: 12px;
129
+ text-transform: uppercase;
130
+ letter-spacing: 0.5px;
131
+ color: var(--gp-grid-header-text);
132
+ cursor: pointer;
133
+ user-select: none;
134
+ display: flex;
135
+ align-items: center;
136
+ padding: 0 12px;
137
+ background-color: transparent;
138
+ transition: background-color 0.1s ease;
139
+ }
140
+
141
+ .gp-grid-container .gp-grid-header-cell:hover {
142
+ background-color: var(--gp-grid-hover);
143
+ }
144
+
145
+ .gp-grid-container .gp-grid-header-cell:active {
146
+ background-color: var(--gp-grid-border-light);
147
+ }
148
+
149
+ .gp-grid-container .gp-grid-header-text {
150
+ flex: 1;
151
+ overflow: hidden;
152
+ text-overflow: ellipsis;
153
+ white-space: nowrap;
154
+ color: var(--gp-grid-header-text);
155
+ }
156
+
157
+ .gp-grid-sort-indicator {
158
+ margin-left: 6px;
159
+ font-size: 10px;
160
+ color: var(--gp-grid-primary);
161
+ display: flex;
162
+ align-items: center;
163
+ }
164
+
165
+ .gp-grid-sort-index {
166
+ font-size: 9px;
167
+ margin-left: 2px;
168
+ color: var(--gp-grid-text-secondary);
169
+ }
170
+
171
+ /* =============================================================================
172
+ Filter Row
173
+ ============================================================================= */
174
+
175
+ .gp-grid-filter-row {
176
+ position: sticky;
177
+ left: 0;
178
+ z-index: 99;
179
+ background-color: var(--gp-grid-filter-bg);
180
+ border-bottom: 1px solid var(--gp-grid-border);
181
+ }
182
+
183
+ .gp-grid-filter-cell {
184
+ position: absolute;
185
+ box-sizing: border-box;
186
+ border-right: 1px solid var(--gp-grid-border);
187
+ padding: 6px 8px;
188
+ display: flex;
189
+ align-items: center;
190
+ background-color: var(--gp-grid-filter-bg);
191
+ }
192
+
193
+ .gp-grid-filter-input {
194
+ width: 100%;
195
+ height: 28px;
196
+ padding: 0 10px;
197
+ font-family: inherit;
198
+ font-size: 12px;
199
+ border: 1px solid var(--gp-grid-input-border);
200
+ border-radius: 4px;
201
+ background-color: var(--gp-grid-input-bg);
202
+ color: var(--gp-grid-text);
203
+ transition: border-color 0.15s ease;
204
+ }
205
+
206
+ .gp-grid-filter-input:focus {
207
+ outline: none;
208
+ border-color: var(--gp-grid-primary);
209
+ }
210
+
211
+ .gp-grid-filter-input::placeholder {
212
+ color: var(--gp-grid-text-muted);
213
+ }
214
+
215
+ /* =============================================================================
216
+ Data Cells
217
+ ============================================================================= */
218
+
219
+ .gp-grid-row {
220
+ position: absolute;
221
+ top: 0;
222
+ left: 0;
223
+ }
224
+
225
+ .gp-grid-cell {
226
+ position: absolute;
227
+ top: 0;
228
+ box-sizing: border-box;
229
+ padding: 0 12px;
230
+ display: flex;
231
+ align-items: center;
232
+ cursor: cell;
233
+ color: var(--gp-grid-text);
234
+ border-right: 1px solid var(--gp-grid-border-light);
235
+ border-bottom: 1px solid var(--gp-grid-border-light);
236
+ background-color: var(--gp-grid-bg);
237
+ overflow: hidden;
238
+ text-overflow: ellipsis;
239
+ white-space: nowrap;
240
+ }
241
+
242
+ /* Alternating row colors */
243
+ .gp-grid-row--even .gp-grid-cell {
244
+ background-color: var(--gp-grid-bg-alt);
245
+ }
246
+
247
+ .gp-grid-cell:hover {
248
+ background-color: var(--gp-grid-hover) !important;
249
+ }
250
+
251
+ /* Active cell (focused) */
252
+ .gp-grid-cell--active {
253
+ background-color: var(--gp-grid-primary-light) !important;
254
+ border: 2px solid var(--gp-grid-primary) !important;
255
+ outline: none;
256
+ z-index: 5;
257
+ padding: 0 11px;
258
+ }
259
+
260
+ /* Selected cells (range selection) */
261
+ .gp-grid-cell--selected {
262
+ background-color: var(--gp-grid-primary-light) !important;
263
+ }
264
+
265
+ /* Editing cell */
266
+ .gp-grid-cell--editing {
267
+ background-color: var(--gp-grid-bg) !important;
268
+ border: 2px solid var(--gp-grid-primary) !important;
269
+ padding: 0 !important;
270
+ z-index: 10;
271
+ }
272
+
273
+ /* =============================================================================
274
+ Fill Handle (drag to fill)
275
+ ============================================================================= */
276
+
277
+ .gp-grid-fill-handle {
278
+ position: absolute;
279
+ width: 8px;
280
+ height: 8px;
281
+ background-color: var(--gp-grid-primary);
282
+ border: 2px solid var(--gp-grid-bg);
283
+ cursor: crosshair;
284
+ z-index: 100;
285
+ pointer-events: auto;
286
+ box-sizing: border-box;
287
+ border-radius: 1px;
288
+ }
289
+
290
+ .gp-grid-fill-handle:hover {
291
+ transform: scale(1.2);
292
+ }
293
+
294
+ /* Fill preview (cells being filled) */
295
+ .gp-grid-cell--fill-preview {
296
+ background-color: var(--gp-grid-primary-light) !important;
297
+ border: 1px dashed var(--gp-grid-primary) !important;
298
+ }
299
+
300
+ /* =============================================================================
301
+ Edit Input
302
+ ============================================================================= */
303
+
304
+ .gp-grid-edit-input {
305
+ width: 100%;
306
+ height: 100%;
307
+ padding: 0 11px;
308
+ font-family: inherit;
309
+ font-size: inherit;
310
+ color: var(--gp-grid-text);
311
+ border: none;
312
+ background-color: transparent;
313
+ }
314
+
315
+ .gp-grid-edit-input:focus {
316
+ outline: none;
317
+ }
318
+
319
+ /* =============================================================================
320
+ Loading & Error States
321
+ ============================================================================= */
322
+
323
+ .gp-grid-loading {
324
+ position: absolute;
325
+ top: 50%;
326
+ left: 50%;
327
+ transform: translate(-50%, -50%);
328
+ padding: 12px 20px;
329
+ background-color: var(--gp-grid-loading-bg);
330
+ color: var(--gp-grid-loading-text);
331
+ border-radius: 6px;
332
+ border: 1px solid var(--gp-grid-border);
333
+ font-weight: 500;
334
+ font-size: 13px;
335
+ z-index: 1000;
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 10px;
339
+ }
340
+
341
+ .gp-grid-loading-spinner {
342
+ width: 16px;
343
+ height: 16px;
344
+ border: 2px solid var(--gp-grid-border);
345
+ border-top-color: var(--gp-grid-primary);
346
+ border-radius: 50%;
347
+ animation: gp-grid-spin 0.7s linear infinite;
348
+ }
349
+
350
+ @keyframes gp-grid-spin {
351
+ to {
352
+ transform: rotate(360deg);
353
+ }
354
+ }
355
+
356
+ .gp-grid-error {
357
+ position: absolute;
358
+ top: 50%;
359
+ left: 50%;
360
+ transform: translate(-50%, -50%);
361
+ padding: 12px 20px;
362
+ background-color: var(--gp-grid-error-bg);
363
+ color: var(--gp-grid-error-text);
364
+ border-radius: 6px;
365
+ border: 1px solid var(--gp-grid-error-text);
366
+ font-weight: 500;
367
+ font-size: 13px;
368
+ z-index: 1000;
369
+ max-width: 80%;
370
+ text-align: center;
371
+ }
372
+
373
+ /* =============================================================================
374
+ Empty State
375
+ ============================================================================= */
376
+
377
+ .gp-grid-empty {
378
+ position: absolute;
379
+ top: 50%;
380
+ left: 50%;
381
+ transform: translate(-50%, -50%);
382
+ color: var(--gp-grid-text-muted);
383
+ font-size: 14px;
384
+ text-align: center;
385
+ }
386
+
387
+ /* =============================================================================
388
+ Scrollbar Styling
389
+ ============================================================================= */
390
+
391
+ .gp-grid-container::-webkit-scrollbar {
392
+ width: 8px;
393
+ height: 8px;
394
+ }
395
+
396
+ .gp-grid-container::-webkit-scrollbar-track {
397
+ background-color: var(--gp-grid-scrollbar-track);
398
+ }
399
+
400
+ .gp-grid-container::-webkit-scrollbar-thumb {
401
+ background-color: var(--gp-grid-scrollbar-thumb);
402
+ border-radius: 4px;
403
+ }
404
+
405
+ .gp-grid-container::-webkit-scrollbar-thumb:hover {
406
+ background-color: var(--gp-grid-scrollbar-thumb-hover);
407
+ }
408
+
409
+ .gp-grid-container::-webkit-scrollbar-corner {
410
+ background-color: var(--gp-grid-scrollbar-track);
411
+ }
412
+ `;
413
+
414
+ let stylesInjected = false;
415
+
416
+ /**
417
+ * Inject grid styles into the document head.
418
+ * This is called automatically when the Grid component mounts.
419
+ * Styles are only injected once, even if multiple Grid instances exist.
420
+ */
421
+ export function injectStyles(): void {
422
+ if (stylesInjected) return;
423
+ if (typeof document === "undefined") return; // SSR safety
424
+
425
+ // Check if styles already exist (e.g., from a previous mount)
426
+ if (document.getElementById(STYLE_ID)) {
427
+ stylesInjected = true;
428
+ return;
429
+ }
430
+
431
+ const styleElement = document.createElement("style");
432
+ styleElement.id = STYLE_ID;
433
+ styleElement.textContent = gridStyles;
434
+ document.head.appendChild(styleElement);
435
+ stylesInjected = true;
436
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": [
5
+ "ES2020",
6
+ "DOM",
7
+ "DOM.Iterable"
8
+ ],
9
+ "module": "ESNext",
10
+ "moduleResolution": "bundler",
11
+ "jsx": "react-jsx",
12
+ "declaration": true,
13
+ "outDir": "dist",
14
+ "noImplicitAny": false,
15
+ "strict": true,
16
+ "esModuleInterop": true,
17
+ "allowSyntheticDefaultImports": true,
18
+ "skipLibCheck": true,
19
+ "baseUrl": ".",
20
+ "paths": {
21
+ "gp-grid-core": [
22
+ "../core/src"
23
+ ]
24
+ }
25
+ },
26
+ "include": [
27
+ "src"
28
+ ]
29
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: ["./src/index.ts"],
5
+ platform: "neutral",
6
+ dts: true,
7
+ sourcemap: true,
8
+ format: ["esm"],
9
+ clean: true,
10
+ external: ["react", "react-dom", "react/jsx-runtime"],
11
+ });
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ test: {
7
+ globals: true,
8
+ environment: 'happy-dom',
9
+ coverage: {
10
+ provider: 'v8',
11
+ reporter: ['text', 'json', 'html'],
12
+ include: ['src/**/*.tsx', 'src/**/*.ts'],
13
+ exclude: ['src/**/*.test.tsx', 'src/**/*.test.ts', 'src/**/*.bench.ts'],
14
+ },
15
+ },
16
+ });