@mshuja288/aksuicore 0.4.0 → 0.4.2
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/CHANGELOG.md +11 -0
- package/dist/aksui.css +270 -26
- package/dist/index.cjs +548 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +548 -271
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/patterns/RecordWorkspace.tsx +574 -252
- package/src/patterns/index.ts +3 -0
- package/src/theme/patterns.css +267 -25
- package/src/theme/primitives.css +3 -1
|
@@ -21,6 +21,25 @@ function useIsClient() {
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Hover tooltips only on fine pointers (desktop). Touch / narrow layouts never get them. */
|
|
25
|
+
function useHoverTooltipsEnabled() {
|
|
26
|
+
return useSyncExternalStore(
|
|
27
|
+
(onStoreChange) => {
|
|
28
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
29
|
+
return () => {};
|
|
30
|
+
}
|
|
31
|
+
const media = window.matchMedia("(hover: hover) and (pointer: fine)");
|
|
32
|
+
media.addEventListener("change", onStoreChange);
|
|
33
|
+
return () => media.removeEventListener("change", onStoreChange);
|
|
34
|
+
},
|
|
35
|
+
() =>
|
|
36
|
+
typeof window !== "undefined" &&
|
|
37
|
+
typeof window.matchMedia === "function" &&
|
|
38
|
+
window.matchMedia("(hover: hover) and (pointer: fine)").matches,
|
|
39
|
+
() => false,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
24
43
|
export type TableColumn<T> = {
|
|
25
44
|
key: string;
|
|
26
45
|
header: string;
|
|
@@ -46,12 +65,34 @@ export type ExportColumnRef = {
|
|
|
46
65
|
header: string;
|
|
47
66
|
};
|
|
48
67
|
|
|
68
|
+
export type BulkImportMode = "insert" | "update" | "upsert";
|
|
69
|
+
|
|
70
|
+
export type BulkImportError = {
|
|
71
|
+
row: number;
|
|
72
|
+
field?: string | null;
|
|
73
|
+
code: string;
|
|
74
|
+
message: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type BulkImportResult = {
|
|
78
|
+
totalRows: number;
|
|
79
|
+
failed: number;
|
|
80
|
+
applied: boolean;
|
|
81
|
+
errors: BulkImportError[];
|
|
82
|
+
};
|
|
83
|
+
|
|
49
84
|
const EXPORT_FORMAT_OPTIONS: { format: ExportDownloadFormat; label: string }[] = [
|
|
50
85
|
{ format: "csv", label: "CSV" },
|
|
51
86
|
{ format: "xlsx", label: "Excel" },
|
|
52
87
|
{ format: "json", label: "JSON" },
|
|
53
88
|
];
|
|
54
89
|
|
|
90
|
+
const BULK_IMPORT_MODE_OPTIONS: { mode: BulkImportMode; label: string }[] = [
|
|
91
|
+
{ mode: "insert", label: "Insert" },
|
|
92
|
+
{ mode: "update", label: "Update" },
|
|
93
|
+
{ mode: "upsert", label: "Upsert" },
|
|
94
|
+
];
|
|
95
|
+
|
|
55
96
|
type SortDirection = "asc" | "desc";
|
|
56
97
|
type SortState = { key: string; direction: SortDirection };
|
|
57
98
|
type DetailLine = { label: string; value: string };
|
|
@@ -175,6 +216,16 @@ function PreviewIcon() {
|
|
|
175
216
|
);
|
|
176
217
|
}
|
|
177
218
|
|
|
219
|
+
function UploadIcon() {
|
|
220
|
+
return (
|
|
221
|
+
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
222
|
+
<path d="M10 16.5V7.3" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
|
223
|
+
<path d="M6.5 10.8 10 7.2l3.5 3.6" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
|
|
224
|
+
<path d="M4 4.5h12" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
|
225
|
+
</svg>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
178
229
|
function DownloadIcon() {
|
|
179
230
|
return (
|
|
180
231
|
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
@@ -195,6 +246,21 @@ function MoreIcon() {
|
|
|
195
246
|
);
|
|
196
247
|
}
|
|
197
248
|
|
|
249
|
+
function ToolsIcon() {
|
|
250
|
+
return (
|
|
251
|
+
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
252
|
+
<path
|
|
253
|
+
d="M11.5 3.5h5v5M16.2 3.8 10 10M8.2 5.2H3.8v11h11V12"
|
|
254
|
+
fill="none"
|
|
255
|
+
stroke="currentColor"
|
|
256
|
+
strokeWidth="1.6"
|
|
257
|
+
strokeLinecap="round"
|
|
258
|
+
strokeLinejoin="round"
|
|
259
|
+
/>
|
|
260
|
+
</svg>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
198
264
|
function DetailList({ lines }: { lines: DetailLine[] }) {
|
|
199
265
|
return (
|
|
200
266
|
<dl className="record-detail-list">
|
|
@@ -224,7 +290,11 @@ export function RecordWorkspace<T>({
|
|
|
224
290
|
canDelete,
|
|
225
291
|
onDownload,
|
|
226
292
|
canDownload = true,
|
|
293
|
+
canExport,
|
|
227
294
|
downloadBusy = false,
|
|
295
|
+
onBulkImport,
|
|
296
|
+
canImport = true,
|
|
297
|
+
importBusy = false,
|
|
228
298
|
showColumnPicker = true,
|
|
229
299
|
columnPrefsKey,
|
|
230
300
|
visibleColumnKeys,
|
|
@@ -251,8 +321,13 @@ export function RecordWorkspace<T>({
|
|
|
251
321
|
* Second argument lists currently visible columns (exportKey ?? key).
|
|
252
322
|
*/
|
|
253
323
|
onDownload?: (format: ExportDownloadFormat, columns: ExportColumnRef[]) => void;
|
|
324
|
+
/** Alias for `canDownload` (RBAC). When set, export requires this and `canDownload` to be true. */
|
|
325
|
+
canExport?: boolean;
|
|
254
326
|
canDownload?: boolean;
|
|
255
327
|
downloadBusy?: boolean;
|
|
328
|
+
onBulkImport?: (mode: BulkImportMode, file: File) => Promise<BulkImportResult>;
|
|
329
|
+
canImport?: boolean;
|
|
330
|
+
importBusy?: boolean;
|
|
256
331
|
/** Column checklist in the side rail. Default true. */
|
|
257
332
|
showColumnPicker?: boolean;
|
|
258
333
|
/** Persist visible column keys in localStorage under this key. */
|
|
@@ -278,16 +353,28 @@ export function RecordWorkspace<T>({
|
|
|
278
353
|
const [filters, setFilters] = useState<Record<string, string>>({});
|
|
279
354
|
const [sort, setSort] = useState<SortState | null>(null);
|
|
280
355
|
const [sideOpen, setSideOpen] = useState(false);
|
|
356
|
+
const [toolsDrawerOpen, setToolsDrawerOpen] = useState(false);
|
|
281
357
|
const [downloadOpen, setDownloadOpen] = useState(false);
|
|
358
|
+
const [bulkOpen, setBulkOpen] = useState(false);
|
|
359
|
+
const [bulkMode, setBulkMode] = useState<BulkImportMode>("upsert");
|
|
360
|
+
const [bulkFile, setBulkFile] = useState<File | null>(null);
|
|
361
|
+
const [bulkReport, setBulkReport] = useState<BulkImportResult | null>(null);
|
|
362
|
+
const bulkFileInputRef = useRef<HTMLInputElement | null>(null);
|
|
282
363
|
const [columnsOpen, setColumnsOpen] = useState(false);
|
|
283
364
|
const [previewEnabled, setPreviewEnabled] = useState(false);
|
|
284
365
|
const [previewKey, setPreviewKey] = useState<string | number | null>(null);
|
|
285
366
|
const [tooltip, setTooltip] = useState<TooltipState | null>(null);
|
|
286
367
|
const [pageSize, setPageSize] = useState("15");
|
|
287
368
|
const [page, setPage] = useState(1);
|
|
288
|
-
const [
|
|
369
|
+
const [actionsMenu, setActionsMenu] = useState<{
|
|
370
|
+
key: string | number;
|
|
371
|
+
item: T;
|
|
372
|
+
x: number;
|
|
373
|
+
y: number;
|
|
374
|
+
} | null>(null);
|
|
289
375
|
const actionsMenuRef = useRef<HTMLDivElement | null>(null);
|
|
290
376
|
const isClient = useIsClient();
|
|
377
|
+
const hoverTooltipsEnabled = useHoverTooltipsEnabled();
|
|
291
378
|
|
|
292
379
|
useEffect(() => {
|
|
293
380
|
if (isControlled) {
|
|
@@ -376,6 +463,7 @@ export function RecordWorkspace<T>({
|
|
|
376
463
|
);
|
|
377
464
|
|
|
378
465
|
const hiddenColumnCount = columns.length - visibleColumns.length;
|
|
466
|
+
const toolsBadgeCount = activeFilterCount + (hiddenColumnCount > 0 ? 1 : 0) + (previewEnabled ? 1 : 0);
|
|
379
467
|
const showAll = pageSize === "all";
|
|
380
468
|
const numericPageSize = showAll ? Math.max(sorted.length, 1) : Number(pageSize);
|
|
381
469
|
const pageCount = showAll ? 1 : Math.max(1, Math.ceil(sorted.length / numericPageSize));
|
|
@@ -383,7 +471,9 @@ export function RecordWorkspace<T>({
|
|
|
383
471
|
const start = showAll ? 0 : (currentPage - 1) * numericPageSize;
|
|
384
472
|
const pageItems = showAll ? sorted : sorted.slice(start, start + numericPageSize);
|
|
385
473
|
const showAdd = canAdd && Boolean(onAdd);
|
|
386
|
-
const
|
|
474
|
+
const exportAllowed = (canExport ?? true) && canDownload;
|
|
475
|
+
const showDownload = Boolean(onDownload) && exportAllowed;
|
|
476
|
+
const showBulkImport = Boolean(onBulkImport) && canImport;
|
|
387
477
|
const showFilters = filterableColumns.length > 0;
|
|
388
478
|
const showColumns = showColumnPicker && columns.length > 0;
|
|
389
479
|
const previewItem = previewKey === null ? null : sorted.find((item) => rowKey(item) === previewKey) ?? null;
|
|
@@ -391,21 +481,42 @@ export function RecordWorkspace<T>({
|
|
|
391
481
|
key: column.exportKey ?? column.key,
|
|
392
482
|
header: column.header,
|
|
393
483
|
}));
|
|
484
|
+
const canShowTooltip = hoverTooltipsEnabled && !previewEnabled && actionsMenu === null;
|
|
394
485
|
|
|
395
486
|
useEffect(() => {
|
|
396
|
-
if (
|
|
487
|
+
if (actionsMenu === null) {
|
|
397
488
|
return;
|
|
398
489
|
}
|
|
399
490
|
|
|
400
491
|
function onPointerDown(event: globalThis.MouseEvent) {
|
|
401
492
|
if (actionsMenuRef.current && !actionsMenuRef.current.contains(event.target as Node)) {
|
|
402
|
-
|
|
493
|
+
setActionsMenu(null);
|
|
403
494
|
}
|
|
404
495
|
}
|
|
405
496
|
|
|
406
497
|
document.addEventListener("mousedown", onPointerDown);
|
|
407
498
|
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
408
|
-
}, [
|
|
499
|
+
}, [actionsMenu]);
|
|
500
|
+
|
|
501
|
+
useEffect(() => {
|
|
502
|
+
if (!toolsDrawerOpen) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function onKeyDown(event: KeyboardEvent) {
|
|
507
|
+
if (event.key === "Escape") {
|
|
508
|
+
setToolsDrawerOpen(false);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
document.addEventListener("keydown", onKeyDown);
|
|
513
|
+
const previousOverflow = document.body.style.overflow;
|
|
514
|
+
document.body.style.overflow = "hidden";
|
|
515
|
+
return () => {
|
|
516
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
517
|
+
document.body.style.overflow = previousOverflow;
|
|
518
|
+
};
|
|
519
|
+
}, [toolsDrawerOpen]);
|
|
409
520
|
|
|
410
521
|
function requestDownload(format: ExportDownloadFormat) {
|
|
411
522
|
if (!onDownload || downloadBusy) {
|
|
@@ -415,6 +526,30 @@ export function RecordWorkspace<T>({
|
|
|
415
526
|
setDownloadOpen(false);
|
|
416
527
|
}
|
|
417
528
|
|
|
529
|
+
async function submitBulkImport() {
|
|
530
|
+
if (!onBulkImport || importBusy || !bulkFile) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
setBulkReport(null);
|
|
534
|
+
try {
|
|
535
|
+
const report = await onBulkImport(bulkMode, bulkFile);
|
|
536
|
+
setBulkReport(report);
|
|
537
|
+
if (report.applied) {
|
|
538
|
+
setBulkFile(null);
|
|
539
|
+
if (bulkFileInputRef.current) {
|
|
540
|
+
bulkFileInputRef.current.value = "";
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
setBulkReport({
|
|
545
|
+
totalRows: 0,
|
|
546
|
+
failed: 1,
|
|
547
|
+
applied: false,
|
|
548
|
+
errors: [{ row: 0, code: "ImportFailed", message: "Import request failed." }],
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
418
553
|
function cellText(column: TableColumn<T>, item: T) {
|
|
419
554
|
const raw = column.display ? column.display(item) : column.value(item);
|
|
420
555
|
return raw === null || raw === undefined || raw === "" ? "—" : String(raw);
|
|
@@ -470,6 +605,9 @@ export function RecordWorkspace<T>({
|
|
|
470
605
|
}
|
|
471
606
|
|
|
472
607
|
function showTooltip(item: T, event: MouseEvent) {
|
|
608
|
+
if (!canShowTooltip) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
473
611
|
const padding = 16;
|
|
474
612
|
const width = 280;
|
|
475
613
|
const x = Math.min(event.clientX + 14, window.innerWidth - width - padding);
|
|
@@ -477,6 +615,22 @@ export function RecordWorkspace<T>({
|
|
|
477
615
|
setTooltip({ x, y, lines: detailLines(item) });
|
|
478
616
|
}
|
|
479
617
|
|
|
618
|
+
function openRowActions(item: T, event: MouseEvent<HTMLButtonElement>) {
|
|
619
|
+
event.stopPropagation();
|
|
620
|
+
setTooltip(null);
|
|
621
|
+
const key = rowKey(item);
|
|
622
|
+
if (actionsMenu?.key === key) {
|
|
623
|
+
setActionsMenu(null);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
627
|
+
const menuWidth = 168;
|
|
628
|
+
const menuHeight = 148;
|
|
629
|
+
const x = Math.min(Math.max(8, rect.right - menuWidth), window.innerWidth - menuWidth - 8);
|
|
630
|
+
const y = Math.min(rect.bottom + 6, window.innerHeight - menuHeight - 8);
|
|
631
|
+
setActionsMenu({ key, item, x, y });
|
|
632
|
+
}
|
|
633
|
+
|
|
480
634
|
const summary =
|
|
481
635
|
sorted.length === 0
|
|
482
636
|
? "0 records"
|
|
@@ -484,6 +638,309 @@ export function RecordWorkspace<T>({
|
|
|
484
638
|
? `1–${sorted.length} of ${sorted.length}`
|
|
485
639
|
: `${start + 1}–${Math.min(start + numericPageSize, sorted.length)} of ${sorted.length}`;
|
|
486
640
|
|
|
641
|
+
function renderToolsBody({ compactIcons }: { compactIcons: boolean }) {
|
|
642
|
+
if (compactIcons) {
|
|
643
|
+
return (
|
|
644
|
+
<div className="side-rail-icons">
|
|
645
|
+
<label className="side-rail-icon preview-check-icon" title="Preview">
|
|
646
|
+
<input
|
|
647
|
+
type="checkbox"
|
|
648
|
+
checked={previewEnabled}
|
|
649
|
+
onChange={(event) => setPreviewEnabledValue(event.target.checked)}
|
|
650
|
+
aria-label="Preview"
|
|
651
|
+
/>
|
|
652
|
+
<PreviewIcon />
|
|
653
|
+
</label>
|
|
654
|
+
{showAdd ? (
|
|
655
|
+
<button type="button" className="ghost side-rail-icon" title={addLabel} aria-label={addLabel} onClick={onAdd}>
|
|
656
|
+
<PlusIcon />
|
|
657
|
+
</button>
|
|
658
|
+
) : null}
|
|
659
|
+
{showColumns ? (
|
|
660
|
+
<button
|
|
661
|
+
type="button"
|
|
662
|
+
className="ghost side-rail-icon"
|
|
663
|
+
title="Columns"
|
|
664
|
+
aria-label="Columns"
|
|
665
|
+
onClick={() => {
|
|
666
|
+
setSideOpen(true);
|
|
667
|
+
setColumnsOpen(true);
|
|
668
|
+
}}
|
|
669
|
+
>
|
|
670
|
+
<ColumnsIcon />
|
|
671
|
+
{hiddenColumnCount > 0 ? <span className="filter-badge">{hiddenColumnCount}</span> : null}
|
|
672
|
+
</button>
|
|
673
|
+
) : null}
|
|
674
|
+
{showDownload ? (
|
|
675
|
+
<button
|
|
676
|
+
type="button"
|
|
677
|
+
className="ghost side-rail-icon"
|
|
678
|
+
title="Download"
|
|
679
|
+
aria-label="Download"
|
|
680
|
+
disabled={downloadBusy}
|
|
681
|
+
onClick={() => {
|
|
682
|
+
setSideOpen(true);
|
|
683
|
+
setDownloadOpen(true);
|
|
684
|
+
}}
|
|
685
|
+
>
|
|
686
|
+
<DownloadIcon />
|
|
687
|
+
</button>
|
|
688
|
+
) : null}
|
|
689
|
+
{showBulkImport ? (
|
|
690
|
+
<button
|
|
691
|
+
type="button"
|
|
692
|
+
className="ghost side-rail-icon"
|
|
693
|
+
title="Bulk entry"
|
|
694
|
+
aria-label="Bulk entry"
|
|
695
|
+
disabled={importBusy}
|
|
696
|
+
onClick={() => {
|
|
697
|
+
setSideOpen(true);
|
|
698
|
+
setBulkOpen(true);
|
|
699
|
+
}}
|
|
700
|
+
>
|
|
701
|
+
<UploadIcon />
|
|
702
|
+
</button>
|
|
703
|
+
) : null}
|
|
704
|
+
{showFilters ? (
|
|
705
|
+
<button
|
|
706
|
+
type="button"
|
|
707
|
+
className="ghost side-rail-icon"
|
|
708
|
+
title="Show filters"
|
|
709
|
+
aria-label="Show filters"
|
|
710
|
+
onClick={() => setSideOpen(true)}
|
|
711
|
+
>
|
|
712
|
+
<FilterIcon />
|
|
713
|
+
{activeFilterCount > 0 ? <span className="filter-badge">{activeFilterCount}</span> : null}
|
|
714
|
+
</button>
|
|
715
|
+
) : null}
|
|
716
|
+
{showFilters ? (
|
|
717
|
+
<button
|
|
718
|
+
type="button"
|
|
719
|
+
className="ghost side-rail-icon"
|
|
720
|
+
title="Reset filters"
|
|
721
|
+
aria-label="Reset filters"
|
|
722
|
+
disabled={activeFilterCount === 0}
|
|
723
|
+
onClick={clearFilters}
|
|
724
|
+
>
|
|
725
|
+
<ResetIcon />
|
|
726
|
+
</button>
|
|
727
|
+
) : null}
|
|
728
|
+
</div>
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return (
|
|
733
|
+
<div className="side-rail-body">
|
|
734
|
+
<div className="preview-check">
|
|
735
|
+
<input
|
|
736
|
+
type="checkbox"
|
|
737
|
+
checked={previewEnabled}
|
|
738
|
+
onChange={(event) => setPreviewEnabledValue(event.target.checked)}
|
|
739
|
+
aria-label="Preview"
|
|
740
|
+
/>
|
|
741
|
+
<PreviewIcon />
|
|
742
|
+
<span aria-hidden="true">Preview</span>
|
|
743
|
+
</div>
|
|
744
|
+
|
|
745
|
+
{showAdd ? (
|
|
746
|
+
<button
|
|
747
|
+
type="button"
|
|
748
|
+
className="side-rail-add"
|
|
749
|
+
onClick={() => {
|
|
750
|
+
setToolsDrawerOpen(false);
|
|
751
|
+
onAdd?.();
|
|
752
|
+
}}
|
|
753
|
+
>
|
|
754
|
+
<PlusIcon />
|
|
755
|
+
<span>{addLabel}</span>
|
|
756
|
+
</button>
|
|
757
|
+
) : null}
|
|
758
|
+
|
|
759
|
+
{showColumns ? (
|
|
760
|
+
<div className="side-rail-columns">
|
|
761
|
+
<button
|
|
762
|
+
type="button"
|
|
763
|
+
className="ghost side-rail-columns-toggle"
|
|
764
|
+
aria-expanded={columnsOpen}
|
|
765
|
+
onClick={() => setColumnsOpen((open) => !open)}
|
|
766
|
+
>
|
|
767
|
+
<ColumnsIcon />
|
|
768
|
+
<span>Columns{hiddenColumnCount > 0 ? ` (${visibleColumns.length}/${columns.length})` : ""}</span>
|
|
769
|
+
</button>
|
|
770
|
+
{columnsOpen ? (
|
|
771
|
+
<div className="side-rail-columns-panel" role="group" aria-label="Visible columns">
|
|
772
|
+
<div className="side-rail-columns-actions">
|
|
773
|
+
<button type="button" className="ghost" onClick={selectAllColumns}>
|
|
774
|
+
Select all
|
|
775
|
+
</button>
|
|
776
|
+
<button type="button" className="ghost" onClick={resetColumns}>
|
|
777
|
+
Reset
|
|
778
|
+
</button>
|
|
779
|
+
</div>
|
|
780
|
+
<div className="side-rail-columns-list">
|
|
781
|
+
{columns.map((column) => {
|
|
782
|
+
const checked = resolvedVisibleKeys.includes(column.key);
|
|
783
|
+
const disableUncheck = checked && resolvedVisibleKeys.length <= 1;
|
|
784
|
+
return (
|
|
785
|
+
<label key={column.key} className="side-rail-column-option">
|
|
786
|
+
<input
|
|
787
|
+
type="checkbox"
|
|
788
|
+
checked={checked}
|
|
789
|
+
disabled={disableUncheck}
|
|
790
|
+
onChange={(event) => toggleColumnVisible(column.key, event.target.checked)}
|
|
791
|
+
/>
|
|
792
|
+
<span>{column.header}</span>
|
|
793
|
+
</label>
|
|
794
|
+
);
|
|
795
|
+
})}
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
) : null}
|
|
799
|
+
</div>
|
|
800
|
+
) : null}
|
|
801
|
+
|
|
802
|
+
{showDownload ? (
|
|
803
|
+
<div className="side-rail-download">
|
|
804
|
+
<button
|
|
805
|
+
type="button"
|
|
806
|
+
className="ghost side-rail-download-toggle"
|
|
807
|
+
aria-expanded={downloadOpen}
|
|
808
|
+
disabled={downloadBusy}
|
|
809
|
+
onClick={() => setDownloadOpen((open) => !open)}
|
|
810
|
+
>
|
|
811
|
+
<DownloadIcon />
|
|
812
|
+
<span>{downloadBusy ? "Downloading…" : "Download"}</span>
|
|
813
|
+
</button>
|
|
814
|
+
{downloadOpen ? (
|
|
815
|
+
<div className="side-rail-download-formats" role="group" aria-label="Download format">
|
|
816
|
+
{EXPORT_FORMAT_OPTIONS.map((option) => (
|
|
817
|
+
<button
|
|
818
|
+
key={option.format}
|
|
819
|
+
type="button"
|
|
820
|
+
className="ghost side-rail-download-format"
|
|
821
|
+
disabled={downloadBusy}
|
|
822
|
+
onClick={() => requestDownload(option.format)}
|
|
823
|
+
>
|
|
824
|
+
{option.label}
|
|
825
|
+
</button>
|
|
826
|
+
))}
|
|
827
|
+
</div>
|
|
828
|
+
) : null}
|
|
829
|
+
</div>
|
|
830
|
+
) : null}
|
|
831
|
+
|
|
832
|
+
{showBulkImport ? (
|
|
833
|
+
<div className="side-rail-bulk">
|
|
834
|
+
<button
|
|
835
|
+
type="button"
|
|
836
|
+
className="ghost side-rail-bulk-toggle"
|
|
837
|
+
aria-expanded={bulkOpen}
|
|
838
|
+
disabled={importBusy}
|
|
839
|
+
onClick={() => setBulkOpen((open) => !open)}
|
|
840
|
+
>
|
|
841
|
+
<UploadIcon />
|
|
842
|
+
<span>{importBusy ? "Importing…" : "Bulk entry"}</span>
|
|
843
|
+
</button>
|
|
844
|
+
{bulkOpen ? (
|
|
845
|
+
<div className="side-rail-bulk-panel" role="group" aria-label="Bulk entry">
|
|
846
|
+
<label className="side-rail-bulk-mode">
|
|
847
|
+
Mode
|
|
848
|
+
<Select
|
|
849
|
+
value={bulkMode}
|
|
850
|
+
options={BULK_IMPORT_MODE_OPTIONS.map((option) => ({ value: option.mode, label: option.label }))}
|
|
851
|
+
onChange={(value) => setBulkMode(value as BulkImportMode)}
|
|
852
|
+
/>
|
|
853
|
+
</label>
|
|
854
|
+
<label className="side-rail-bulk-file">
|
|
855
|
+
File
|
|
856
|
+
<input
|
|
857
|
+
ref={bulkFileInputRef}
|
|
858
|
+
type="file"
|
|
859
|
+
accept=".csv,.xlsx,.json"
|
|
860
|
+
disabled={importBusy}
|
|
861
|
+
onChange={(event) => {
|
|
862
|
+
setBulkFile(event.target.files?.[0] ?? null);
|
|
863
|
+
setBulkReport(null);
|
|
864
|
+
}}
|
|
865
|
+
/>
|
|
866
|
+
</label>
|
|
867
|
+
<button
|
|
868
|
+
type="button"
|
|
869
|
+
className="side-rail-bulk-submit"
|
|
870
|
+
disabled={importBusy || !bulkFile}
|
|
871
|
+
onClick={() => void submitBulkImport()}
|
|
872
|
+
>
|
|
873
|
+
{importBusy ? "Importing…" : "Import"}
|
|
874
|
+
</button>
|
|
875
|
+
{bulkReport ? (
|
|
876
|
+
<div className="side-rail-bulk-report" role="status">
|
|
877
|
+
<p>
|
|
878
|
+
{bulkReport.applied
|
|
879
|
+
? `Applied ${bulkReport.totalRows} row(s).`
|
|
880
|
+
: `${bulkReport.failed} error(s) in ${bulkReport.totalRows} row(s).`}
|
|
881
|
+
</p>
|
|
882
|
+
{bulkReport.errors.length > 0 ? (
|
|
883
|
+
<ul className="side-rail-bulk-errors">
|
|
884
|
+
{bulkReport.errors.slice(0, 8).map((error, index) => (
|
|
885
|
+
<li key={`${error.row}-${error.code}-${index}`}>
|
|
886
|
+
Row {error.row}: {error.message}
|
|
887
|
+
</li>
|
|
888
|
+
))}
|
|
889
|
+
{bulkReport.errors.length > 8 ? (
|
|
890
|
+
<li>…and {bulkReport.errors.length - 8} more</li>
|
|
891
|
+
) : null}
|
|
892
|
+
</ul>
|
|
893
|
+
) : null}
|
|
894
|
+
</div>
|
|
895
|
+
) : null}
|
|
896
|
+
</div>
|
|
897
|
+
) : null}
|
|
898
|
+
</div>
|
|
899
|
+
) : null}
|
|
900
|
+
|
|
901
|
+
{showFilters ? (
|
|
902
|
+
<div className="side-rail-filters">
|
|
903
|
+
<div className="filter-panel-head">
|
|
904
|
+
<h3>Filters</h3>
|
|
905
|
+
<button
|
|
906
|
+
type="button"
|
|
907
|
+
className="ghost filter-clear"
|
|
908
|
+
title="Reset filters"
|
|
909
|
+
aria-label="Reset filters"
|
|
910
|
+
disabled={activeFilterCount === 0}
|
|
911
|
+
onClick={clearFilters}
|
|
912
|
+
>
|
|
913
|
+
<ResetIcon />
|
|
914
|
+
<span>Reset</span>
|
|
915
|
+
</button>
|
|
916
|
+
</div>
|
|
917
|
+
<div className="filter-panel-stack">
|
|
918
|
+
{filterableColumns.map((column) => (
|
|
919
|
+
<label key={column.key}>
|
|
920
|
+
{column.header}
|
|
921
|
+
{column.filterOptions ? (
|
|
922
|
+
<Select
|
|
923
|
+
value={filters[column.key] ?? ""}
|
|
924
|
+
options={[{ value: "", label: column.filterPlaceholder ?? "All" }, ...column.filterOptions]}
|
|
925
|
+
onChange={(value) => changeFilter(column.key, value)}
|
|
926
|
+
placeholder={column.filterPlaceholder ?? "All"}
|
|
927
|
+
/>
|
|
928
|
+
) : (
|
|
929
|
+
<input
|
|
930
|
+
value={filters[column.key] ?? ""}
|
|
931
|
+
onChange={(event) => changeFilter(column.key, event.target.value)}
|
|
932
|
+
placeholder={`Filter ${column.header.toLowerCase()}`}
|
|
933
|
+
/>
|
|
934
|
+
)}
|
|
935
|
+
</label>
|
|
936
|
+
))}
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
) : null}
|
|
940
|
+
</div>
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
487
944
|
function renderRowActions(item: T) {
|
|
488
945
|
const key = rowKey(item);
|
|
489
946
|
const updateAllowed = canUpdate ? canUpdate(item) : Boolean(onUpdate);
|
|
@@ -525,69 +982,60 @@ export function RecordWorkspace<T>({
|
|
|
525
982
|
) : null}
|
|
526
983
|
</div>
|
|
527
984
|
|
|
528
|
-
<div className="row-actions row-actions-mobile"
|
|
985
|
+
<div className="row-actions row-actions-mobile">
|
|
529
986
|
<button
|
|
530
987
|
type="button"
|
|
531
|
-
className="ghost row-action-menu-trigger"
|
|
988
|
+
className={actionsMenu?.key === key ? "ghost row-action-menu-trigger open" : "ghost row-action-menu-trigger"}
|
|
532
989
|
aria-haspopup="menu"
|
|
533
|
-
aria-expanded={
|
|
990
|
+
aria-expanded={actionsMenu?.key === key}
|
|
534
991
|
aria-label="Row actions"
|
|
535
|
-
onClick={() =>
|
|
992
|
+
onClick={(event) => openRowActions(item, event)}
|
|
536
993
|
>
|
|
537
994
|
<MoreIcon />
|
|
538
995
|
</button>
|
|
539
|
-
{actionsMenuKey === key ? (
|
|
540
|
-
<div className="row-action-menu" role="menu">
|
|
541
|
-
{onView ? (
|
|
542
|
-
<button
|
|
543
|
-
type="button"
|
|
544
|
-
role="menuitem"
|
|
545
|
-
className="ghost"
|
|
546
|
-
onClick={() => {
|
|
547
|
-
setActionsMenuKey(null);
|
|
548
|
-
onView(item);
|
|
549
|
-
}}
|
|
550
|
-
>
|
|
551
|
-
View
|
|
552
|
-
</button>
|
|
553
|
-
) : null}
|
|
554
|
-
{onUpdate ? (
|
|
555
|
-
<button
|
|
556
|
-
type="button"
|
|
557
|
-
role="menuitem"
|
|
558
|
-
className="ghost"
|
|
559
|
-
disabled={!updateAllowed}
|
|
560
|
-
onClick={() => {
|
|
561
|
-
setActionsMenuKey(null);
|
|
562
|
-
onUpdate(item);
|
|
563
|
-
}}
|
|
564
|
-
>
|
|
565
|
-
Update
|
|
566
|
-
</button>
|
|
567
|
-
) : null}
|
|
568
|
-
{onDelete ? (
|
|
569
|
-
<button
|
|
570
|
-
type="button"
|
|
571
|
-
role="menuitem"
|
|
572
|
-
className="ghost danger"
|
|
573
|
-
disabled={!deleteAllowed}
|
|
574
|
-
onClick={() => {
|
|
575
|
-
setActionsMenuKey(null);
|
|
576
|
-
onDelete(item);
|
|
577
|
-
}}
|
|
578
|
-
>
|
|
579
|
-
Delete
|
|
580
|
-
</button>
|
|
581
|
-
) : null}
|
|
582
|
-
</div>
|
|
583
|
-
) : null}
|
|
584
996
|
</div>
|
|
585
997
|
</>
|
|
586
998
|
);
|
|
587
999
|
}
|
|
588
1000
|
|
|
1001
|
+
const actionsMenuItem = actionsMenu?.item;
|
|
1002
|
+
const actionsUpdateAllowed = actionsMenuItem
|
|
1003
|
+
? canUpdate
|
|
1004
|
+
? canUpdate(actionsMenuItem)
|
|
1005
|
+
: Boolean(onUpdate)
|
|
1006
|
+
: false;
|
|
1007
|
+
const actionsDeleteAllowed = actionsMenuItem
|
|
1008
|
+
? canDelete
|
|
1009
|
+
? canDelete(actionsMenuItem)
|
|
1010
|
+
: Boolean(onDelete)
|
|
1011
|
+
: false;
|
|
1012
|
+
|
|
589
1013
|
return (
|
|
590
|
-
<div className="list-workspace" aria-label={title}>
|
|
1014
|
+
<div className={toolsDrawerOpen ? "list-workspace tools-drawer-open" : "list-workspace"} aria-label={title}>
|
|
1015
|
+
<div className="tools-mobile-bar">
|
|
1016
|
+
<div className="tools-mobile-bar-start">
|
|
1017
|
+
{showAdd ? (
|
|
1018
|
+
<button type="button" className="tools-mobile-add" onClick={onAdd}>
|
|
1019
|
+
<PlusIcon />
|
|
1020
|
+
<span>{addLabel}</span>
|
|
1021
|
+
</button>
|
|
1022
|
+
) : (
|
|
1023
|
+
<p className="tools-mobile-title">{title}</p>
|
|
1024
|
+
)}
|
|
1025
|
+
</div>
|
|
1026
|
+
<button
|
|
1027
|
+
type="button"
|
|
1028
|
+
className="ghost tools-mobile-toggle"
|
|
1029
|
+
aria-expanded={toolsDrawerOpen}
|
|
1030
|
+
aria-controls="aksui-tools-drawer"
|
|
1031
|
+
onClick={() => setToolsDrawerOpen(true)}
|
|
1032
|
+
>
|
|
1033
|
+
<ToolsIcon />
|
|
1034
|
+
<span>Tools</span>
|
|
1035
|
+
{toolsBadgeCount > 0 ? <span className="filter-badge">{toolsBadgeCount}</span> : null}
|
|
1036
|
+
</button>
|
|
1037
|
+
</div>
|
|
1038
|
+
|
|
591
1039
|
<div
|
|
592
1040
|
className={["list-body", sideOpen ? "side-open" : "", previewEnabled ? "preview-open" : ""]
|
|
593
1041
|
.filter(Boolean)
|
|
@@ -651,16 +1099,10 @@ export function RecordWorkspace<T>({
|
|
|
651
1099
|
key={key}
|
|
652
1100
|
className={selected ? "data-row selected" : "data-row"}
|
|
653
1101
|
onMouseEnter={(event) => {
|
|
654
|
-
|
|
655
|
-
showTooltip(item, event);
|
|
656
|
-
}
|
|
1102
|
+
showTooltip(item, event);
|
|
657
1103
|
focusRecord(item);
|
|
658
1104
|
}}
|
|
659
|
-
onMouseMove={(event) =>
|
|
660
|
-
if (!previewEnabled) {
|
|
661
|
-
showTooltip(item, event);
|
|
662
|
-
}
|
|
663
|
-
}}
|
|
1105
|
+
onMouseMove={(event) => showTooltip(item, event)}
|
|
664
1106
|
onMouseLeave={() => setTooltip(null)}
|
|
665
1107
|
onClick={() => focusRecord(item)}
|
|
666
1108
|
>
|
|
@@ -704,7 +1146,7 @@ export function RecordWorkspace<T>({
|
|
|
704
1146
|
{previewItem ? (
|
|
705
1147
|
<DetailList lines={detailLines(previewItem)} />
|
|
706
1148
|
) : (
|
|
707
|
-
<p className="preview-empty">
|
|
1149
|
+
<p className="preview-empty">Select a row to preview its details here.</p>
|
|
708
1150
|
)}
|
|
709
1151
|
</div>
|
|
710
1152
|
</aside>
|
|
@@ -721,219 +1163,99 @@ export function RecordWorkspace<T>({
|
|
|
721
1163
|
<ChevronIcon open={sideOpen} />
|
|
722
1164
|
{sideOpen ? <span>Collapse</span> : null}
|
|
723
1165
|
</button>
|
|
1166
|
+
{sideOpen ? renderToolsBody({ compactIcons: false }) : renderToolsBody({ compactIcons: true })}
|
|
1167
|
+
</aside>
|
|
1168
|
+
</div>
|
|
724
1169
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
type="button"
|
|
749
|
-
className="ghost side-rail-columns-toggle"
|
|
750
|
-
aria-expanded={columnsOpen}
|
|
751
|
-
onClick={() => setColumnsOpen((open) => !open)}
|
|
752
|
-
>
|
|
753
|
-
<ColumnsIcon />
|
|
754
|
-
<span>Columns{hiddenColumnCount > 0 ? ` (${visibleColumns.length}/${columns.length})` : ""}</span>
|
|
1170
|
+
{isClient
|
|
1171
|
+
? createPortal(
|
|
1172
|
+
<>
|
|
1173
|
+
<button
|
|
1174
|
+
type="button"
|
|
1175
|
+
className={toolsDrawerOpen ? "tools-drawer-backdrop open" : "tools-drawer-backdrop"}
|
|
1176
|
+
aria-label="Close tools"
|
|
1177
|
+
tabIndex={toolsDrawerOpen ? 0 : -1}
|
|
1178
|
+
onClick={() => setToolsDrawerOpen(false)}
|
|
1179
|
+
/>
|
|
1180
|
+
<aside
|
|
1181
|
+
id="aksui-tools-drawer"
|
|
1182
|
+
className={toolsDrawerOpen ? "tools-drawer open" : "tools-drawer"}
|
|
1183
|
+
aria-label="Workspace tools"
|
|
1184
|
+
aria-hidden={!toolsDrawerOpen}
|
|
1185
|
+
>
|
|
1186
|
+
<div className="tools-drawer-head">
|
|
1187
|
+
<div>
|
|
1188
|
+
<p className="tools-drawer-kicker">Workspace</p>
|
|
1189
|
+
<h3>Tools</h3>
|
|
1190
|
+
</div>
|
|
1191
|
+
<button type="button" className="ghost tools-drawer-close" onClick={() => setToolsDrawerOpen(false)}>
|
|
1192
|
+
Close
|
|
755
1193
|
</button>
|
|
756
|
-
{columnsOpen ? (
|
|
757
|
-
<div className="side-rail-columns-panel" role="group" aria-label="Visible columns">
|
|
758
|
-
<div className="side-rail-columns-actions">
|
|
759
|
-
<button type="button" className="ghost" onClick={selectAllColumns}>
|
|
760
|
-
Select all
|
|
761
|
-
</button>
|
|
762
|
-
<button type="button" className="ghost" onClick={resetColumns}>
|
|
763
|
-
Reset
|
|
764
|
-
</button>
|
|
765
|
-
</div>
|
|
766
|
-
<div className="side-rail-columns-list">
|
|
767
|
-
{columns.map((column) => {
|
|
768
|
-
const checked = resolvedVisibleKeys.includes(column.key);
|
|
769
|
-
const disableUncheck = checked && resolvedVisibleKeys.length <= 1;
|
|
770
|
-
return (
|
|
771
|
-
<label key={column.key} className="side-rail-column-option">
|
|
772
|
-
<input
|
|
773
|
-
type="checkbox"
|
|
774
|
-
checked={checked}
|
|
775
|
-
disabled={disableUncheck}
|
|
776
|
-
onChange={(event) => toggleColumnVisible(column.key, event.target.checked)}
|
|
777
|
-
/>
|
|
778
|
-
<span>{column.header}</span>
|
|
779
|
-
</label>
|
|
780
|
-
);
|
|
781
|
-
})}
|
|
782
|
-
</div>
|
|
783
|
-
</div>
|
|
784
|
-
) : null}
|
|
785
1194
|
</div>
|
|
786
|
-
|
|
1195
|
+
<div className="tools-drawer-body">{renderToolsBody({ compactIcons: false })}</div>
|
|
1196
|
+
</aside>
|
|
1197
|
+
</>,
|
|
1198
|
+
document.body,
|
|
1199
|
+
)
|
|
1200
|
+
: null}
|
|
787
1201
|
|
|
788
|
-
|
|
789
|
-
<div className="side-rail-download">
|
|
790
|
-
<button
|
|
791
|
-
type="button"
|
|
792
|
-
className="ghost side-rail-download-toggle"
|
|
793
|
-
aria-expanded={downloadOpen}
|
|
794
|
-
disabled={downloadBusy}
|
|
795
|
-
onClick={() => setDownloadOpen((open) => !open)}
|
|
796
|
-
>
|
|
797
|
-
<DownloadIcon />
|
|
798
|
-
<span>{downloadBusy ? "Downloading…" : "Download"}</span>
|
|
799
|
-
</button>
|
|
800
|
-
{downloadOpen ? (
|
|
801
|
-
<div className="side-rail-download-formats" role="group" aria-label="Download format">
|
|
802
|
-
{EXPORT_FORMAT_OPTIONS.map((option) => (
|
|
803
|
-
<button
|
|
804
|
-
key={option.format}
|
|
805
|
-
type="button"
|
|
806
|
-
className="ghost side-rail-download-format"
|
|
807
|
-
disabled={downloadBusy}
|
|
808
|
-
onClick={() => requestDownload(option.format)}
|
|
809
|
-
>
|
|
810
|
-
{option.label}
|
|
811
|
-
</button>
|
|
812
|
-
))}
|
|
813
|
-
</div>
|
|
814
|
-
) : null}
|
|
815
|
-
</div>
|
|
816
|
-
) : null}
|
|
1202
|
+
{form}
|
|
817
1203
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
disabled={activeFilterCount === 0}
|
|
828
|
-
onClick={clearFilters}
|
|
829
|
-
>
|
|
830
|
-
<ResetIcon />
|
|
831
|
-
<span>Reset</span>
|
|
832
|
-
</button>
|
|
833
|
-
</div>
|
|
834
|
-
<div className="filter-panel-stack">
|
|
835
|
-
{filterableColumns.map((column) => (
|
|
836
|
-
<label key={column.key}>
|
|
837
|
-
{column.header}
|
|
838
|
-
{column.filterOptions ? (
|
|
839
|
-
<Select
|
|
840
|
-
value={filters[column.key] ?? ""}
|
|
841
|
-
options={[{ value: "", label: column.filterPlaceholder ?? "All" }, ...column.filterOptions]}
|
|
842
|
-
onChange={(value) => changeFilter(column.key, value)}
|
|
843
|
-
placeholder={column.filterPlaceholder ?? "All"}
|
|
844
|
-
/>
|
|
845
|
-
) : (
|
|
846
|
-
<input
|
|
847
|
-
value={filters[column.key] ?? ""}
|
|
848
|
-
onChange={(event) => changeFilter(column.key, event.target.value)}
|
|
849
|
-
placeholder={`Filter ${column.header.toLowerCase()}`}
|
|
850
|
-
/>
|
|
851
|
-
)}
|
|
852
|
-
</label>
|
|
853
|
-
))}
|
|
854
|
-
</div>
|
|
855
|
-
</div>
|
|
856
|
-
) : null}
|
|
857
|
-
</div>
|
|
858
|
-
) : (
|
|
859
|
-
<div className="side-rail-icons">
|
|
860
|
-
<label className="side-rail-icon preview-check-icon" title="Preview">
|
|
861
|
-
<input
|
|
862
|
-
type="checkbox"
|
|
863
|
-
checked={previewEnabled}
|
|
864
|
-
onChange={(event) => setPreviewEnabledValue(event.target.checked)}
|
|
865
|
-
aria-label="Preview"
|
|
866
|
-
/>
|
|
867
|
-
<PreviewIcon />
|
|
868
|
-
</label>
|
|
869
|
-
{showAdd ? (
|
|
870
|
-
<button type="button" className="ghost side-rail-icon" title={addLabel} aria-label={addLabel} onClick={onAdd}>
|
|
871
|
-
<PlusIcon />
|
|
872
|
-
</button>
|
|
873
|
-
) : null}
|
|
874
|
-
{showColumns ? (
|
|
1204
|
+
{isClient && actionsMenu && actionsMenuItem
|
|
1205
|
+
? createPortal(
|
|
1206
|
+
<div
|
|
1207
|
+
ref={actionsMenuRef}
|
|
1208
|
+
className="row-action-menu row-action-menu-portal"
|
|
1209
|
+
role="menu"
|
|
1210
|
+
style={{ left: actionsMenu.x, top: actionsMenu.y }}
|
|
1211
|
+
>
|
|
1212
|
+
{onView ? (
|
|
875
1213
|
<button
|
|
876
1214
|
type="button"
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
aria-label="Columns"
|
|
1215
|
+
role="menuitem"
|
|
1216
|
+
className="ghost"
|
|
880
1217
|
onClick={() => {
|
|
881
|
-
|
|
882
|
-
|
|
1218
|
+
setActionsMenu(null);
|
|
1219
|
+
onView(actionsMenuItem);
|
|
883
1220
|
}}
|
|
884
1221
|
>
|
|
885
|
-
|
|
886
|
-
{hiddenColumnCount > 0 ? <span className="filter-badge">{hiddenColumnCount}</span> : null}
|
|
1222
|
+
View
|
|
887
1223
|
</button>
|
|
888
1224
|
) : null}
|
|
889
|
-
{
|
|
1225
|
+
{onUpdate ? (
|
|
890
1226
|
<button
|
|
891
1227
|
type="button"
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
aria-expanded={downloadOpen}
|
|
896
|
-
disabled={downloadBusy}
|
|
1228
|
+
role="menuitem"
|
|
1229
|
+
className="ghost"
|
|
1230
|
+
disabled={!actionsUpdateAllowed}
|
|
897
1231
|
onClick={() => {
|
|
898
|
-
|
|
899
|
-
|
|
1232
|
+
setActionsMenu(null);
|
|
1233
|
+
onUpdate(actionsMenuItem);
|
|
900
1234
|
}}
|
|
901
1235
|
>
|
|
902
|
-
|
|
903
|
-
</button>
|
|
904
|
-
) : null}
|
|
905
|
-
{showFilters ? (
|
|
906
|
-
<button
|
|
907
|
-
type="button"
|
|
908
|
-
className="ghost side-rail-icon"
|
|
909
|
-
title="Show filters"
|
|
910
|
-
aria-label="Show filters"
|
|
911
|
-
onClick={() => setSideOpen(true)}
|
|
912
|
-
>
|
|
913
|
-
<FilterIcon />
|
|
914
|
-
{activeFilterCount > 0 ? <span className="filter-badge">{activeFilterCount}</span> : null}
|
|
1236
|
+
Update
|
|
915
1237
|
</button>
|
|
916
1238
|
) : null}
|
|
917
|
-
{
|
|
1239
|
+
{onDelete ? (
|
|
918
1240
|
<button
|
|
919
1241
|
type="button"
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1242
|
+
role="menuitem"
|
|
1243
|
+
className="ghost danger"
|
|
1244
|
+
disabled={!actionsDeleteAllowed}
|
|
1245
|
+
onClick={() => {
|
|
1246
|
+
setActionsMenu(null);
|
|
1247
|
+
onDelete(actionsMenuItem);
|
|
1248
|
+
}}
|
|
925
1249
|
>
|
|
926
|
-
|
|
1250
|
+
Delete
|
|
927
1251
|
</button>
|
|
928
1252
|
) : null}
|
|
929
|
-
</div
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
{form}
|
|
1253
|
+
</div>,
|
|
1254
|
+
document.body,
|
|
1255
|
+
)
|
|
1256
|
+
: null}
|
|
935
1257
|
|
|
936
|
-
{isClient && tooltip &&
|
|
1258
|
+
{isClient && tooltip && canShowTooltip
|
|
937
1259
|
? createPortal(
|
|
938
1260
|
<div className="record-tooltip" style={{ left: tooltip.x, top: tooltip.y }} role="tooltip">
|
|
939
1261
|
<p className="record-tooltip-title">{title}</p>
|