aplance-ui 1.5.1 → 1.6.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/README.md +1 -1
- package/dist/api.d.ts +2 -1
- package/dist/api.js +103 -68
- package/dist/api.js.map +1 -1
- package/dist/frameworks/vite-react/vite.config.ts.template +1 -1
- package/dist/index.js +103 -68
- package/dist/index.js.map +1 -1
- package/package.json +12 -6
- package/registry/components/primitives/modal/modal.module.css +9 -3
- package/registry/components/primitives/popover/popover-context.ts +2 -0
- package/registry/components/primitives/popover/popover.tsx +47 -2
- package/registry/components/ui/chart/chart.tsx +51 -30
- package/registry/components/ui/chart/types.ts +5 -1
- package/registry/components/ui/data-table/checkbox-column.tsx +12 -8
- package/registry/components/ui/data-table/data-table.module.css +41 -19
- package/registry/components/ui/data-table/data-table.tsx +1285 -1238
- package/registry/components/ui/data-table/filter-bar.tsx +56 -13
- package/registry/components/ui/data-table/filter-modal.tsx +16 -82
- package/registry/components/ui/data-table/filter-sheet.tsx +97 -0
- package/registry/components/ui/data-table/filters.tsx +21 -21
- package/registry/components/ui/data-table/pagination-bar.tsx +4 -6
- package/registry/components/ui/data-table/types.ts +149 -80
- package/registry/components/ui/data-table/useDataTable.tsx +35 -16
- package/registry/components/ui/data-table/utils.ts +691 -471
- package/registry/components/ui/input-fields/date-picker/calendar.tsx +7 -15
- package/registry/components/ui/input-fields/date-picker/date-picker.tsx +1 -1
- package/registry/components/ui/input-fields/date-picker/types.ts +3 -3
- package/registry/components/ui/input-fields/emoji-picker/emoji-palette.tsx +1 -1
- package/registry/registry.json +17 -12
- package/registry/styles/media.css +1 -1
|
@@ -1,1238 +1,1285 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} from "
|
|
48
|
-
import
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} from "
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}),
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}, [
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (!
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
{/*
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
>
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
{
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
FlexRender,
|
|
3
|
+
useTable,
|
|
4
|
+
type ColumnPinningState,
|
|
5
|
+
type RowData,
|
|
6
|
+
type Updater,
|
|
7
|
+
} from "@tanstack/react-table";
|
|
8
|
+
import {
|
|
9
|
+
useCallback,
|
|
10
|
+
useEffect,
|
|
11
|
+
useLayoutEffect,
|
|
12
|
+
useMemo,
|
|
13
|
+
useRef,
|
|
14
|
+
useState,
|
|
15
|
+
type CSSProperties,
|
|
16
|
+
type DragEvent as ReactDragEvent,
|
|
17
|
+
type MouseEvent as ReactMouseEvent,
|
|
18
|
+
type ReactNode,
|
|
19
|
+
} from "react";
|
|
20
|
+
import clsx from "clsx";
|
|
21
|
+
import { Filters } from "./filters";
|
|
22
|
+
import { PaginationBar } from "./pagination-bar";
|
|
23
|
+
import { Loader } from "../loader/loader";
|
|
24
|
+
import type {
|
|
25
|
+
BodyCellProps,
|
|
26
|
+
BodyRowProps,
|
|
27
|
+
DataTableFeatures,
|
|
28
|
+
DataTableMeta,
|
|
29
|
+
DataTableProps,
|
|
30
|
+
FooterCellProps,
|
|
31
|
+
HeaderActionButtonProps,
|
|
32
|
+
HeaderCellProps,
|
|
33
|
+
} from "./types";
|
|
34
|
+
import {
|
|
35
|
+
columnId,
|
|
36
|
+
dataTableFeatures,
|
|
37
|
+
formatAggregated,
|
|
38
|
+
getColumnStyle,
|
|
39
|
+
growColumnWidths,
|
|
40
|
+
isLastPinned,
|
|
41
|
+
normalizeColumnPinningState,
|
|
42
|
+
reorderColumnOrder,
|
|
43
|
+
resolveUpdater,
|
|
44
|
+
SELECT_COLUMN_ID,
|
|
45
|
+
} from "./utils";
|
|
46
|
+
import s from "./data-table.module.css";
|
|
47
|
+
import { Tooltip } from "../tooltip/tooltip";
|
|
48
|
+
import {
|
|
49
|
+
IconArrowsUpDown,
|
|
50
|
+
IconArrowUp,
|
|
51
|
+
IconArrowDown,
|
|
52
|
+
IconChevronRight,
|
|
53
|
+
IconGripVertical,
|
|
54
|
+
IconStack2,
|
|
55
|
+
IconStack3,
|
|
56
|
+
IconPinnedOff,
|
|
57
|
+
IconArrowLeft,
|
|
58
|
+
IconArrowRight,
|
|
59
|
+
} from "@tabler/icons-react";
|
|
60
|
+
import { getScrollbarWidth } from "../../../lib/dom";
|
|
61
|
+
import { Button } from "../button/button";
|
|
62
|
+
|
|
63
|
+
const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
|
|
64
|
+
const DEFAULT_PAGINATION = { pageIndex: 0, pageSize: 20 };
|
|
65
|
+
// Stable refs for optional handlers. Inline `= () => {}` defaults allocate a
|
|
66
|
+
// fresh function each render, invalidating any memo that depends on them
|
|
67
|
+
// (notably `meta` below, which passes `onUpdateData` through tanstack).
|
|
68
|
+
const NOOP_FILTER = () => {};
|
|
69
|
+
const NOOP_PAGINATION = () => {};
|
|
70
|
+
const NOOP_UPDATE = () => {};
|
|
71
|
+
const POINTER_STYLE = { cursor: "pointer" } as const;
|
|
72
|
+
// Controls a row can hold. A click on one is that control's, not the row's.
|
|
73
|
+
const ROW_CONTROLS =
|
|
74
|
+
'a, button, input, select, textarea, label, summary, [contenteditable]:not([contenteditable="false"]), [role="button"], [role="link"], [role="menuitem"], [role="checkbox"], [role="radio"], [role="switch"], [role="option"], [role="tab"]';
|
|
75
|
+
const EMPTY_FILTER_VALUES: Record<string, string> = {};
|
|
76
|
+
const ICON_SIZE = 15;
|
|
77
|
+
const SORT_ICON_SIZE = 13;
|
|
78
|
+
const ICON_STROKE = 1.6;
|
|
79
|
+
// Grace period after `isLoading` flips false before committing to the empty
|
|
80
|
+
// state. Eliminates the flash when consumers set isLoading=false a render
|
|
81
|
+
// before populating rows, without making genuinely-empty results feel sluggish.
|
|
82
|
+
const EMPTY_STATE_GRACE_MS = 150;
|
|
83
|
+
|
|
84
|
+
const MIN_SKELETON_ROWS = 1;
|
|
85
|
+
const useIsomorphicLayoutEffect =
|
|
86
|
+
typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A group row's aggregate for a column without its own `aggregatedCell`.
|
|
90
|
+
* Module-level so the table's `defaultColumn` stays one reference: a new one
|
|
91
|
+
* each render would rebuild every column. The context is typed on the two
|
|
92
|
+
* fields it reads, so it fits every table's rows despite tanstack's invariant
|
|
93
|
+
* row type.
|
|
94
|
+
*/
|
|
95
|
+
const DEFAULT_COLUMN = {
|
|
96
|
+
aggregatedCell: ({
|
|
97
|
+
column,
|
|
98
|
+
getValue,
|
|
99
|
+
}: {
|
|
100
|
+
column: { columnDef: { aggregationFn?: unknown } };
|
|
101
|
+
getValue: () => unknown;
|
|
102
|
+
}) => {
|
|
103
|
+
const text = formatAggregated(getValue(), column.columnDef.aggregationFn);
|
|
104
|
+
return text === null ? null : (
|
|
105
|
+
<span className={s.cell_aggregated}>{text}</span>
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const DataTable = <TData extends { id: string }>({
|
|
111
|
+
data,
|
|
112
|
+
columns,
|
|
113
|
+
isLoading = false,
|
|
114
|
+
emptyMessage = "No results found.",
|
|
115
|
+
|
|
116
|
+
filters = [],
|
|
117
|
+
filterValues = EMPTY_FILTER_VALUES,
|
|
118
|
+
onFilterChange = NOOP_FILTER,
|
|
119
|
+
|
|
120
|
+
sorting,
|
|
121
|
+
onSortingChange,
|
|
122
|
+
manualSorting = false,
|
|
123
|
+
|
|
124
|
+
showPagination = true,
|
|
125
|
+
pagination = DEFAULT_PAGINATION,
|
|
126
|
+
onPaginationChange = NOOP_PAGINATION,
|
|
127
|
+
manualPagination = true,
|
|
128
|
+
totalPages,
|
|
129
|
+
totalCount,
|
|
130
|
+
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
131
|
+
showPageSizeSelect = true,
|
|
132
|
+
rowLabel = "row",
|
|
133
|
+
|
|
134
|
+
rowsSelectable = false,
|
|
135
|
+
getRowCanSelect,
|
|
136
|
+
rowSelection = {},
|
|
137
|
+
onRowSelectionChange,
|
|
138
|
+
|
|
139
|
+
editingRowId = null,
|
|
140
|
+
onUpdateData = NOOP_UPDATE,
|
|
141
|
+
|
|
142
|
+
enableColumnOrdering = false,
|
|
143
|
+
columnOrder,
|
|
144
|
+
onColumnOrderChange,
|
|
145
|
+
enableColumnResizing = false,
|
|
146
|
+
columnSizing,
|
|
147
|
+
onColumnSizingChange,
|
|
148
|
+
enableColumnPinning = false,
|
|
149
|
+
columnPinning,
|
|
150
|
+
onColumnPinningChange,
|
|
151
|
+
enableRowOrdering = false,
|
|
152
|
+
onRowOrderChange,
|
|
153
|
+
columnVisibility,
|
|
154
|
+
onColumnVisibilityChange,
|
|
155
|
+
|
|
156
|
+
enableGrouping = false,
|
|
157
|
+
grouping,
|
|
158
|
+
onGroupingChange,
|
|
159
|
+
expanded,
|
|
160
|
+
onExpandedChange,
|
|
161
|
+
|
|
162
|
+
defaultColumnPinning,
|
|
163
|
+
|
|
164
|
+
showVerticalBorders = false,
|
|
165
|
+
|
|
166
|
+
onRow,
|
|
167
|
+
paginationExtra,
|
|
168
|
+
paginationLabel,
|
|
169
|
+
className,
|
|
170
|
+
}: DataTableProps<TData>) => {
|
|
171
|
+
// Collapse the filter row into a button + modal once it no longer fits inline.
|
|
172
|
+
|
|
173
|
+
// ── Table meta ──────────────────────────────────────────────────────────────
|
|
174
|
+
const meta: DataTableMeta = useMemo(
|
|
175
|
+
() => ({ editingRowId, updateData: onUpdateData }),
|
|
176
|
+
[editingRowId, onUpdateData],
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const shouldPinSelectionColumn =
|
|
180
|
+
rowsSelectable &&
|
|
181
|
+
columns.some((columnDef) => columnId(columnDef) === SELECT_COLUMN_ID);
|
|
182
|
+
|
|
183
|
+
const resolvedControlledColumnPinning = useMemo(
|
|
184
|
+
() =>
|
|
185
|
+
columnPinning === undefined
|
|
186
|
+
? undefined
|
|
187
|
+
: normalizeColumnPinningState(
|
|
188
|
+
columnPinning,
|
|
189
|
+
shouldPinSelectionColumn,
|
|
190
|
+
defaultColumnPinning,
|
|
191
|
+
),
|
|
192
|
+
[columnPinning, shouldPinSelectionColumn, defaultColumnPinning],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Uncontrolled pinning starts from the default pins.
|
|
196
|
+
const initialColumnPinning = useMemo(
|
|
197
|
+
() =>
|
|
198
|
+
enableColumnPinning && columnPinning === undefined
|
|
199
|
+
? normalizeColumnPinningState(
|
|
200
|
+
{
|
|
201
|
+
start: defaultColumnPinning?.start ?? [],
|
|
202
|
+
end: defaultColumnPinning?.end ?? [],
|
|
203
|
+
},
|
|
204
|
+
shouldPinSelectionColumn,
|
|
205
|
+
defaultColumnPinning,
|
|
206
|
+
)
|
|
207
|
+
: undefined,
|
|
208
|
+
[
|
|
209
|
+
enableColumnPinning,
|
|
210
|
+
columnPinning,
|
|
211
|
+
shouldPinSelectionColumn,
|
|
212
|
+
defaultColumnPinning,
|
|
213
|
+
],
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const handleColumnPinningChange = useCallback(
|
|
217
|
+
(updater: Updater<ColumnPinningState>) => {
|
|
218
|
+
if (!onColumnPinningChange) return;
|
|
219
|
+
|
|
220
|
+
onColumnPinningChange((prev) => {
|
|
221
|
+
const normalizedPrev = normalizeColumnPinningState(
|
|
222
|
+
prev,
|
|
223
|
+
shouldPinSelectionColumn,
|
|
224
|
+
defaultColumnPinning,
|
|
225
|
+
) ?? { start: [], end: [] };
|
|
226
|
+
const next = resolveUpdater(updater, normalizedPrev);
|
|
227
|
+
|
|
228
|
+
// Newly end-pinned columns are inserted on the inner edge (closer
|
|
229
|
+
// to the center) so the recently-pinned column visually slides in
|
|
230
|
+
// next to the body, leaving previously-pinned columns flush against
|
|
231
|
+
// the viewport edge.
|
|
232
|
+
const addedToEnd = next.end.filter(
|
|
233
|
+
(id) => !normalizedPrev.end.includes(id),
|
|
234
|
+
);
|
|
235
|
+
const keptInEnd = next.end.filter((id) =>
|
|
236
|
+
normalizedPrev.end.includes(id),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
normalizeColumnPinningState(
|
|
241
|
+
{ start: next.start, end: [...addedToEnd, ...keptInEnd] },
|
|
242
|
+
shouldPinSelectionColumn,
|
|
243
|
+
defaultColumnPinning,
|
|
244
|
+
) ?? { start: [], end: [] }
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
[onColumnPinningChange, shouldPinSelectionColumn, defaultColumnPinning],
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// ── TanStack table instance ─────────────────────────────────────────────────
|
|
252
|
+
// Filtering is delegated to the consumer (see `useDataTable.filterFn`), so
|
|
253
|
+
// `dataTableFeatures` registers no filtering - rows arrive pre-filtered.
|
|
254
|
+
const table = useTable<DataTableFeatures, TData>({
|
|
255
|
+
features: dataTableFeatures,
|
|
256
|
+
data,
|
|
257
|
+
columns,
|
|
258
|
+
defaultColumn: DEFAULT_COLUMN,
|
|
259
|
+
enableRowSelection: rowsSelectable ? (getRowCanSelect ?? true) : false,
|
|
260
|
+
enableColumnResizing,
|
|
261
|
+
enableColumnPinning,
|
|
262
|
+
enableGrouping,
|
|
263
|
+
columnResizeMode: "onChange",
|
|
264
|
+
state: {
|
|
265
|
+
sorting,
|
|
266
|
+
...(showPagination && { pagination }),
|
|
267
|
+
...(rowsSelectable && { rowSelection }),
|
|
268
|
+
...(enableColumnOrdering && columnOrder && { columnOrder }),
|
|
269
|
+
...(enableColumnResizing && columnSizing && { columnSizing }),
|
|
270
|
+
...(enableColumnPinning &&
|
|
271
|
+
resolvedControlledColumnPinning && {
|
|
272
|
+
columnPinning: resolvedControlledColumnPinning,
|
|
273
|
+
}),
|
|
274
|
+
...(columnVisibility && { columnVisibility }),
|
|
275
|
+
...(enableGrouping && grouping && { grouping }),
|
|
276
|
+
...(enableGrouping && expanded !== undefined && { expanded }),
|
|
277
|
+
},
|
|
278
|
+
initialState: {
|
|
279
|
+
...(enableColumnPinning &&
|
|
280
|
+
initialColumnPinning && {
|
|
281
|
+
columnPinning: initialColumnPinning,
|
|
282
|
+
}),
|
|
283
|
+
},
|
|
284
|
+
onSortingChange,
|
|
285
|
+
...(showPagination && {
|
|
286
|
+
onPaginationChange: (updater) =>
|
|
287
|
+
onPaginationChange(resolveUpdater(updater, pagination)),
|
|
288
|
+
}),
|
|
289
|
+
...(rowsSelectable && onRowSelectionChange && { onRowSelectionChange }),
|
|
290
|
+
...(enableColumnOrdering && onColumnOrderChange && { onColumnOrderChange }),
|
|
291
|
+
...(enableColumnResizing &&
|
|
292
|
+
onColumnSizingChange && { onColumnSizingChange }),
|
|
293
|
+
...(enableColumnPinning &&
|
|
294
|
+
onColumnPinningChange && {
|
|
295
|
+
onColumnPinningChange: handleColumnPinningChange,
|
|
296
|
+
}),
|
|
297
|
+
...(onColumnVisibilityChange && { onColumnVisibilityChange }),
|
|
298
|
+
...(enableGrouping && onGroupingChange && { onGroupingChange }),
|
|
299
|
+
...(enableGrouping && onExpandedChange && { onExpandedChange }),
|
|
300
|
+
getRowId: (row) => row.id,
|
|
301
|
+
// A page at a time: the table counts pages from what it is told - the page
|
|
302
|
+
// count, else the row count, else unknown (-1, which keeps "next" open).
|
|
303
|
+
// Every row: it pages them itself. Without pagination, nothing is paged.
|
|
304
|
+
manualPagination: !showPagination || manualPagination,
|
|
305
|
+
...(showPagination &&
|
|
306
|
+
manualPagination && {
|
|
307
|
+
pageCount: totalPages ?? (totalCount === undefined ? -1 : undefined),
|
|
308
|
+
rowCount: totalCount,
|
|
309
|
+
}),
|
|
310
|
+
// The page and the open groups are the user's: an edit, a refetch, or a
|
|
311
|
+
// new grouping must not send them back to page one or close every group.
|
|
312
|
+
autoResetPageIndex: false,
|
|
313
|
+
autoResetExpanded: false,
|
|
314
|
+
// Keep grouped columns in place so header controls stay spatially stable.
|
|
315
|
+
groupedColumnMode: false,
|
|
316
|
+
manualSorting,
|
|
317
|
+
meta,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ── Column drag-and-drop reorder (HTML5 native, no extra deps) ──────────────
|
|
321
|
+
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
|
|
322
|
+
const [dropTargetColumnId, setDropTargetColumnId] = useState<string | null>(
|
|
323
|
+
null,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const handleColumnDrop = useCallback(
|
|
327
|
+
(sourceId: string, targetId: string) => {
|
|
328
|
+
if (sourceId === targetId) return;
|
|
329
|
+
const targetCol = table.getColumn(targetId);
|
|
330
|
+
// Refuse drops onto fixed columns (e.g. the row-select column) so they
|
|
331
|
+
// stay anchored in their position.
|
|
332
|
+
if (targetCol && targetCol.getCanHide() === false) return;
|
|
333
|
+
const current =
|
|
334
|
+
columnOrder && columnOrder.length
|
|
335
|
+
? columnOrder
|
|
336
|
+
: table.getAllLeafColumns().map((c) => c.id);
|
|
337
|
+
const next = reorderColumnOrder(current, sourceId, targetId);
|
|
338
|
+
table.setColumnOrder(next);
|
|
339
|
+
},
|
|
340
|
+
[columnOrder, table],
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const handleDragEnd = useCallback(() => {
|
|
344
|
+
setDraggedColumnId(null);
|
|
345
|
+
setDropTargetColumnId(null);
|
|
346
|
+
}, []);
|
|
347
|
+
|
|
348
|
+
const [draggedRowId, setDraggedRowId] = useState<string | null>(null);
|
|
349
|
+
const [dropTargetRowId, setDropTargetRowId] = useState<string | null>(null);
|
|
350
|
+
|
|
351
|
+
const handleDragStartRow = useCallback((id: string) => {
|
|
352
|
+
setDraggedRowId(id);
|
|
353
|
+
}, []);
|
|
354
|
+
|
|
355
|
+
const handleDragOverRow = useCallback((id: string | null) => {
|
|
356
|
+
setDropTargetRowId(id);
|
|
357
|
+
}, []);
|
|
358
|
+
|
|
359
|
+
const handleDragEndRow = useCallback(() => {
|
|
360
|
+
setDraggedRowId(null);
|
|
361
|
+
setDropTargetRowId(null);
|
|
362
|
+
}, []);
|
|
363
|
+
|
|
364
|
+
const handleDropRow = useCallback(
|
|
365
|
+
(draggedId: string, targetId: string) => {
|
|
366
|
+
const draggedRow = table.getRow(draggedId);
|
|
367
|
+
const targetRow = table.getRow(targetId);
|
|
368
|
+
if (draggedRow && targetRow) {
|
|
369
|
+
onRowOrderChange?.(draggedRow.original, targetRow.original);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
[onRowOrderChange, table],
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// ── Sync header/body scrolling ──────────────────────────────────────────────
|
|
376
|
+
// The header lives in its own `overflow:hidden` container; we drive its
|
|
377
|
+
// scrollLeft from the body's scrollLeft on every scroll event so pinned
|
|
378
|
+
// columns stay perfectly aligned with their body counterparts.
|
|
379
|
+
const headerRef = useRef<HTMLDivElement>(null);
|
|
380
|
+
const bodyRef = useRef<HTMLDivElement>(null);
|
|
381
|
+
const bodyTableRef = useRef<HTMLTableElement>(null);
|
|
382
|
+
const skeletonRowRef = useRef<HTMLTableRowElement>(null);
|
|
383
|
+
const rows = table.getRowModel().rows;
|
|
384
|
+
const headerCount = table.getFlatHeaders().length;
|
|
385
|
+
const totalWidth = table.getTotalSize();
|
|
386
|
+
const {
|
|
387
|
+
columnPinning: tableColumnPinning,
|
|
388
|
+
columnSizing: tableColumnSizing,
|
|
389
|
+
columnOrder: tableColumnOrder,
|
|
390
|
+
columnVisibility: tableColumnVisibility,
|
|
391
|
+
grouping: tableGrouping,
|
|
392
|
+
} = table.state;
|
|
393
|
+
const [bodyViewportWidth, setBodyViewportWidth] = useState(0);
|
|
394
|
+
const bodyViewportWidthRef = useRef(0);
|
|
395
|
+
// Body wrap height tracked separately so the loading skeleton can render
|
|
396
|
+
// exactly the count of rows that fits the visible body. Avoids the
|
|
397
|
+
// "skeleton overflows + body wrap scrolls" feel during cold load.
|
|
398
|
+
const [bodyViewportHeight, setBodyViewportHeight] = useState(0);
|
|
399
|
+
const bodyViewportHeightRef = useRef(0);
|
|
400
|
+
const [skeletonRowHeight, setSkeletonRowHeight] = useState(0);
|
|
401
|
+
const skeletonRowHeightRef = useRef(0);
|
|
402
|
+
|
|
403
|
+
const commitBodyViewportWidth = useCallback((nextWidth: number) => {
|
|
404
|
+
if (bodyViewportWidthRef.current === nextWidth) return;
|
|
405
|
+
bodyViewportWidthRef.current = nextWidth;
|
|
406
|
+
setBodyViewportWidth(nextWidth);
|
|
407
|
+
}, []);
|
|
408
|
+
|
|
409
|
+
const commitBodyViewportHeight = useCallback((nextHeight: number) => {
|
|
410
|
+
if (bodyViewportHeightRef.current === nextHeight) return;
|
|
411
|
+
bodyViewportHeightRef.current = nextHeight;
|
|
412
|
+
setBodyViewportHeight(nextHeight);
|
|
413
|
+
}, []);
|
|
414
|
+
|
|
415
|
+
const commitSkeletonRowHeight = useCallback((nextHeight: number) => {
|
|
416
|
+
if (nextHeight <= 0 || skeletonRowHeightRef.current === nextHeight) return;
|
|
417
|
+
skeletonRowHeightRef.current = nextHeight;
|
|
418
|
+
setSkeletonRowHeight(nextHeight);
|
|
419
|
+
}, []);
|
|
420
|
+
|
|
421
|
+
const syncHeaderMetrics = useCallback(() => {
|
|
422
|
+
const body = bodyRef.current;
|
|
423
|
+
const header = headerRef.current;
|
|
424
|
+
if (!body || !header) return;
|
|
425
|
+
|
|
426
|
+
const scrollbarW = getScrollbarWidth();
|
|
427
|
+
const hasScroll = body.scrollHeight > body.clientHeight;
|
|
428
|
+
const paddingRight = hasScroll ? `${scrollbarW}px` : "0px";
|
|
429
|
+
|
|
430
|
+
header.style.paddingRight = paddingRight;
|
|
431
|
+
header.parentElement?.style.setProperty(
|
|
432
|
+
"--header-padding-right",
|
|
433
|
+
paddingRight,
|
|
434
|
+
);
|
|
435
|
+
header.parentElement?.setAttribute("data-has-scroll", hasScroll.toString());
|
|
436
|
+
// Mirror onto the body wrap so CSS can release the reserved scrollbar gutter
|
|
437
|
+
// when there's no vertical overflow (no blank strip on a few-row table).
|
|
438
|
+
body.setAttribute("data-has-scroll", hasScroll.toString());
|
|
439
|
+
|
|
440
|
+
// Re-sync horizontal scroll position after width changes so pinned
|
|
441
|
+
// columns stay perfectly aligned with their body counterparts.
|
|
442
|
+
header.scrollLeft = body.scrollLeft;
|
|
443
|
+
commitBodyViewportWidth(body.clientWidth);
|
|
444
|
+
commitBodyViewportHeight(body.clientHeight);
|
|
445
|
+
}, [commitBodyViewportWidth, commitBodyViewportHeight]);
|
|
446
|
+
|
|
447
|
+
useIsomorphicLayoutEffect(() => {
|
|
448
|
+
syncHeaderMetrics();
|
|
449
|
+
}, [
|
|
450
|
+
rows.length,
|
|
451
|
+
headerCount,
|
|
452
|
+
totalWidth,
|
|
453
|
+
tableColumnPinning,
|
|
454
|
+
tableColumnSizing,
|
|
455
|
+
tableColumnOrder,
|
|
456
|
+
tableColumnVisibility,
|
|
457
|
+
tableGrouping,
|
|
458
|
+
syncHeaderMetrics,
|
|
459
|
+
]);
|
|
460
|
+
|
|
461
|
+
useIsomorphicLayoutEffect(() => {
|
|
462
|
+
const body = bodyRef.current;
|
|
463
|
+
if (!body) return;
|
|
464
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
465
|
+
|
|
466
|
+
const observer = new ResizeObserver(syncHeaderMetrics);
|
|
467
|
+
observer.observe(body);
|
|
468
|
+
if (bodyTableRef.current) observer.observe(bodyTableRef.current);
|
|
469
|
+
|
|
470
|
+
return () => {
|
|
471
|
+
observer.disconnect();
|
|
472
|
+
};
|
|
473
|
+
}, [syncHeaderMetrics, rows.length, isLoading]);
|
|
474
|
+
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
const body = bodyRef.current;
|
|
477
|
+
const header = headerRef.current;
|
|
478
|
+
if (!body || !header) return;
|
|
479
|
+
|
|
480
|
+
const syncFromBody = () => {
|
|
481
|
+
if (header.scrollLeft !== body.scrollLeft) {
|
|
482
|
+
header.scrollLeft = body.scrollLeft;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const syncFromHeader = () => {
|
|
487
|
+
if (body.scrollLeft !== header.scrollLeft) {
|
|
488
|
+
body.scrollLeft = header.scrollLeft;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
body.addEventListener("scroll", syncFromBody, { passive: true });
|
|
493
|
+
header.addEventListener("scroll", syncFromHeader, { passive: true });
|
|
494
|
+
|
|
495
|
+
return () => {
|
|
496
|
+
body.removeEventListener("scroll", syncFromBody);
|
|
497
|
+
header.removeEventListener("scroll", syncFromHeader);
|
|
498
|
+
};
|
|
499
|
+
}, []);
|
|
500
|
+
|
|
501
|
+
// ── Derived UI state ────────────────────────────────────────────────────────
|
|
502
|
+
// Defer the empty state by a tick after a loading-to-idle transition so the
|
|
503
|
+
// loader doesn't flicker into "no results" for one paint before rows land.
|
|
504
|
+
// Genuinely empty results (no in-flight load) commit immediately on mount.
|
|
505
|
+
const isRowsEmpty = rows.length === 0;
|
|
506
|
+
const [showEmptyState, setShowEmptyState] = useState(
|
|
507
|
+
() => !isLoading && isRowsEmpty,
|
|
508
|
+
);
|
|
509
|
+
const wasLoadingRef = useRef(isLoading);
|
|
510
|
+
|
|
511
|
+
// wasLoadingRef is intentionally omitted from the dep array: it's a ref used
|
|
512
|
+
// to track the previous value of isLoading across renders without triggering
|
|
513
|
+
// re-runs. React guarantees ref objects are stable across the component lifetime.
|
|
514
|
+
useEffect(() => {
|
|
515
|
+
const wasLoading = wasLoadingRef.current;
|
|
516
|
+
wasLoadingRef.current = isLoading;
|
|
517
|
+
|
|
518
|
+
if (isLoading || !isRowsEmpty) {
|
|
519
|
+
setShowEmptyState(false);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (!wasLoading) {
|
|
523
|
+
setShowEmptyState(true);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const id = window.setTimeout(
|
|
527
|
+
() => setShowEmptyState(true),
|
|
528
|
+
EMPTY_STATE_GRACE_MS,
|
|
529
|
+
);
|
|
530
|
+
return () => window.clearTimeout(id);
|
|
531
|
+
}, [isLoading, isRowsEmpty]);
|
|
532
|
+
|
|
533
|
+
// Single tab-like discriminator drives the body - loader, empty, or content.
|
|
534
|
+
// While loading OR while bridging the loading-to-empty grace window, the
|
|
535
|
+
// loader stays visible to avoid the brief "no results" flash.
|
|
536
|
+
const view: "loader" | "empty" | "content" =
|
|
537
|
+
isLoading || (isRowsEmpty && !showEmptyState)
|
|
538
|
+
? "loader"
|
|
539
|
+
: isRowsEmpty
|
|
540
|
+
? "empty"
|
|
541
|
+
: "content";
|
|
542
|
+
|
|
543
|
+
useIsomorphicLayoutEffect(() => {
|
|
544
|
+
if (view !== "loader") return;
|
|
545
|
+
const row = skeletonRowRef.current;
|
|
546
|
+
if (!row) return;
|
|
547
|
+
|
|
548
|
+
const measureRow = () => {
|
|
549
|
+
commitSkeletonRowHeight(Math.ceil(row.getBoundingClientRect().height));
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
measureRow();
|
|
553
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
554
|
+
|
|
555
|
+
const observer = new ResizeObserver(measureRow);
|
|
556
|
+
observer.observe(row);
|
|
557
|
+
|
|
558
|
+
return () => {
|
|
559
|
+
observer.disconnect();
|
|
560
|
+
};
|
|
561
|
+
}, [commitSkeletonRowHeight, view]);
|
|
562
|
+
|
|
563
|
+
const selectedCount = rowsSelectable
|
|
564
|
+
? table.getSelectedRowModel().flatRows.length
|
|
565
|
+
: 0;
|
|
566
|
+
const headerGroups = table.getHeaderGroups();
|
|
567
|
+
const visibleLeafColumns = table.getVisibleLeafColumns();
|
|
568
|
+
// The rows of footers columns define, e.g. a total from
|
|
569
|
+
// `column.getAggregationValue()`; none when no column has a footer.
|
|
570
|
+
const footerGroups = table
|
|
571
|
+
.getFooterGroups()
|
|
572
|
+
.filter((group) =>
|
|
573
|
+
group.headers.some(
|
|
574
|
+
(header) =>
|
|
575
|
+
!header.isPlaceholder && header.column.columnDef.footer !== undefined,
|
|
576
|
+
),
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
const skeletonRowCount =
|
|
580
|
+
bodyViewportHeight > 0 && skeletonRowHeight > 0
|
|
581
|
+
? Math.max(
|
|
582
|
+
MIN_SKELETON_ROWS,
|
|
583
|
+
Math.floor(bodyViewportHeight / skeletonRowHeight),
|
|
584
|
+
)
|
|
585
|
+
: MIN_SKELETON_ROWS;
|
|
586
|
+
|
|
587
|
+
const columnExtraWidths = growColumnWidths(
|
|
588
|
+
visibleLeafColumns,
|
|
589
|
+
bodyViewportWidth - totalWidth,
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
const tableWidth = Math.max(totalWidth, bodyViewportWidth);
|
|
593
|
+
|
|
594
|
+
// Columns that fit the body never scroll it sideways. The body's width is
|
|
595
|
+
// read from clientWidth, which rounds to the nearest pixel, so on a
|
|
596
|
+
// fractional layout (page zoom, display scaling) the table can overhang the
|
|
597
|
+
// body by under a pixel. Left to `overflow-x: auto`, that sliver shows a
|
|
598
|
+
// horizontal scrollbar, which shortens the body, flips its scrollbar gutter
|
|
599
|
+
// and starts another resize.
|
|
600
|
+
const columnsFit = bodyViewportWidth > 0 && totalWidth <= bodyViewportWidth;
|
|
601
|
+
|
|
602
|
+
// Match the table element width to the measured column widths. This keeps
|
|
603
|
+
// header, body, and skeleton rows on the same sizing contract during resize.
|
|
604
|
+
const tableSizingStyle: CSSProperties = {
|
|
605
|
+
width: `${tableWidth}px`,
|
|
606
|
+
minWidth: `${totalWidth}px`,
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const bodySkeletonStyle: CSSProperties = {
|
|
610
|
+
height: "var(--spacing-9)",
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const selectSkeletonStyle: CSSProperties = {
|
|
614
|
+
width: "var(--data-table-selection-control-size, var(--spacing-9))",
|
|
615
|
+
height: "var(--data-table-selection-control-size, var(--spacing-9))",
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const tableClass = clsx(
|
|
619
|
+
s.table,
|
|
620
|
+
showVerticalBorders && s.table_vertical_borders,
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<div className={clsx(s.root, className)}>
|
|
625
|
+
{/* Filters - inline row on desktop; a "Filters" button + modal once the
|
|
626
|
+
row no longer fits (so phones aren't buried under stacked fields). */}
|
|
627
|
+
{filters.length > 0 && (
|
|
628
|
+
<Filters
|
|
629
|
+
fields={filters}
|
|
630
|
+
values={filterValues}
|
|
631
|
+
onChange={onFilterChange}
|
|
632
|
+
/>
|
|
633
|
+
)}
|
|
634
|
+
|
|
635
|
+
{/* Content wrap - grows to fill remaining height.
|
|
636
|
+
table_shell fills it; pagination pins to the bottom via margin-top:auto */}
|
|
637
|
+
<div className={s.content_wrap}>
|
|
638
|
+
{/* Table: detached header pill + scrollable body */}
|
|
639
|
+
<div
|
|
640
|
+
className={clsx(
|
|
641
|
+
s.table_shell,
|
|
642
|
+
!showPagination && s.table_shell_no_pagination,
|
|
643
|
+
)}
|
|
644
|
+
>
|
|
645
|
+
{/* Pinned header pill - scroll synced to body via JS */}
|
|
646
|
+
<div className={s.table_header_container}>
|
|
647
|
+
<div className={s.table_header_wrap} ref={headerRef}>
|
|
648
|
+
<table className={tableClass} style={tableSizingStyle}>
|
|
649
|
+
<thead>
|
|
650
|
+
{headerGroups.map((hg) => (
|
|
651
|
+
<tr key={hg.id}>
|
|
652
|
+
{hg.headers.map((header) => (
|
|
653
|
+
<HeaderCell<TData>
|
|
654
|
+
key={header.id}
|
|
655
|
+
header={header}
|
|
656
|
+
extraWidth={columnExtraWidths[header.column.id] ?? 0}
|
|
657
|
+
enableColumnOrdering={enableColumnOrdering}
|
|
658
|
+
enableColumnResizing={enableColumnResizing}
|
|
659
|
+
enableColumnPinning={enableColumnPinning}
|
|
660
|
+
enableGrouping={enableGrouping}
|
|
661
|
+
draggedColumnId={draggedColumnId}
|
|
662
|
+
dropTargetColumnId={dropTargetColumnId}
|
|
663
|
+
onDragStartCol={setDraggedColumnId}
|
|
664
|
+
onDragEndCol={handleDragEnd}
|
|
665
|
+
onDragOverCol={setDropTargetColumnId}
|
|
666
|
+
onDropCol={handleColumnDrop}
|
|
667
|
+
/>
|
|
668
|
+
))}
|
|
669
|
+
</tr>
|
|
670
|
+
))}
|
|
671
|
+
</thead>
|
|
672
|
+
</table>
|
|
673
|
+
</div>
|
|
674
|
+
<div className={s.header_padding_mask} />
|
|
675
|
+
</div>
|
|
676
|
+
|
|
677
|
+
{/* Scrollable body - only this element scrolls. The body is a
|
|
678
|
+
single switch over `view`: at most one branch ever renders, the
|
|
679
|
+
other two are unmounted entirely. */}
|
|
680
|
+
<div
|
|
681
|
+
className={s.table_body_wrap}
|
|
682
|
+
style={{ pointerEvents: isLoading ? "none" : undefined }}
|
|
683
|
+
data-fits={columnsFit}
|
|
684
|
+
ref={bodyRef}
|
|
685
|
+
>
|
|
686
|
+
{view === "loader" && (
|
|
687
|
+
<table className={tableClass} style={tableSizingStyle}>
|
|
688
|
+
<tbody>
|
|
689
|
+
{Array.from({ length: skeletonRowCount }).map(
|
|
690
|
+
(_, rowIndex) => (
|
|
691
|
+
<tr
|
|
692
|
+
key={`skeleton-${rowIndex}`}
|
|
693
|
+
ref={rowIndex === 0 ? skeletonRowRef : undefined}
|
|
694
|
+
>
|
|
695
|
+
{visibleLeafColumns.map((column) => {
|
|
696
|
+
const isPinned = column.getIsPinned();
|
|
697
|
+
const isLastPinnedCol =
|
|
698
|
+
isPinned && isLastPinned(column);
|
|
699
|
+
const isSelectColumn = column.id === SELECT_COLUMN_ID;
|
|
700
|
+
|
|
701
|
+
return (
|
|
702
|
+
<td
|
|
703
|
+
key={column.id}
|
|
704
|
+
className={clsx(
|
|
705
|
+
isSelectColumn && s.col_select,
|
|
706
|
+
isPinned === "start" && s.col_pinned_start,
|
|
707
|
+
isPinned === "end" && s.col_pinned_end,
|
|
708
|
+
isLastPinnedCol && s.col_pinned_last,
|
|
709
|
+
)}
|
|
710
|
+
style={getColumnStyle(
|
|
711
|
+
column,
|
|
712
|
+
column.getSize(),
|
|
713
|
+
columnExtraWidths[column.id] ?? 0,
|
|
714
|
+
)}
|
|
715
|
+
>
|
|
716
|
+
<div className={s.td_inner}>
|
|
717
|
+
<Loader
|
|
718
|
+
variant="skeleton"
|
|
719
|
+
className={
|
|
720
|
+
isSelectColumn
|
|
721
|
+
? s.loading_select_skeleton
|
|
722
|
+
: undefined
|
|
723
|
+
}
|
|
724
|
+
style={
|
|
725
|
+
isSelectColumn
|
|
726
|
+
? selectSkeletonStyle
|
|
727
|
+
: bodySkeletonStyle
|
|
728
|
+
}
|
|
729
|
+
/>
|
|
730
|
+
</div>
|
|
731
|
+
</td>
|
|
732
|
+
);
|
|
733
|
+
})}
|
|
734
|
+
</tr>
|
|
735
|
+
),
|
|
736
|
+
)}
|
|
737
|
+
</tbody>
|
|
738
|
+
</table>
|
|
739
|
+
)}
|
|
740
|
+
{view === "empty" && (
|
|
741
|
+
<div className={s.state_overlay}>
|
|
742
|
+
<p className={s.empty_msg}>{emptyMessage}</p>
|
|
743
|
+
</div>
|
|
744
|
+
)}
|
|
745
|
+
{view === "content" && (
|
|
746
|
+
<table
|
|
747
|
+
className={tableClass}
|
|
748
|
+
style={tableSizingStyle}
|
|
749
|
+
ref={bodyTableRef}
|
|
750
|
+
>
|
|
751
|
+
<tbody>
|
|
752
|
+
{rows.map((row) => (
|
|
753
|
+
<BodyRow<TData>
|
|
754
|
+
key={row.id}
|
|
755
|
+
row={row}
|
|
756
|
+
editingRowId={editingRowId}
|
|
757
|
+
onRow={onRow}
|
|
758
|
+
enableGrouping={enableGrouping}
|
|
759
|
+
columnExtraWidths={columnExtraWidths}
|
|
760
|
+
enableRowOrdering={enableRowOrdering}
|
|
761
|
+
draggedRowId={draggedRowId}
|
|
762
|
+
dropTargetRowId={dropTargetRowId}
|
|
763
|
+
onDragStartRow={handleDragStartRow}
|
|
764
|
+
onDragOverRow={handleDragOverRow}
|
|
765
|
+
onDragEndRow={handleDragEndRow}
|
|
766
|
+
onDropRow={handleDropRow}
|
|
767
|
+
/>
|
|
768
|
+
))}
|
|
769
|
+
</tbody>
|
|
770
|
+
{footerGroups.length > 0 && (
|
|
771
|
+
<tfoot>
|
|
772
|
+
{footerGroups.map((group) => (
|
|
773
|
+
<tr key={group.id}>
|
|
774
|
+
{group.headers.map((header) => (
|
|
775
|
+
<FooterCell<TData>
|
|
776
|
+
key={header.id}
|
|
777
|
+
header={header}
|
|
778
|
+
extraWidth={
|
|
779
|
+
columnExtraWidths[header.column.id] ?? 0
|
|
780
|
+
}
|
|
781
|
+
/>
|
|
782
|
+
))}
|
|
783
|
+
</tr>
|
|
784
|
+
))}
|
|
785
|
+
</tfoot>
|
|
786
|
+
)}
|
|
787
|
+
</table>
|
|
788
|
+
)}
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
|
|
792
|
+
{/* Pagination - margin-top:auto pins it to the bottom of content_wrap */}
|
|
793
|
+
{showPagination && (
|
|
794
|
+
<PaginationBar
|
|
795
|
+
table={table}
|
|
796
|
+
pagination={pagination}
|
|
797
|
+
onPaginationChange={onPaginationChange}
|
|
798
|
+
pageSizeOptions={pageSizeOptions}
|
|
799
|
+
showPageSizeSelect={showPageSizeSelect}
|
|
800
|
+
isLoading={view === "loader"}
|
|
801
|
+
totalCount={totalCount}
|
|
802
|
+
rowLabel={rowLabel}
|
|
803
|
+
rowsSelectable={rowsSelectable}
|
|
804
|
+
selectedCount={selectedCount}
|
|
805
|
+
label={paginationLabel}
|
|
806
|
+
extra={paginationExtra}
|
|
807
|
+
/>
|
|
808
|
+
)}
|
|
809
|
+
</div>
|
|
810
|
+
</div>
|
|
811
|
+
);
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
815
|
+
// HeaderActionButton - local compact icon button for the table header.
|
|
816
|
+
// Keeps the header tight (no library Button padding) without depending on the
|
|
817
|
+
// Button component while still mirroring its hover/focus visuals.
|
|
818
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
819
|
+
|
|
820
|
+
const HeaderActionButton = ({
|
|
821
|
+
onClick,
|
|
822
|
+
ariaLabel,
|
|
823
|
+
ariaPressed,
|
|
824
|
+
active = false,
|
|
825
|
+
children,
|
|
826
|
+
}: HeaderActionButtonProps) => {
|
|
827
|
+
return (
|
|
828
|
+
<Button
|
|
829
|
+
type="button"
|
|
830
|
+
variant="ghost"
|
|
831
|
+
size="sm"
|
|
832
|
+
layout="box"
|
|
833
|
+
className={s.icon_btn}
|
|
834
|
+
onClick={onClick}
|
|
835
|
+
aria-label={ariaLabel}
|
|
836
|
+
aria-pressed={ariaPressed}
|
|
837
|
+
draggable={false}
|
|
838
|
+
isActive={active}
|
|
839
|
+
>
|
|
840
|
+
{children}
|
|
841
|
+
</Button>
|
|
842
|
+
);
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
846
|
+
// HeaderCell - one <th> with sort, group, pin, drag, and resize affordances.
|
|
847
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
848
|
+
|
|
849
|
+
const HeaderCell = <TData extends RowData>({
|
|
850
|
+
header,
|
|
851
|
+
extraWidth,
|
|
852
|
+
enableColumnOrdering,
|
|
853
|
+
enableColumnResizing,
|
|
854
|
+
enableColumnPinning,
|
|
855
|
+
enableGrouping,
|
|
856
|
+
draggedColumnId,
|
|
857
|
+
dropTargetColumnId,
|
|
858
|
+
onDragStartCol,
|
|
859
|
+
onDragEndCol,
|
|
860
|
+
onDragOverCol,
|
|
861
|
+
onDropCol,
|
|
862
|
+
}: HeaderCellProps<TData>) => {
|
|
863
|
+
const column = header.column;
|
|
864
|
+
const colMeta = column.columnDef.meta;
|
|
865
|
+
const align = colMeta?.align ?? "left";
|
|
866
|
+
// Fixed columns (e.g. row-select) opt out of hide-ability AND of being a
|
|
867
|
+
// drag source / drop target so they stay anchored in their position.
|
|
868
|
+
const isFixed = column.getCanHide() === false;
|
|
869
|
+
const canDrag = enableColumnOrdering && !isFixed;
|
|
870
|
+
const canResize = enableColumnResizing && column.getCanResize();
|
|
871
|
+
const canPin = enableColumnPinning && column.getCanPin() && !isFixed;
|
|
872
|
+
const canGroup = enableGrouping && column.getCanGroup() && !isFixed;
|
|
873
|
+
const canSort = column.getCanSort();
|
|
874
|
+
const isPinned = column.getIsPinned();
|
|
875
|
+
const isLastPinnedCol = isPinned && isLastPinned(column);
|
|
876
|
+
const isBeingDragged = draggedColumnId === column.id;
|
|
877
|
+
const isDropTarget =
|
|
878
|
+
dropTargetColumnId === column.id &&
|
|
879
|
+
draggedColumnId !== column.id &&
|
|
880
|
+
!isFixed;
|
|
881
|
+
const isGrouped = enableGrouping && column.getIsGrouped();
|
|
882
|
+
const isSelectColumn = column.id === SELECT_COLUMN_ID;
|
|
883
|
+
const sortState = column.getIsSorted();
|
|
884
|
+
const hasHeaderActions = canSort || canGroup || canPin;
|
|
885
|
+
const sortTooltip =
|
|
886
|
+
sortState === "asc"
|
|
887
|
+
? "Sort descending"
|
|
888
|
+
: sortState === "desc"
|
|
889
|
+
? "Clear sorting"
|
|
890
|
+
: "Sort ascending";
|
|
891
|
+
|
|
892
|
+
const handleDragStart = (e: ReactDragEvent<HTMLTableCellElement>) => {
|
|
893
|
+
e.dataTransfer.effectAllowed = "move";
|
|
894
|
+
e.dataTransfer.setData("text/plain", column.id);
|
|
895
|
+
onDragStartCol(column.id);
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
const handleDragOver = (e: ReactDragEvent<HTMLTableCellElement>) => {
|
|
899
|
+
if (!draggedColumnId || draggedColumnId === column.id || isFixed) return;
|
|
900
|
+
e.preventDefault();
|
|
901
|
+
e.dataTransfer.dropEffect = "move";
|
|
902
|
+
if (dropTargetColumnId !== column.id) onDragOverCol(column.id);
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
const handleDragLeave = () => {
|
|
906
|
+
if (dropTargetColumnId === column.id) onDragOverCol(null);
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
const handleDrop = (e: ReactDragEvent<HTMLTableCellElement>) => {
|
|
910
|
+
e.preventDefault();
|
|
911
|
+
if (!draggedColumnId || isFixed) return;
|
|
912
|
+
onDropCol(draggedColumnId, column.id);
|
|
913
|
+
onDragEndCol();
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
const sortIcon = !sortState ? (
|
|
917
|
+
<IconArrowsUpDown size={SORT_ICON_SIZE} stroke={ICON_STROKE} />
|
|
918
|
+
) : sortState === "asc" ? (
|
|
919
|
+
<IconArrowUp size={SORT_ICON_SIZE} stroke={ICON_STROKE} />
|
|
920
|
+
) : (
|
|
921
|
+
<IconArrowDown size={SORT_ICON_SIZE} stroke={ICON_STROKE} />
|
|
922
|
+
);
|
|
923
|
+
|
|
924
|
+
return (
|
|
925
|
+
<th
|
|
926
|
+
colSpan={header.colSpan}
|
|
927
|
+
onDragOver={canDrag ? handleDragOver : undefined}
|
|
928
|
+
onDragLeave={canDrag ? handleDragLeave : undefined}
|
|
929
|
+
onDrop={canDrag ? handleDrop : undefined}
|
|
930
|
+
className={clsx(
|
|
931
|
+
colMeta?.headerClassName,
|
|
932
|
+
s[`align_${align}`],
|
|
933
|
+
isSelectColumn && s.col_select,
|
|
934
|
+
isPinned === "start" && s.col_pinned_start,
|
|
935
|
+
isPinned === "end" && s.col_pinned_end,
|
|
936
|
+
isLastPinnedCol && s.col_pinned_last,
|
|
937
|
+
isBeingDragged && s.col_dragging,
|
|
938
|
+
isDropTarget && s.col_drop_target,
|
|
939
|
+
)}
|
|
940
|
+
style={getColumnStyle(column, header.getSize(), extraWidth)}
|
|
941
|
+
>
|
|
942
|
+
{!header.isPlaceholder && (
|
|
943
|
+
<div className={s.th_inner}>
|
|
944
|
+
{canDrag && (
|
|
945
|
+
<span
|
|
946
|
+
className={s.drag_handle}
|
|
947
|
+
aria-hidden
|
|
948
|
+
draggable
|
|
949
|
+
onDragStart={handleDragStart}
|
|
950
|
+
onDragEnd={onDragEndCol}
|
|
951
|
+
>
|
|
952
|
+
<IconGripVertical size={ICON_SIZE} stroke={ICON_STROKE} />
|
|
953
|
+
</span>
|
|
954
|
+
)}
|
|
955
|
+
{isSelectColumn ? (
|
|
956
|
+
<FlexRender header={header} />
|
|
957
|
+
) : (
|
|
958
|
+
<span className={s.th_label}>
|
|
959
|
+
<FlexRender header={header} />
|
|
960
|
+
</span>
|
|
961
|
+
)}
|
|
962
|
+
{hasHeaderActions && (
|
|
963
|
+
<span className={s.th_actions}>
|
|
964
|
+
{canSort && (
|
|
965
|
+
<Tooltip content={sortTooltip}>
|
|
966
|
+
<HeaderActionButton
|
|
967
|
+
onClick={column.getToggleSortingHandler()}
|
|
968
|
+
ariaLabel={`Sort by ${column.id}`}
|
|
969
|
+
ariaPressed={!!sortState}
|
|
970
|
+
active={!!sortState}
|
|
971
|
+
>
|
|
972
|
+
{sortIcon}
|
|
973
|
+
</HeaderActionButton>
|
|
974
|
+
</Tooltip>
|
|
975
|
+
)}
|
|
976
|
+
{canGroup && (
|
|
977
|
+
<Tooltip
|
|
978
|
+
content={isGrouped ? "Ungroup column" : "Group by column"}
|
|
979
|
+
>
|
|
980
|
+
<HeaderActionButton
|
|
981
|
+
onClick={column.getToggleGroupingHandler()}
|
|
982
|
+
ariaLabel={
|
|
983
|
+
isGrouped ? "Ungroup column" : "Group by this column"
|
|
984
|
+
}
|
|
985
|
+
ariaPressed={isGrouped}
|
|
986
|
+
active={isGrouped}
|
|
987
|
+
>
|
|
988
|
+
{isGrouped ? (
|
|
989
|
+
<IconStack3 size={ICON_SIZE} stroke={ICON_STROKE} />
|
|
990
|
+
) : (
|
|
991
|
+
<IconStack2 size={ICON_SIZE} stroke={ICON_STROKE} />
|
|
992
|
+
)}
|
|
993
|
+
</HeaderActionButton>
|
|
994
|
+
</Tooltip>
|
|
995
|
+
)}
|
|
996
|
+
{canPin && !isPinned && (
|
|
997
|
+
<>
|
|
998
|
+
<Tooltip content="Pin left">
|
|
999
|
+
<HeaderActionButton
|
|
1000
|
+
onClick={() => column.pin("start")}
|
|
1001
|
+
ariaLabel="Pin left"
|
|
1002
|
+
>
|
|
1003
|
+
<IconArrowLeft size={ICON_SIZE} stroke={ICON_STROKE} />
|
|
1004
|
+
</HeaderActionButton>
|
|
1005
|
+
</Tooltip>
|
|
1006
|
+
<Tooltip content="Pin right">
|
|
1007
|
+
<HeaderActionButton
|
|
1008
|
+
onClick={() => column.pin("end")}
|
|
1009
|
+
ariaLabel="Pin right"
|
|
1010
|
+
>
|
|
1011
|
+
<IconArrowRight size={ICON_SIZE} stroke={ICON_STROKE} />
|
|
1012
|
+
</HeaderActionButton>
|
|
1013
|
+
</Tooltip>
|
|
1014
|
+
</>
|
|
1015
|
+
)}
|
|
1016
|
+
{canPin && isPinned && (
|
|
1017
|
+
<Tooltip content="Unpin column">
|
|
1018
|
+
<HeaderActionButton
|
|
1019
|
+
onClick={() => column.pin(false)}
|
|
1020
|
+
ariaLabel="Unpin column"
|
|
1021
|
+
ariaPressed
|
|
1022
|
+
active
|
|
1023
|
+
>
|
|
1024
|
+
<IconPinnedOff size={ICON_SIZE} stroke={ICON_STROKE} />
|
|
1025
|
+
</HeaderActionButton>
|
|
1026
|
+
</Tooltip>
|
|
1027
|
+
)}
|
|
1028
|
+
</span>
|
|
1029
|
+
)}
|
|
1030
|
+
{canResize && (
|
|
1031
|
+
<span
|
|
1032
|
+
role="separator"
|
|
1033
|
+
aria-orientation="vertical"
|
|
1034
|
+
className={clsx(
|
|
1035
|
+
s.resize_handle,
|
|
1036
|
+
column.getIsResizing() && s.resize_handle_active,
|
|
1037
|
+
)}
|
|
1038
|
+
onMouseDown={header.getResizeHandler()}
|
|
1039
|
+
onTouchStart={header.getResizeHandler()}
|
|
1040
|
+
onClick={(e) => e.stopPropagation()}
|
|
1041
|
+
onDoubleClick={() => column.resetSize()}
|
|
1042
|
+
draggable={false}
|
|
1043
|
+
/>
|
|
1044
|
+
)}
|
|
1045
|
+
</div>
|
|
1046
|
+
)}
|
|
1047
|
+
</th>
|
|
1048
|
+
);
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1052
|
+
// FooterCell - one footer <td>, sized and pinned like the column's body cells.
|
|
1053
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1054
|
+
|
|
1055
|
+
const FooterCell = <TData extends RowData>({
|
|
1056
|
+
header,
|
|
1057
|
+
extraWidth,
|
|
1058
|
+
}: FooterCellProps<TData>) => {
|
|
1059
|
+
const column = header.column;
|
|
1060
|
+
const isPinned = column.getIsPinned();
|
|
1061
|
+
return (
|
|
1062
|
+
<td
|
|
1063
|
+
colSpan={header.colSpan}
|
|
1064
|
+
className={clsx(
|
|
1065
|
+
s[`align_${column.columnDef.meta?.align ?? "left"}`],
|
|
1066
|
+
column.id === SELECT_COLUMN_ID && s.col_select,
|
|
1067
|
+
isPinned === "start" && s.col_pinned_start,
|
|
1068
|
+
isPinned === "end" && s.col_pinned_end,
|
|
1069
|
+
isPinned && isLastPinned(column) && s.col_pinned_last,
|
|
1070
|
+
)}
|
|
1071
|
+
style={getColumnStyle(column, header.getSize(), extraWidth)}
|
|
1072
|
+
>
|
|
1073
|
+
<div className={s.td_inner}>
|
|
1074
|
+
{header.isPlaceholder ? null : <FlexRender footer={header} />}
|
|
1075
|
+
</div>
|
|
1076
|
+
</td>
|
|
1077
|
+
);
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1081
|
+
// BodyRow - one <tr> + its visible cells.
|
|
1082
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1083
|
+
|
|
1084
|
+
const BodyRow = <TData extends { id: string }>({
|
|
1085
|
+
row,
|
|
1086
|
+
editingRowId,
|
|
1087
|
+
onRow,
|
|
1088
|
+
enableGrouping,
|
|
1089
|
+
columnExtraWidths,
|
|
1090
|
+
enableRowOrdering,
|
|
1091
|
+
draggedRowId,
|
|
1092
|
+
dropTargetRowId,
|
|
1093
|
+
onDragStartRow,
|
|
1094
|
+
onDragOverRow,
|
|
1095
|
+
onDragEndRow,
|
|
1096
|
+
onDropRow,
|
|
1097
|
+
}: BodyRowProps<TData>) => {
|
|
1098
|
+
const isGroup = enableGrouping && row.getIsGrouped();
|
|
1099
|
+
const visibleCells = row.getVisibleCells();
|
|
1100
|
+
const groupSourceCell = isGroup
|
|
1101
|
+
? visibleCells.find((cell) => cell.getIsGrouped())
|
|
1102
|
+
: undefined;
|
|
1103
|
+
const groupAnchorCellId = isGroup
|
|
1104
|
+
? (visibleCells.find((cell) => cell.column.id !== SELECT_COLUMN_ID)?.id ??
|
|
1105
|
+
groupSourceCell?.id ??
|
|
1106
|
+
null)
|
|
1107
|
+
: null;
|
|
1108
|
+
const isEditingThisRow =
|
|
1109
|
+
!isGroup && editingRowId !== null && row.original.id === editingRowId;
|
|
1110
|
+
|
|
1111
|
+
// Editing rows host inputs/selects; their clicks must not bubble to row click.
|
|
1112
|
+
// Only a click on the row itself opens it: not one on a control inside it
|
|
1113
|
+
// (a link, a copy button), nor one that reaches it through a portal (a row's
|
|
1114
|
+
// action menu), which React bubbles here from outside the row.
|
|
1115
|
+
const handleRowClick =
|
|
1116
|
+
!isGroup && !isEditingThisRow && onRow
|
|
1117
|
+
? (event: ReactMouseEvent<HTMLTableRowElement>) => {
|
|
1118
|
+
const target = event.target as Element;
|
|
1119
|
+
if (!event.currentTarget.contains(target)) return;
|
|
1120
|
+
const control = target.closest(ROW_CONTROLS);
|
|
1121
|
+
if (control && event.currentTarget.contains(control)) return;
|
|
1122
|
+
onRow(row.original);
|
|
1123
|
+
}
|
|
1124
|
+
: undefined;
|
|
1125
|
+
|
|
1126
|
+
const canDrag = enableRowOrdering && !isGroup && !isEditingThisRow;
|
|
1127
|
+
const isBeingDragged = draggedRowId === row.id;
|
|
1128
|
+
const isDropTarget =
|
|
1129
|
+
dropTargetRowId === row.id && draggedRowId !== row.id && !isGroup;
|
|
1130
|
+
|
|
1131
|
+
return (
|
|
1132
|
+
<tr
|
|
1133
|
+
draggable={canDrag}
|
|
1134
|
+
onDragStart={(e) => {
|
|
1135
|
+
if (!canDrag) return;
|
|
1136
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1137
|
+
e.dataTransfer.setData("text/plain", row.id);
|
|
1138
|
+
onDragStartRow?.(row.id);
|
|
1139
|
+
}}
|
|
1140
|
+
onDragOver={(e) => {
|
|
1141
|
+
if (!canDrag || !draggedRowId || draggedRowId === row.id) return;
|
|
1142
|
+
e.preventDefault();
|
|
1143
|
+
e.dataTransfer.dropEffect = "move";
|
|
1144
|
+
if (dropTargetRowId !== row.id) onDragOverRow?.(row.id);
|
|
1145
|
+
}}
|
|
1146
|
+
onDragLeave={() => {
|
|
1147
|
+
if (!canDrag) return;
|
|
1148
|
+
if (dropTargetRowId === row.id) onDragOverRow?.(null);
|
|
1149
|
+
}}
|
|
1150
|
+
onDragEnd={() => {
|
|
1151
|
+
if (!canDrag) return;
|
|
1152
|
+
onDragEndRow?.();
|
|
1153
|
+
}}
|
|
1154
|
+
onDrop={(e) => {
|
|
1155
|
+
if (!canDrag) return;
|
|
1156
|
+
e.preventDefault();
|
|
1157
|
+
if (!draggedRowId || draggedRowId === row.id) return;
|
|
1158
|
+
onDropRow?.(draggedRowId, row.id);
|
|
1159
|
+
onDragEndRow?.();
|
|
1160
|
+
}}
|
|
1161
|
+
className={clsx(
|
|
1162
|
+
row.getIsSelected() && s.row_selected,
|
|
1163
|
+
isEditingThisRow && s.row_editing,
|
|
1164
|
+
isGroup && s.row_group,
|
|
1165
|
+
isBeingDragged && s.row_dragging,
|
|
1166
|
+
isDropTarget && s.row_drop_target,
|
|
1167
|
+
)}
|
|
1168
|
+
onClick={handleRowClick}
|
|
1169
|
+
style={handleRowClick ? POINTER_STYLE : undefined}
|
|
1170
|
+
>
|
|
1171
|
+
{visibleCells.map((cell) => (
|
|
1172
|
+
<BodyCell<TData>
|
|
1173
|
+
key={cell.id}
|
|
1174
|
+
cell={cell}
|
|
1175
|
+
extraWidth={columnExtraWidths[cell.column.id] ?? 0}
|
|
1176
|
+
groupSourceCell={groupSourceCell}
|
|
1177
|
+
isGroupAnchorCell={groupAnchorCellId === cell.id}
|
|
1178
|
+
/>
|
|
1179
|
+
))}
|
|
1180
|
+
</tr>
|
|
1181
|
+
);
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1185
|
+
// BodyCell - one <td>. The group row's anchor cell hosts the expand toggle and
|
|
1186
|
+
// the grouped value; every other cell renders through `FlexRender`, which
|
|
1187
|
+
// picks the aggregated, placeholder, or normal body. Applies meta-driven
|
|
1188
|
+
// align + truncate + pin classes.
|
|
1189
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1190
|
+
|
|
1191
|
+
const BodyCell = <TData extends RowData>({
|
|
1192
|
+
cell,
|
|
1193
|
+
extraWidth,
|
|
1194
|
+
groupSourceCell,
|
|
1195
|
+
isGroupAnchorCell,
|
|
1196
|
+
}: BodyCellProps<TData>) => {
|
|
1197
|
+
const column = cell.column;
|
|
1198
|
+
const colMeta = column.columnDef.meta;
|
|
1199
|
+
const align = colMeta?.align ?? "left";
|
|
1200
|
+
const truncate = colMeta?.truncate ?? true;
|
|
1201
|
+
const isPinned = column.getIsPinned();
|
|
1202
|
+
const isLastPinnedCol = isPinned && isLastPinned(column);
|
|
1203
|
+
const isSelectColumn = column.id === SELECT_COLUMN_ID;
|
|
1204
|
+
const groupedRenderCell =
|
|
1205
|
+
groupSourceCell ?? (cell.getIsGrouped() ? cell : undefined);
|
|
1206
|
+
const groupDepth = groupedRenderCell?.row.depth ?? 0;
|
|
1207
|
+
|
|
1208
|
+
let body: ReactNode;
|
|
1209
|
+
if (groupedRenderCell && isGroupAnchorCell) {
|
|
1210
|
+
body = (
|
|
1211
|
+
<button
|
|
1212
|
+
type="button"
|
|
1213
|
+
className={s.group_toggle}
|
|
1214
|
+
onClick={groupedRenderCell.row.getToggleExpandedHandler()}
|
|
1215
|
+
aria-expanded={groupedRenderCell.row.getIsExpanded()}
|
|
1216
|
+
aria-label={
|
|
1217
|
+
groupedRenderCell.row.getIsExpanded()
|
|
1218
|
+
? "Collapse group"
|
|
1219
|
+
: "Expand group"
|
|
1220
|
+
}
|
|
1221
|
+
style={
|
|
1222
|
+
groupDepth > 0
|
|
1223
|
+
? { paddingInlineStart: `calc(${groupDepth} * var(--spacing-4))` }
|
|
1224
|
+
: undefined
|
|
1225
|
+
}
|
|
1226
|
+
>
|
|
1227
|
+
<IconChevronRight
|
|
1228
|
+
size={ICON_SIZE}
|
|
1229
|
+
stroke={ICON_STROKE}
|
|
1230
|
+
className={clsx(
|
|
1231
|
+
s.group_chevron,
|
|
1232
|
+
groupedRenderCell.row.getIsExpanded() && s.group_chevron_open,
|
|
1233
|
+
)}
|
|
1234
|
+
/>
|
|
1235
|
+
<span className={s.group_value}>
|
|
1236
|
+
<FlexRender cell={groupedRenderCell} />
|
|
1237
|
+
</span>
|
|
1238
|
+
<span className={s.group_count}>
|
|
1239
|
+
({groupedRenderCell.row.subRows.length})
|
|
1240
|
+
</span>
|
|
1241
|
+
</button>
|
|
1242
|
+
);
|
|
1243
|
+
} else if (groupedRenderCell && !cell.getIsAggregated()) {
|
|
1244
|
+
// A group row shows its toggle and its aggregates only. Any other column
|
|
1245
|
+
// (the grouped one, or one with nothing to aggregate, like text under
|
|
1246
|
+
// `auto`) stays empty instead of calling its `cell` with no row value.
|
|
1247
|
+
body = null;
|
|
1248
|
+
} else {
|
|
1249
|
+
body = <FlexRender cell={cell} />;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Group rows are full-width summary rows; sticky pinning makes no sense
|
|
1253
|
+
// on them. We strip BOTH the pinned CSS classes AND the inline sticky
|
|
1254
|
+
// offsets. Without this, td:focus-within (which sets position:relative)
|
|
1255
|
+
// would combine with the offset from getColumnStyle and physically shift
|
|
1256
|
+
// the cell when the user clicks the group-toggle chevron.
|
|
1257
|
+
const isGroupRow = !!groupedRenderCell;
|
|
1258
|
+
const baseStyle = getColumnStyle(column, column.getSize(), extraWidth);
|
|
1259
|
+
const cellStyle: CSSProperties = isGroupRow
|
|
1260
|
+
? {
|
|
1261
|
+
width: baseStyle.width,
|
|
1262
|
+
minWidth: baseStyle.minWidth,
|
|
1263
|
+
maxWidth: baseStyle.maxWidth,
|
|
1264
|
+
}
|
|
1265
|
+
: baseStyle;
|
|
1266
|
+
|
|
1267
|
+
return (
|
|
1268
|
+
<td
|
|
1269
|
+
className={clsx(
|
|
1270
|
+
colMeta?.cellClassName,
|
|
1271
|
+
s[`align_${align}`],
|
|
1272
|
+
isGroupAnchorCell && s.group_anchor_cell,
|
|
1273
|
+
!isGroupRow && isSelectColumn && s.col_select,
|
|
1274
|
+
!isGroupRow && isPinned === "start" && s.col_pinned_start,
|
|
1275
|
+
!isGroupRow && isPinned === "end" && s.col_pinned_end,
|
|
1276
|
+
!isGroupRow && isLastPinnedCol && s.col_pinned_last,
|
|
1277
|
+
)}
|
|
1278
|
+
style={cellStyle}
|
|
1279
|
+
>
|
|
1280
|
+
<div className={clsx(s.td_inner, !truncate && s.td_inner_no_truncate)}>
|
|
1281
|
+
{body}
|
|
1282
|
+
</div>
|
|
1283
|
+
</td>
|
|
1284
|
+
);
|
|
1285
|
+
};
|