@mshuja288/aksuicore 0.4.0 → 0.4.1
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 +195 -25
- package/dist/index.cjs +415 -270
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +415 -270
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/patterns/RecordWorkspace.tsx +417 -251
- package/src/theme/patterns.css +192 -24
- 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;
|
|
@@ -195,6 +214,21 @@ function MoreIcon() {
|
|
|
195
214
|
);
|
|
196
215
|
}
|
|
197
216
|
|
|
217
|
+
function ToolsIcon() {
|
|
218
|
+
return (
|
|
219
|
+
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
220
|
+
<path
|
|
221
|
+
d="M11.5 3.5h5v5M16.2 3.8 10 10M8.2 5.2H3.8v11h11V12"
|
|
222
|
+
fill="none"
|
|
223
|
+
stroke="currentColor"
|
|
224
|
+
strokeWidth="1.6"
|
|
225
|
+
strokeLinecap="round"
|
|
226
|
+
strokeLinejoin="round"
|
|
227
|
+
/>
|
|
228
|
+
</svg>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
198
232
|
function DetailList({ lines }: { lines: DetailLine[] }) {
|
|
199
233
|
return (
|
|
200
234
|
<dl className="record-detail-list">
|
|
@@ -278,6 +312,7 @@ export function RecordWorkspace<T>({
|
|
|
278
312
|
const [filters, setFilters] = useState<Record<string, string>>({});
|
|
279
313
|
const [sort, setSort] = useState<SortState | null>(null);
|
|
280
314
|
const [sideOpen, setSideOpen] = useState(false);
|
|
315
|
+
const [toolsDrawerOpen, setToolsDrawerOpen] = useState(false);
|
|
281
316
|
const [downloadOpen, setDownloadOpen] = useState(false);
|
|
282
317
|
const [columnsOpen, setColumnsOpen] = useState(false);
|
|
283
318
|
const [previewEnabled, setPreviewEnabled] = useState(false);
|
|
@@ -285,9 +320,15 @@ export function RecordWorkspace<T>({
|
|
|
285
320
|
const [tooltip, setTooltip] = useState<TooltipState | null>(null);
|
|
286
321
|
const [pageSize, setPageSize] = useState("15");
|
|
287
322
|
const [page, setPage] = useState(1);
|
|
288
|
-
const [
|
|
323
|
+
const [actionsMenu, setActionsMenu] = useState<{
|
|
324
|
+
key: string | number;
|
|
325
|
+
item: T;
|
|
326
|
+
x: number;
|
|
327
|
+
y: number;
|
|
328
|
+
} | null>(null);
|
|
289
329
|
const actionsMenuRef = useRef<HTMLDivElement | null>(null);
|
|
290
330
|
const isClient = useIsClient();
|
|
331
|
+
const hoverTooltipsEnabled = useHoverTooltipsEnabled();
|
|
291
332
|
|
|
292
333
|
useEffect(() => {
|
|
293
334
|
if (isControlled) {
|
|
@@ -376,6 +417,7 @@ export function RecordWorkspace<T>({
|
|
|
376
417
|
);
|
|
377
418
|
|
|
378
419
|
const hiddenColumnCount = columns.length - visibleColumns.length;
|
|
420
|
+
const toolsBadgeCount = activeFilterCount + (hiddenColumnCount > 0 ? 1 : 0) + (previewEnabled ? 1 : 0);
|
|
379
421
|
const showAll = pageSize === "all";
|
|
380
422
|
const numericPageSize = showAll ? Math.max(sorted.length, 1) : Number(pageSize);
|
|
381
423
|
const pageCount = showAll ? 1 : Math.max(1, Math.ceil(sorted.length / numericPageSize));
|
|
@@ -391,21 +433,42 @@ export function RecordWorkspace<T>({
|
|
|
391
433
|
key: column.exportKey ?? column.key,
|
|
392
434
|
header: column.header,
|
|
393
435
|
}));
|
|
436
|
+
const canShowTooltip = hoverTooltipsEnabled && !previewEnabled && actionsMenu === null;
|
|
394
437
|
|
|
395
438
|
useEffect(() => {
|
|
396
|
-
if (
|
|
439
|
+
if (actionsMenu === null) {
|
|
397
440
|
return;
|
|
398
441
|
}
|
|
399
442
|
|
|
400
443
|
function onPointerDown(event: globalThis.MouseEvent) {
|
|
401
444
|
if (actionsMenuRef.current && !actionsMenuRef.current.contains(event.target as Node)) {
|
|
402
|
-
|
|
445
|
+
setActionsMenu(null);
|
|
403
446
|
}
|
|
404
447
|
}
|
|
405
448
|
|
|
406
449
|
document.addEventListener("mousedown", onPointerDown);
|
|
407
450
|
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
408
|
-
}, [
|
|
451
|
+
}, [actionsMenu]);
|
|
452
|
+
|
|
453
|
+
useEffect(() => {
|
|
454
|
+
if (!toolsDrawerOpen) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function onKeyDown(event: KeyboardEvent) {
|
|
459
|
+
if (event.key === "Escape") {
|
|
460
|
+
setToolsDrawerOpen(false);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
document.addEventListener("keydown", onKeyDown);
|
|
465
|
+
const previousOverflow = document.body.style.overflow;
|
|
466
|
+
document.body.style.overflow = "hidden";
|
|
467
|
+
return () => {
|
|
468
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
469
|
+
document.body.style.overflow = previousOverflow;
|
|
470
|
+
};
|
|
471
|
+
}, [toolsDrawerOpen]);
|
|
409
472
|
|
|
410
473
|
function requestDownload(format: ExportDownloadFormat) {
|
|
411
474
|
if (!onDownload || downloadBusy) {
|
|
@@ -470,6 +533,9 @@ export function RecordWorkspace<T>({
|
|
|
470
533
|
}
|
|
471
534
|
|
|
472
535
|
function showTooltip(item: T, event: MouseEvent) {
|
|
536
|
+
if (!canShowTooltip) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
473
539
|
const padding = 16;
|
|
474
540
|
const width = 280;
|
|
475
541
|
const x = Math.min(event.clientX + 14, window.innerWidth - width - padding);
|
|
@@ -477,6 +543,22 @@ export function RecordWorkspace<T>({
|
|
|
477
543
|
setTooltip({ x, y, lines: detailLines(item) });
|
|
478
544
|
}
|
|
479
545
|
|
|
546
|
+
function openRowActions(item: T, event: MouseEvent<HTMLButtonElement>) {
|
|
547
|
+
event.stopPropagation();
|
|
548
|
+
setTooltip(null);
|
|
549
|
+
const key = rowKey(item);
|
|
550
|
+
if (actionsMenu?.key === key) {
|
|
551
|
+
setActionsMenu(null);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
555
|
+
const menuWidth = 168;
|
|
556
|
+
const menuHeight = 148;
|
|
557
|
+
const x = Math.min(Math.max(8, rect.right - menuWidth), window.innerWidth - menuWidth - 8);
|
|
558
|
+
const y = Math.min(rect.bottom + 6, window.innerHeight - menuHeight - 8);
|
|
559
|
+
setActionsMenu({ key, item, x, y });
|
|
560
|
+
}
|
|
561
|
+
|
|
480
562
|
const summary =
|
|
481
563
|
sorted.length === 0
|
|
482
564
|
? "0 records"
|
|
@@ -484,6 +566,225 @@ export function RecordWorkspace<T>({
|
|
|
484
566
|
? `1–${sorted.length} of ${sorted.length}`
|
|
485
567
|
: `${start + 1}–${Math.min(start + numericPageSize, sorted.length)} of ${sorted.length}`;
|
|
486
568
|
|
|
569
|
+
function renderToolsBody({ compactIcons }: { compactIcons: boolean }) {
|
|
570
|
+
if (compactIcons) {
|
|
571
|
+
return (
|
|
572
|
+
<div className="side-rail-icons">
|
|
573
|
+
<label className="side-rail-icon preview-check-icon" title="Preview">
|
|
574
|
+
<input
|
|
575
|
+
type="checkbox"
|
|
576
|
+
checked={previewEnabled}
|
|
577
|
+
onChange={(event) => setPreviewEnabledValue(event.target.checked)}
|
|
578
|
+
aria-label="Preview"
|
|
579
|
+
/>
|
|
580
|
+
<PreviewIcon />
|
|
581
|
+
</label>
|
|
582
|
+
{showAdd ? (
|
|
583
|
+
<button type="button" className="ghost side-rail-icon" title={addLabel} aria-label={addLabel} onClick={onAdd}>
|
|
584
|
+
<PlusIcon />
|
|
585
|
+
</button>
|
|
586
|
+
) : null}
|
|
587
|
+
{showColumns ? (
|
|
588
|
+
<button
|
|
589
|
+
type="button"
|
|
590
|
+
className="ghost side-rail-icon"
|
|
591
|
+
title="Columns"
|
|
592
|
+
aria-label="Columns"
|
|
593
|
+
onClick={() => {
|
|
594
|
+
setSideOpen(true);
|
|
595
|
+
setColumnsOpen(true);
|
|
596
|
+
}}
|
|
597
|
+
>
|
|
598
|
+
<ColumnsIcon />
|
|
599
|
+
{hiddenColumnCount > 0 ? <span className="filter-badge">{hiddenColumnCount}</span> : null}
|
|
600
|
+
</button>
|
|
601
|
+
) : null}
|
|
602
|
+
{showDownload ? (
|
|
603
|
+
<button
|
|
604
|
+
type="button"
|
|
605
|
+
className="ghost side-rail-icon"
|
|
606
|
+
title="Download"
|
|
607
|
+
aria-label="Download"
|
|
608
|
+
disabled={downloadBusy}
|
|
609
|
+
onClick={() => {
|
|
610
|
+
setSideOpen(true);
|
|
611
|
+
setDownloadOpen(true);
|
|
612
|
+
}}
|
|
613
|
+
>
|
|
614
|
+
<DownloadIcon />
|
|
615
|
+
</button>
|
|
616
|
+
) : null}
|
|
617
|
+
{showFilters ? (
|
|
618
|
+
<button
|
|
619
|
+
type="button"
|
|
620
|
+
className="ghost side-rail-icon"
|
|
621
|
+
title="Show filters"
|
|
622
|
+
aria-label="Show filters"
|
|
623
|
+
onClick={() => setSideOpen(true)}
|
|
624
|
+
>
|
|
625
|
+
<FilterIcon />
|
|
626
|
+
{activeFilterCount > 0 ? <span className="filter-badge">{activeFilterCount}</span> : null}
|
|
627
|
+
</button>
|
|
628
|
+
) : null}
|
|
629
|
+
{showFilters ? (
|
|
630
|
+
<button
|
|
631
|
+
type="button"
|
|
632
|
+
className="ghost side-rail-icon"
|
|
633
|
+
title="Reset filters"
|
|
634
|
+
aria-label="Reset filters"
|
|
635
|
+
disabled={activeFilterCount === 0}
|
|
636
|
+
onClick={clearFilters}
|
|
637
|
+
>
|
|
638
|
+
<ResetIcon />
|
|
639
|
+
</button>
|
|
640
|
+
) : null}
|
|
641
|
+
</div>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return (
|
|
646
|
+
<div className="side-rail-body">
|
|
647
|
+
<div className="preview-check">
|
|
648
|
+
<input
|
|
649
|
+
type="checkbox"
|
|
650
|
+
checked={previewEnabled}
|
|
651
|
+
onChange={(event) => setPreviewEnabledValue(event.target.checked)}
|
|
652
|
+
aria-label="Preview"
|
|
653
|
+
/>
|
|
654
|
+
<PreviewIcon />
|
|
655
|
+
<span aria-hidden="true">Preview</span>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
{showAdd ? (
|
|
659
|
+
<button
|
|
660
|
+
type="button"
|
|
661
|
+
className="side-rail-add"
|
|
662
|
+
onClick={() => {
|
|
663
|
+
setToolsDrawerOpen(false);
|
|
664
|
+
onAdd?.();
|
|
665
|
+
}}
|
|
666
|
+
>
|
|
667
|
+
<PlusIcon />
|
|
668
|
+
<span>{addLabel}</span>
|
|
669
|
+
</button>
|
|
670
|
+
) : null}
|
|
671
|
+
|
|
672
|
+
{showColumns ? (
|
|
673
|
+
<div className="side-rail-columns">
|
|
674
|
+
<button
|
|
675
|
+
type="button"
|
|
676
|
+
className="ghost side-rail-columns-toggle"
|
|
677
|
+
aria-expanded={columnsOpen}
|
|
678
|
+
onClick={() => setColumnsOpen((open) => !open)}
|
|
679
|
+
>
|
|
680
|
+
<ColumnsIcon />
|
|
681
|
+
<span>Columns{hiddenColumnCount > 0 ? ` (${visibleColumns.length}/${columns.length})` : ""}</span>
|
|
682
|
+
</button>
|
|
683
|
+
{columnsOpen ? (
|
|
684
|
+
<div className="side-rail-columns-panel" role="group" aria-label="Visible columns">
|
|
685
|
+
<div className="side-rail-columns-actions">
|
|
686
|
+
<button type="button" className="ghost" onClick={selectAllColumns}>
|
|
687
|
+
Select all
|
|
688
|
+
</button>
|
|
689
|
+
<button type="button" className="ghost" onClick={resetColumns}>
|
|
690
|
+
Reset
|
|
691
|
+
</button>
|
|
692
|
+
</div>
|
|
693
|
+
<div className="side-rail-columns-list">
|
|
694
|
+
{columns.map((column) => {
|
|
695
|
+
const checked = resolvedVisibleKeys.includes(column.key);
|
|
696
|
+
const disableUncheck = checked && resolvedVisibleKeys.length <= 1;
|
|
697
|
+
return (
|
|
698
|
+
<label key={column.key} className="side-rail-column-option">
|
|
699
|
+
<input
|
|
700
|
+
type="checkbox"
|
|
701
|
+
checked={checked}
|
|
702
|
+
disabled={disableUncheck}
|
|
703
|
+
onChange={(event) => toggleColumnVisible(column.key, event.target.checked)}
|
|
704
|
+
/>
|
|
705
|
+
<span>{column.header}</span>
|
|
706
|
+
</label>
|
|
707
|
+
);
|
|
708
|
+
})}
|
|
709
|
+
</div>
|
|
710
|
+
</div>
|
|
711
|
+
) : null}
|
|
712
|
+
</div>
|
|
713
|
+
) : null}
|
|
714
|
+
|
|
715
|
+
{showDownload ? (
|
|
716
|
+
<div className="side-rail-download">
|
|
717
|
+
<button
|
|
718
|
+
type="button"
|
|
719
|
+
className="ghost side-rail-download-toggle"
|
|
720
|
+
aria-expanded={downloadOpen}
|
|
721
|
+
disabled={downloadBusy}
|
|
722
|
+
onClick={() => setDownloadOpen((open) => !open)}
|
|
723
|
+
>
|
|
724
|
+
<DownloadIcon />
|
|
725
|
+
<span>{downloadBusy ? "Downloading…" : "Download"}</span>
|
|
726
|
+
</button>
|
|
727
|
+
{downloadOpen ? (
|
|
728
|
+
<div className="side-rail-download-formats" role="group" aria-label="Download format">
|
|
729
|
+
{EXPORT_FORMAT_OPTIONS.map((option) => (
|
|
730
|
+
<button
|
|
731
|
+
key={option.format}
|
|
732
|
+
type="button"
|
|
733
|
+
className="ghost side-rail-download-format"
|
|
734
|
+
disabled={downloadBusy}
|
|
735
|
+
onClick={() => requestDownload(option.format)}
|
|
736
|
+
>
|
|
737
|
+
{option.label}
|
|
738
|
+
</button>
|
|
739
|
+
))}
|
|
740
|
+
</div>
|
|
741
|
+
) : null}
|
|
742
|
+
</div>
|
|
743
|
+
) : null}
|
|
744
|
+
|
|
745
|
+
{showFilters ? (
|
|
746
|
+
<div className="side-rail-filters">
|
|
747
|
+
<div className="filter-panel-head">
|
|
748
|
+
<h3>Filters</h3>
|
|
749
|
+
<button
|
|
750
|
+
type="button"
|
|
751
|
+
className="ghost filter-clear"
|
|
752
|
+
title="Reset filters"
|
|
753
|
+
aria-label="Reset filters"
|
|
754
|
+
disabled={activeFilterCount === 0}
|
|
755
|
+
onClick={clearFilters}
|
|
756
|
+
>
|
|
757
|
+
<ResetIcon />
|
|
758
|
+
<span>Reset</span>
|
|
759
|
+
</button>
|
|
760
|
+
</div>
|
|
761
|
+
<div className="filter-panel-stack">
|
|
762
|
+
{filterableColumns.map((column) => (
|
|
763
|
+
<label key={column.key}>
|
|
764
|
+
{column.header}
|
|
765
|
+
{column.filterOptions ? (
|
|
766
|
+
<Select
|
|
767
|
+
value={filters[column.key] ?? ""}
|
|
768
|
+
options={[{ value: "", label: column.filterPlaceholder ?? "All" }, ...column.filterOptions]}
|
|
769
|
+
onChange={(value) => changeFilter(column.key, value)}
|
|
770
|
+
placeholder={column.filterPlaceholder ?? "All"}
|
|
771
|
+
/>
|
|
772
|
+
) : (
|
|
773
|
+
<input
|
|
774
|
+
value={filters[column.key] ?? ""}
|
|
775
|
+
onChange={(event) => changeFilter(column.key, event.target.value)}
|
|
776
|
+
placeholder={`Filter ${column.header.toLowerCase()}`}
|
|
777
|
+
/>
|
|
778
|
+
)}
|
|
779
|
+
</label>
|
|
780
|
+
))}
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
) : null}
|
|
784
|
+
</div>
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
487
788
|
function renderRowActions(item: T) {
|
|
488
789
|
const key = rowKey(item);
|
|
489
790
|
const updateAllowed = canUpdate ? canUpdate(item) : Boolean(onUpdate);
|
|
@@ -525,69 +826,60 @@ export function RecordWorkspace<T>({
|
|
|
525
826
|
) : null}
|
|
526
827
|
</div>
|
|
527
828
|
|
|
528
|
-
<div className="row-actions row-actions-mobile"
|
|
829
|
+
<div className="row-actions row-actions-mobile">
|
|
529
830
|
<button
|
|
530
831
|
type="button"
|
|
531
|
-
className="ghost row-action-menu-trigger"
|
|
832
|
+
className={actionsMenu?.key === key ? "ghost row-action-menu-trigger open" : "ghost row-action-menu-trigger"}
|
|
532
833
|
aria-haspopup="menu"
|
|
533
|
-
aria-expanded={
|
|
834
|
+
aria-expanded={actionsMenu?.key === key}
|
|
534
835
|
aria-label="Row actions"
|
|
535
|
-
onClick={() =>
|
|
836
|
+
onClick={(event) => openRowActions(item, event)}
|
|
536
837
|
>
|
|
537
838
|
<MoreIcon />
|
|
538
839
|
</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
840
|
</div>
|
|
585
841
|
</>
|
|
586
842
|
);
|
|
587
843
|
}
|
|
588
844
|
|
|
845
|
+
const actionsMenuItem = actionsMenu?.item;
|
|
846
|
+
const actionsUpdateAllowed = actionsMenuItem
|
|
847
|
+
? canUpdate
|
|
848
|
+
? canUpdate(actionsMenuItem)
|
|
849
|
+
: Boolean(onUpdate)
|
|
850
|
+
: false;
|
|
851
|
+
const actionsDeleteAllowed = actionsMenuItem
|
|
852
|
+
? canDelete
|
|
853
|
+
? canDelete(actionsMenuItem)
|
|
854
|
+
: Boolean(onDelete)
|
|
855
|
+
: false;
|
|
856
|
+
|
|
589
857
|
return (
|
|
590
|
-
<div className="list-workspace" aria-label={title}>
|
|
858
|
+
<div className={toolsDrawerOpen ? "list-workspace tools-drawer-open" : "list-workspace"} aria-label={title}>
|
|
859
|
+
<div className="tools-mobile-bar">
|
|
860
|
+
<div className="tools-mobile-bar-start">
|
|
861
|
+
{showAdd ? (
|
|
862
|
+
<button type="button" className="tools-mobile-add" onClick={onAdd}>
|
|
863
|
+
<PlusIcon />
|
|
864
|
+
<span>{addLabel}</span>
|
|
865
|
+
</button>
|
|
866
|
+
) : (
|
|
867
|
+
<p className="tools-mobile-title">{title}</p>
|
|
868
|
+
)}
|
|
869
|
+
</div>
|
|
870
|
+
<button
|
|
871
|
+
type="button"
|
|
872
|
+
className="ghost tools-mobile-toggle"
|
|
873
|
+
aria-expanded={toolsDrawerOpen}
|
|
874
|
+
aria-controls="aksui-tools-drawer"
|
|
875
|
+
onClick={() => setToolsDrawerOpen(true)}
|
|
876
|
+
>
|
|
877
|
+
<ToolsIcon />
|
|
878
|
+
<span>Tools</span>
|
|
879
|
+
{toolsBadgeCount > 0 ? <span className="filter-badge">{toolsBadgeCount}</span> : null}
|
|
880
|
+
</button>
|
|
881
|
+
</div>
|
|
882
|
+
|
|
591
883
|
<div
|
|
592
884
|
className={["list-body", sideOpen ? "side-open" : "", previewEnabled ? "preview-open" : ""]
|
|
593
885
|
.filter(Boolean)
|
|
@@ -651,16 +943,10 @@ export function RecordWorkspace<T>({
|
|
|
651
943
|
key={key}
|
|
652
944
|
className={selected ? "data-row selected" : "data-row"}
|
|
653
945
|
onMouseEnter={(event) => {
|
|
654
|
-
|
|
655
|
-
showTooltip(item, event);
|
|
656
|
-
}
|
|
946
|
+
showTooltip(item, event);
|
|
657
947
|
focusRecord(item);
|
|
658
948
|
}}
|
|
659
|
-
onMouseMove={(event) =>
|
|
660
|
-
if (!previewEnabled) {
|
|
661
|
-
showTooltip(item, event);
|
|
662
|
-
}
|
|
663
|
-
}}
|
|
949
|
+
onMouseMove={(event) => showTooltip(item, event)}
|
|
664
950
|
onMouseLeave={() => setTooltip(null)}
|
|
665
951
|
onClick={() => focusRecord(item)}
|
|
666
952
|
>
|
|
@@ -704,7 +990,7 @@ export function RecordWorkspace<T>({
|
|
|
704
990
|
{previewItem ? (
|
|
705
991
|
<DetailList lines={detailLines(previewItem)} />
|
|
706
992
|
) : (
|
|
707
|
-
<p className="preview-empty">
|
|
993
|
+
<p className="preview-empty">Select a row to preview its details here.</p>
|
|
708
994
|
)}
|
|
709
995
|
</div>
|
|
710
996
|
</aside>
|
|
@@ -721,219 +1007,99 @@ export function RecordWorkspace<T>({
|
|
|
721
1007
|
<ChevronIcon open={sideOpen} />
|
|
722
1008
|
{sideOpen ? <span>Collapse</span> : null}
|
|
723
1009
|
</button>
|
|
1010
|
+
{sideOpen ? renderToolsBody({ compactIcons: false }) : renderToolsBody({ compactIcons: true })}
|
|
1011
|
+
</aside>
|
|
1012
|
+
</div>
|
|
724
1013
|
|
|
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>
|
|
1014
|
+
{isClient
|
|
1015
|
+
? createPortal(
|
|
1016
|
+
<>
|
|
1017
|
+
<button
|
|
1018
|
+
type="button"
|
|
1019
|
+
className={toolsDrawerOpen ? "tools-drawer-backdrop open" : "tools-drawer-backdrop"}
|
|
1020
|
+
aria-label="Close tools"
|
|
1021
|
+
tabIndex={toolsDrawerOpen ? 0 : -1}
|
|
1022
|
+
onClick={() => setToolsDrawerOpen(false)}
|
|
1023
|
+
/>
|
|
1024
|
+
<aside
|
|
1025
|
+
id="aksui-tools-drawer"
|
|
1026
|
+
className={toolsDrawerOpen ? "tools-drawer open" : "tools-drawer"}
|
|
1027
|
+
aria-label="Workspace tools"
|
|
1028
|
+
aria-hidden={!toolsDrawerOpen}
|
|
1029
|
+
>
|
|
1030
|
+
<div className="tools-drawer-head">
|
|
1031
|
+
<div>
|
|
1032
|
+
<p className="tools-drawer-kicker">Workspace</p>
|
|
1033
|
+
<h3>Tools</h3>
|
|
1034
|
+
</div>
|
|
1035
|
+
<button type="button" className="ghost tools-drawer-close" onClick={() => setToolsDrawerOpen(false)}>
|
|
1036
|
+
Close
|
|
755
1037
|
</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
1038
|
</div>
|
|
786
|
-
|
|
1039
|
+
<div className="tools-drawer-body">{renderToolsBody({ compactIcons: false })}</div>
|
|
1040
|
+
</aside>
|
|
1041
|
+
</>,
|
|
1042
|
+
document.body,
|
|
1043
|
+
)
|
|
1044
|
+
: null}
|
|
787
1045
|
|
|
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}
|
|
1046
|
+
{form}
|
|
817
1047
|
|
|
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 ? (
|
|
1048
|
+
{isClient && actionsMenu && actionsMenuItem
|
|
1049
|
+
? createPortal(
|
|
1050
|
+
<div
|
|
1051
|
+
ref={actionsMenuRef}
|
|
1052
|
+
className="row-action-menu row-action-menu-portal"
|
|
1053
|
+
role="menu"
|
|
1054
|
+
style={{ left: actionsMenu.x, top: actionsMenu.y }}
|
|
1055
|
+
>
|
|
1056
|
+
{onView ? (
|
|
875
1057
|
<button
|
|
876
1058
|
type="button"
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
aria-label="Columns"
|
|
1059
|
+
role="menuitem"
|
|
1060
|
+
className="ghost"
|
|
880
1061
|
onClick={() => {
|
|
881
|
-
|
|
882
|
-
|
|
1062
|
+
setActionsMenu(null);
|
|
1063
|
+
onView(actionsMenuItem);
|
|
883
1064
|
}}
|
|
884
1065
|
>
|
|
885
|
-
|
|
886
|
-
{hiddenColumnCount > 0 ? <span className="filter-badge">{hiddenColumnCount}</span> : null}
|
|
1066
|
+
View
|
|
887
1067
|
</button>
|
|
888
1068
|
) : null}
|
|
889
|
-
{
|
|
1069
|
+
{onUpdate ? (
|
|
890
1070
|
<button
|
|
891
1071
|
type="button"
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
aria-expanded={downloadOpen}
|
|
896
|
-
disabled={downloadBusy}
|
|
1072
|
+
role="menuitem"
|
|
1073
|
+
className="ghost"
|
|
1074
|
+
disabled={!actionsUpdateAllowed}
|
|
897
1075
|
onClick={() => {
|
|
898
|
-
|
|
899
|
-
|
|
1076
|
+
setActionsMenu(null);
|
|
1077
|
+
onUpdate(actionsMenuItem);
|
|
900
1078
|
}}
|
|
901
1079
|
>
|
|
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}
|
|
1080
|
+
Update
|
|
915
1081
|
</button>
|
|
916
1082
|
) : null}
|
|
917
|
-
{
|
|
1083
|
+
{onDelete ? (
|
|
918
1084
|
<button
|
|
919
1085
|
type="button"
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1086
|
+
role="menuitem"
|
|
1087
|
+
className="ghost danger"
|
|
1088
|
+
disabled={!actionsDeleteAllowed}
|
|
1089
|
+
onClick={() => {
|
|
1090
|
+
setActionsMenu(null);
|
|
1091
|
+
onDelete(actionsMenuItem);
|
|
1092
|
+
}}
|
|
925
1093
|
>
|
|
926
|
-
|
|
1094
|
+
Delete
|
|
927
1095
|
</button>
|
|
928
1096
|
) : null}
|
|
929
|
-
</div
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
{form}
|
|
1097
|
+
</div>,
|
|
1098
|
+
document.body,
|
|
1099
|
+
)
|
|
1100
|
+
: null}
|
|
935
1101
|
|
|
936
|
-
{isClient && tooltip &&
|
|
1102
|
+
{isClient && tooltip && canShowTooltip
|
|
937
1103
|
? createPortal(
|
|
938
1104
|
<div className="record-tooltip" style={{ left: tooltip.x, top: tooltip.y }} role="tooltip">
|
|
939
1105
|
<p className="record-tooltip-title">{title}</p>
|