@mshuja288/aksuicore 0.3.0 → 0.4.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/CHANGELOG.md +13 -0
- package/dist/aksui.css +327 -6
- package/dist/index.cjs +430 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -9
- package/dist/index.d.ts +49 -9
- package/dist/index.js +430 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/patterns/AppShell.tsx +140 -17
- package/src/patterns/RecordWorkspace.tsx +347 -46
- package/src/patterns/index.ts +2 -1
- package/src/primitives/SideNav.tsx +3 -1
- package/src/primitives/Topbar.tsx +38 -8
- package/src/theme/patterns.css +164 -0
- package/src/theme/primitives.css +9 -1
- package/src/theme/shell.css +154 -5
package/dist/index.cjs
CHANGED
|
@@ -517,31 +517,48 @@ function Snackbar({
|
|
|
517
517
|
);
|
|
518
518
|
}
|
|
519
519
|
var StatusMessage = Snackbar;
|
|
520
|
+
function MenuIcon({ open }) {
|
|
521
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 20 20", "aria-hidden": "true", children: open ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 5l10 10M15 5 5 15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }) : /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.5 5.5h13M3.5 10h13M3.5 14.5h13", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }) });
|
|
522
|
+
}
|
|
520
523
|
function Topbar({
|
|
521
524
|
title,
|
|
522
525
|
logoSrc,
|
|
523
526
|
logoAlt = "Logo",
|
|
524
527
|
username = "",
|
|
525
528
|
onLogout,
|
|
529
|
+
showMenuButton = false,
|
|
530
|
+
menuOpen = false,
|
|
531
|
+
onMenuClick,
|
|
526
532
|
children
|
|
527
533
|
}) {
|
|
528
|
-
const [
|
|
534
|
+
const [accountOpen, setAccountOpen] = react.useState(false);
|
|
529
535
|
const menuRef = react.useRef(null);
|
|
530
536
|
const initial = (username || "U").slice(0, 1).toUpperCase();
|
|
531
537
|
react.useEffect(() => {
|
|
532
|
-
if (!
|
|
538
|
+
if (!accountOpen) {
|
|
533
539
|
return;
|
|
534
540
|
}
|
|
535
541
|
function onPointerDown(event) {
|
|
536
542
|
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
537
|
-
|
|
543
|
+
setAccountOpen(false);
|
|
538
544
|
}
|
|
539
545
|
}
|
|
540
546
|
document.addEventListener("mousedown", onPointerDown);
|
|
541
547
|
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
542
|
-
}, [
|
|
548
|
+
}, [accountOpen]);
|
|
543
549
|
return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "topbar", children: [
|
|
544
550
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "topbar-brand", children: [
|
|
551
|
+
showMenuButton && onMenuClick ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
552
|
+
"button",
|
|
553
|
+
{
|
|
554
|
+
type: "button",
|
|
555
|
+
className: "ghost topbar-menu-button",
|
|
556
|
+
"aria-label": menuOpen ? "Close navigation menu" : "Open navigation menu",
|
|
557
|
+
"aria-expanded": menuOpen,
|
|
558
|
+
onClick: onMenuClick,
|
|
559
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(MenuIcon, { open: menuOpen })
|
|
560
|
+
}
|
|
561
|
+
) : null,
|
|
545
562
|
logoSrc ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
546
563
|
"img",
|
|
547
564
|
{
|
|
@@ -564,13 +581,13 @@ function Topbar({
|
|
|
564
581
|
type: "button",
|
|
565
582
|
className: "avatar-button",
|
|
566
583
|
"aria-haspopup": "menu",
|
|
567
|
-
"aria-expanded":
|
|
568
|
-
onClick: () =>
|
|
584
|
+
"aria-expanded": accountOpen,
|
|
585
|
+
onClick: () => setAccountOpen((open) => !open),
|
|
569
586
|
title: username || "Account",
|
|
570
587
|
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "avatar", children: initial })
|
|
571
588
|
}
|
|
572
589
|
),
|
|
573
|
-
|
|
590
|
+
accountOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "avatar-menu", role: "menu", children: [
|
|
574
591
|
username ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "avatar-name", children: username }) : null,
|
|
575
592
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
576
593
|
Button,
|
|
@@ -578,7 +595,7 @@ function Topbar({
|
|
|
578
595
|
variant: "ghost",
|
|
579
596
|
role: "menuitem",
|
|
580
597
|
onClick: () => {
|
|
581
|
-
|
|
598
|
+
setAccountOpen(false);
|
|
582
599
|
onLogout();
|
|
583
600
|
},
|
|
584
601
|
children: "Logout"
|
|
@@ -593,9 +610,10 @@ function SideNav({
|
|
|
593
610
|
items,
|
|
594
611
|
activeId,
|
|
595
612
|
onSelect,
|
|
596
|
-
label = "Sections"
|
|
613
|
+
label = "Sections",
|
|
614
|
+
className = ""
|
|
597
615
|
}) {
|
|
598
|
-
return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "side-nav", "aria-label": label, children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
616
|
+
return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: ["side-nav", className].filter(Boolean).join(" "), "aria-label": label, children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
599
617
|
"button",
|
|
600
618
|
{
|
|
601
619
|
type: "button",
|
|
@@ -697,28 +715,131 @@ function AppShell({
|
|
|
697
715
|
activeNavId,
|
|
698
716
|
onNavSelect,
|
|
699
717
|
navLabel = "Sections",
|
|
700
|
-
|
|
718
|
+
mobileNav = "drawer",
|
|
719
|
+
showBottomNav,
|
|
720
|
+
navDrawerOpen,
|
|
721
|
+
onNavDrawerOpenChange,
|
|
701
722
|
topbarActions,
|
|
702
723
|
children,
|
|
703
724
|
screenClassName = ""
|
|
704
725
|
}) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
726
|
+
const [internalDrawerOpen, setInternalDrawerOpen] = react.useState(false);
|
|
727
|
+
const drawerControlled = navDrawerOpen !== void 0;
|
|
728
|
+
const drawerOpen = drawerControlled ? navDrawerOpen : internalDrawerOpen;
|
|
729
|
+
const useBottomNav = showBottomNav ?? (mobileNav === "bottom" || mobileNav === "hybrid");
|
|
730
|
+
const useDrawer = mobileNav === "drawer" || mobileNav === "hybrid";
|
|
731
|
+
const activeItem = navItems.find((item) => item.id === activeNavId);
|
|
732
|
+
function setDrawerOpen(open) {
|
|
733
|
+
if (!drawerControlled) {
|
|
734
|
+
setInternalDrawerOpen(open);
|
|
735
|
+
}
|
|
736
|
+
onNavDrawerOpenChange?.(open);
|
|
737
|
+
}
|
|
738
|
+
react.useEffect(() => {
|
|
739
|
+
if (!drawerOpen) {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
function onKeyDown(event) {
|
|
743
|
+
if (event.key === "Escape") {
|
|
744
|
+
if (!drawerControlled) {
|
|
745
|
+
setInternalDrawerOpen(false);
|
|
746
|
+
}
|
|
747
|
+
onNavDrawerOpenChange?.(false);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
document.addEventListener("keydown", onKeyDown);
|
|
751
|
+
const previousOverflow = document.body.style.overflow;
|
|
752
|
+
document.body.style.overflow = "hidden";
|
|
753
|
+
return () => {
|
|
754
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
755
|
+
document.body.style.overflow = previousOverflow;
|
|
756
|
+
};
|
|
757
|
+
}, [drawerControlled, drawerOpen, onNavDrawerOpenChange]);
|
|
758
|
+
function selectNav(id) {
|
|
759
|
+
onNavSelect(id);
|
|
760
|
+
setDrawerOpen(false);
|
|
761
|
+
}
|
|
762
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
763
|
+
"div",
|
|
764
|
+
{
|
|
765
|
+
className: [
|
|
766
|
+
"aksui-root",
|
|
767
|
+
"app-shell",
|
|
768
|
+
`mobile-nav-${mobileNav}`,
|
|
769
|
+
useBottomNav ? "has-bottom-nav" : "",
|
|
770
|
+
drawerOpen ? "nav-drawer-open" : ""
|
|
771
|
+
].filter(Boolean).join(" "),
|
|
772
|
+
children: [
|
|
773
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
774
|
+
Topbar,
|
|
775
|
+
{
|
|
776
|
+
title,
|
|
777
|
+
logoSrc,
|
|
778
|
+
logoAlt,
|
|
779
|
+
username,
|
|
780
|
+
onLogout,
|
|
781
|
+
showMenuButton: useDrawer,
|
|
782
|
+
menuOpen: drawerOpen,
|
|
783
|
+
onMenuClick: () => setDrawerOpen(!drawerOpen),
|
|
784
|
+
children: topbarActions
|
|
785
|
+
}
|
|
786
|
+
),
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "app-body", children: [
|
|
788
|
+
/* @__PURE__ */ jsxRuntime.jsx(SideNav, { items: navItems, activeId: activeNavId, onSelect: onNavSelect, label: navLabel }),
|
|
789
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: ["screen", screenClassName].filter(Boolean).join(" "), children })
|
|
790
|
+
] }),
|
|
791
|
+
useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
792
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
793
|
+
"button",
|
|
794
|
+
{
|
|
795
|
+
type: "button",
|
|
796
|
+
className: drawerOpen ? "nav-drawer-backdrop open" : "nav-drawer-backdrop",
|
|
797
|
+
"aria-label": "Close navigation menu",
|
|
798
|
+
tabIndex: drawerOpen ? 0 : -1,
|
|
799
|
+
onClick: () => setDrawerOpen(false)
|
|
800
|
+
}
|
|
801
|
+
),
|
|
802
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
803
|
+
"aside",
|
|
804
|
+
{
|
|
805
|
+
className: drawerOpen ? "nav-drawer open" : "nav-drawer",
|
|
806
|
+
"aria-label": navLabel,
|
|
807
|
+
"aria-hidden": !drawerOpen,
|
|
808
|
+
children: [
|
|
809
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nav-drawer-head", children: [
|
|
810
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "nav-drawer-title", children: navLabel }),
|
|
811
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ghost nav-drawer-close", onClick: () => setDrawerOpen(false), children: "Close" })
|
|
812
|
+
] }),
|
|
813
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
814
|
+
SideNav,
|
|
815
|
+
{
|
|
816
|
+
className: "nav-drawer-nav",
|
|
817
|
+
items: navItems,
|
|
818
|
+
activeId: activeNavId,
|
|
819
|
+
onSelect: selectNav,
|
|
820
|
+
label: navLabel
|
|
821
|
+
}
|
|
822
|
+
)
|
|
823
|
+
]
|
|
824
|
+
}
|
|
825
|
+
)
|
|
826
|
+
] }) : null,
|
|
827
|
+
useBottomNav ? /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "bottom-nav", "aria-label": navLabel, children: mobileNav === "hybrid" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
828
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "active bottom-nav-current", disabled: true, children: activeItem?.short ?? activeItem?.label ?? "Section" }),
|
|
829
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "bottom-nav-menu", onClick: () => setDrawerOpen(true), children: "Menu" })
|
|
830
|
+
] }) : navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
831
|
+
"button",
|
|
832
|
+
{
|
|
833
|
+
type: "button",
|
|
834
|
+
className: activeNavId === item.id ? "active" : "",
|
|
835
|
+
onClick: () => onNavSelect(item.id),
|
|
836
|
+
children: item.short ?? item.label
|
|
837
|
+
},
|
|
838
|
+
item.id
|
|
839
|
+
)) }) : null
|
|
840
|
+
]
|
|
841
|
+
}
|
|
842
|
+
);
|
|
722
843
|
}
|
|
723
844
|
function useIsClient2() {
|
|
724
845
|
return react.useSyncExternalStore(
|
|
@@ -733,6 +854,37 @@ var EXPORT_FORMAT_OPTIONS = [
|
|
|
733
854
|
{ format: "xlsx", label: "Excel" },
|
|
734
855
|
{ format: "json", label: "JSON" }
|
|
735
856
|
];
|
|
857
|
+
function defaultVisibleKeys(columns) {
|
|
858
|
+
return columns.filter((column) => column.defaultVisible !== false).map((column) => column.key);
|
|
859
|
+
}
|
|
860
|
+
function readStoredColumnKeys(prefsKey, fallback, allowed) {
|
|
861
|
+
if (!prefsKey || typeof window === "undefined") {
|
|
862
|
+
return fallback;
|
|
863
|
+
}
|
|
864
|
+
try {
|
|
865
|
+
const raw = window.localStorage.getItem(prefsKey);
|
|
866
|
+
if (!raw) {
|
|
867
|
+
return fallback;
|
|
868
|
+
}
|
|
869
|
+
const parsed = JSON.parse(raw);
|
|
870
|
+
if (!Array.isArray(parsed)) {
|
|
871
|
+
return fallback;
|
|
872
|
+
}
|
|
873
|
+
const keys = parsed.filter((entry) => typeof entry === "string" && allowed.has(entry));
|
|
874
|
+
return keys.length > 0 ? keys : fallback;
|
|
875
|
+
} catch {
|
|
876
|
+
return fallback;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function writeStoredColumnKeys(prefsKey, keys) {
|
|
880
|
+
if (!prefsKey || typeof window === "undefined") {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
window.localStorage.setItem(prefsKey, JSON.stringify(keys));
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
}
|
|
736
888
|
function compareColumnValues(column, left, right) {
|
|
737
889
|
const leftRaw = column.display ? column.display(left) : column.value(left);
|
|
738
890
|
const rightRaw = column.display ? column.display(right) : column.value(right);
|
|
@@ -766,6 +918,9 @@ function ResetIcon() {
|
|
|
766
918
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4.5 4.2v4h4", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
767
919
|
] });
|
|
768
920
|
}
|
|
921
|
+
function ColumnsIcon() {
|
|
922
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 20 20", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4.5h3.5v11H4zM8.25 4.5h3.5v11h-3.5zM12.5 4.5H16v11h-3.5z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }) });
|
|
923
|
+
}
|
|
769
924
|
function ChevronIcon({ open }) {
|
|
770
925
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: open ? "side-rail-chevron open" : "side-rail-chevron", viewBox: "0 0 20 20", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 4.5 13.5 10 8 15.5", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
771
926
|
}
|
|
@@ -782,6 +937,13 @@ function DownloadIcon() {
|
|
|
782
937
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 15.5h12", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" })
|
|
783
938
|
] });
|
|
784
939
|
}
|
|
940
|
+
function MoreIcon() {
|
|
941
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 20 20", "aria-hidden": "true", children: [
|
|
942
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "10", r: "1.5", fill: "currentColor" }),
|
|
943
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "10", cy: "10", r: "1.5", fill: "currentColor" }),
|
|
944
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15.5", cy: "10", r: "1.5", fill: "currentColor" })
|
|
945
|
+
] });
|
|
946
|
+
}
|
|
785
947
|
function DetailList({ lines }) {
|
|
786
948
|
return /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "record-detail-list", children: lines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "record-detail-row", children: [
|
|
787
949
|
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: line.label }),
|
|
@@ -805,24 +967,83 @@ function RecordWorkspace({
|
|
|
805
967
|
onDownload,
|
|
806
968
|
canDownload = true,
|
|
807
969
|
downloadBusy = false,
|
|
970
|
+
showColumnPicker = true,
|
|
971
|
+
columnPrefsKey,
|
|
972
|
+
visibleColumnKeys,
|
|
973
|
+
defaultVisibleColumnKeys,
|
|
974
|
+
onVisibleColumnKeysChange,
|
|
808
975
|
toolbar,
|
|
809
976
|
form
|
|
810
977
|
}) {
|
|
811
|
-
const
|
|
978
|
+
const allowedKeys = react.useMemo(() => new Set(columns.map((column) => column.key)), [columns]);
|
|
979
|
+
const fallbackKeys = react.useMemo(
|
|
980
|
+
() => defaultVisibleColumnKeys ?? defaultVisibleKeys(columns),
|
|
981
|
+
[columns, defaultVisibleColumnKeys]
|
|
982
|
+
);
|
|
983
|
+
const isControlled = visibleColumnKeys !== void 0;
|
|
984
|
+
const [internalVisibleKeys, setInternalVisibleKeys] = react.useState(
|
|
985
|
+
() => readStoredColumnKeys(columnPrefsKey, fallbackKeys, allowedKeys)
|
|
986
|
+
);
|
|
812
987
|
const [filters, setFilters] = react.useState({});
|
|
813
988
|
const [sort, setSort] = react.useState(null);
|
|
814
989
|
const [sideOpen, setSideOpen] = react.useState(false);
|
|
815
990
|
const [downloadOpen, setDownloadOpen] = react.useState(false);
|
|
991
|
+
const [columnsOpen, setColumnsOpen] = react.useState(false);
|
|
816
992
|
const [previewEnabled, setPreviewEnabled] = react.useState(false);
|
|
817
993
|
const [previewKey, setPreviewKey] = react.useState(null);
|
|
818
994
|
const [tooltip, setTooltip] = react.useState(null);
|
|
819
995
|
const [pageSize, setPageSize] = react.useState("15");
|
|
820
996
|
const [page, setPage] = react.useState(1);
|
|
997
|
+
const [actionsMenuKey, setActionsMenuKey] = react.useState(null);
|
|
998
|
+
const actionsMenuRef = react.useRef(null);
|
|
821
999
|
const isClient = useIsClient2();
|
|
1000
|
+
react.useEffect(() => {
|
|
1001
|
+
if (isControlled) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
setInternalVisibleKeys(readStoredColumnKeys(columnPrefsKey, fallbackKeys, allowedKeys));
|
|
1005
|
+
}, [allowedKeys, columnPrefsKey, fallbackKeys, isControlled]);
|
|
1006
|
+
const resolvedVisibleKeys = react.useMemo(() => {
|
|
1007
|
+
const source = isControlled ? visibleColumnKeys : internalVisibleKeys;
|
|
1008
|
+
const filtered2 = source.filter((key) => allowedKeys.has(key));
|
|
1009
|
+
return filtered2.length > 0 ? filtered2 : fallbackKeys.filter((key) => allowedKeys.has(key));
|
|
1010
|
+
}, [allowedKeys, fallbackKeys, internalVisibleKeys, isControlled, visibleColumnKeys]);
|
|
1011
|
+
const visibleColumns = react.useMemo(() => {
|
|
1012
|
+
const byKey = new Map(columns.map((column) => [column.key, column]));
|
|
1013
|
+
return resolvedVisibleKeys.map((key) => byKey.get(key)).filter((column) => Boolean(column));
|
|
1014
|
+
}, [columns, resolvedVisibleKeys]);
|
|
1015
|
+
function setVisibleKeys(next) {
|
|
1016
|
+
const unique = Array.from(new Set(next.filter((key) => allowedKeys.has(key))));
|
|
1017
|
+
const safe = unique.length > 0 ? unique : fallbackKeys.filter((key) => allowedKeys.has(key));
|
|
1018
|
+
if (!isControlled) {
|
|
1019
|
+
setInternalVisibleKeys(safe);
|
|
1020
|
+
writeStoredColumnKeys(columnPrefsKey, safe);
|
|
1021
|
+
}
|
|
1022
|
+
onVisibleColumnKeysChange?.(safe);
|
|
1023
|
+
}
|
|
1024
|
+
function toggleColumnVisible(key, checked) {
|
|
1025
|
+
if (checked) {
|
|
1026
|
+
if (resolvedVisibleKeys.includes(key)) {
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
setVisibleKeys([...resolvedVisibleKeys, key]);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (resolvedVisibleKeys.length <= 1) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
setVisibleKeys(resolvedVisibleKeys.filter((entry) => entry !== key));
|
|
1036
|
+
}
|
|
1037
|
+
function selectAllColumns() {
|
|
1038
|
+
setVisibleKeys(columns.map((column) => column.key));
|
|
1039
|
+
}
|
|
1040
|
+
function resetColumns() {
|
|
1041
|
+
setVisibleKeys(fallbackKeys);
|
|
1042
|
+
}
|
|
1043
|
+
const filterableColumns = visibleColumns.filter((column) => column.filterable !== false);
|
|
822
1044
|
const filtered = react.useMemo(() => {
|
|
823
|
-
const activeFilters = columns.filter((column) => column.filterable !== false);
|
|
824
1045
|
return items.filter(
|
|
825
|
-
(item) =>
|
|
1046
|
+
(item) => filterableColumns.every((column) => {
|
|
826
1047
|
const query = (filters[column.key] ?? "").trim();
|
|
827
1048
|
if (!query) {
|
|
828
1049
|
return true;
|
|
@@ -835,22 +1056,23 @@ function RecordWorkspace({
|
|
|
835
1056
|
return text.toLowerCase().includes(query.toLowerCase());
|
|
836
1057
|
})
|
|
837
1058
|
);
|
|
838
|
-
}, [items, filters,
|
|
1059
|
+
}, [items, filters, filterableColumns]);
|
|
839
1060
|
const sorted = react.useMemo(() => {
|
|
840
1061
|
if (!sort) {
|
|
841
1062
|
return filtered;
|
|
842
1063
|
}
|
|
843
|
-
const column =
|
|
1064
|
+
const column = visibleColumns.find((entry) => entry.key === sort.key);
|
|
844
1065
|
if (!column || column.sortable === false) {
|
|
845
1066
|
return filtered;
|
|
846
1067
|
}
|
|
847
1068
|
const direction = sort.direction === "asc" ? 1 : -1;
|
|
848
1069
|
return [...filtered].sort((left, right) => compareColumnValues(column, left, right) * direction);
|
|
849
|
-
}, [filtered, sort,
|
|
1070
|
+
}, [filtered, sort, visibleColumns]);
|
|
850
1071
|
const activeFilterCount = react.useMemo(
|
|
851
1072
|
() => filterableColumns.reduce((count, column) => count + ((filters[column.key] ?? "").trim() ? 1 : 0), 0),
|
|
852
1073
|
[filterableColumns, filters]
|
|
853
1074
|
);
|
|
1075
|
+
const hiddenColumnCount = columns.length - visibleColumns.length;
|
|
854
1076
|
const showAll = pageSize === "all";
|
|
855
1077
|
const numericPageSize = showAll ? Math.max(sorted.length, 1) : Number(pageSize);
|
|
856
1078
|
const pageCount = showAll ? 1 : Math.max(1, Math.ceil(sorted.length / numericPageSize));
|
|
@@ -860,12 +1082,29 @@ function RecordWorkspace({
|
|
|
860
1082
|
const showAdd = canAdd && Boolean(onAdd);
|
|
861
1083
|
const showDownload = Boolean(onDownload) && canDownload;
|
|
862
1084
|
const showFilters = filterableColumns.length > 0;
|
|
1085
|
+
const showColumns = showColumnPicker && columns.length > 0;
|
|
863
1086
|
const previewItem = previewKey === null ? null : sorted.find((item) => rowKey(item) === previewKey) ?? null;
|
|
1087
|
+
const exportColumns = visibleColumns.map((column) => ({
|
|
1088
|
+
key: column.exportKey ?? column.key,
|
|
1089
|
+
header: column.header
|
|
1090
|
+
}));
|
|
1091
|
+
react.useEffect(() => {
|
|
1092
|
+
if (actionsMenuKey === null) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
function onPointerDown(event) {
|
|
1096
|
+
if (actionsMenuRef.current && !actionsMenuRef.current.contains(event.target)) {
|
|
1097
|
+
setActionsMenuKey(null);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
document.addEventListener("mousedown", onPointerDown);
|
|
1101
|
+
return () => document.removeEventListener("mousedown", onPointerDown);
|
|
1102
|
+
}, [actionsMenuKey]);
|
|
864
1103
|
function requestDownload(format) {
|
|
865
1104
|
if (!onDownload || downloadBusy) {
|
|
866
1105
|
return;
|
|
867
1106
|
}
|
|
868
|
-
onDownload(format);
|
|
1107
|
+
onDownload(format, exportColumns);
|
|
869
1108
|
setDownloadOpen(false);
|
|
870
1109
|
}
|
|
871
1110
|
function cellText(column, item) {
|
|
@@ -873,12 +1112,12 @@ function RecordWorkspace({
|
|
|
873
1112
|
return raw === null || raw === void 0 || raw === "" ? "\u2014" : String(raw);
|
|
874
1113
|
}
|
|
875
1114
|
function detailLines(item) {
|
|
876
|
-
return
|
|
1115
|
+
return visibleColumns.map((column) => ({
|
|
877
1116
|
label: column.header,
|
|
878
1117
|
value: cellText(column, item)
|
|
879
1118
|
}));
|
|
880
1119
|
}
|
|
881
|
-
const previewTitle = previewItem &&
|
|
1120
|
+
const previewTitle = previewItem && visibleColumns[0] ? cellText(visibleColumns[0], previewItem) : null;
|
|
882
1121
|
function changeFilter(key, value) {
|
|
883
1122
|
setFilters((current) => ({ ...current, [key]: value }));
|
|
884
1123
|
setPage(1);
|
|
@@ -922,6 +1161,99 @@ function RecordWorkspace({
|
|
|
922
1161
|
setTooltip({ x, y, lines: detailLines(item) });
|
|
923
1162
|
}
|
|
924
1163
|
const summary = sorted.length === 0 ? "0 records" : showAll ? `1\u2013${sorted.length} of ${sorted.length}` : `${start + 1}\u2013${Math.min(start + numericPageSize, sorted.length)} of ${sorted.length}`;
|
|
1164
|
+
function renderRowActions(item) {
|
|
1165
|
+
const key = rowKey(item);
|
|
1166
|
+
const updateAllowed = canUpdate ? canUpdate(item) : Boolean(onUpdate);
|
|
1167
|
+
const deleteAllowed = canDelete ? canDelete(item) : Boolean(onDelete);
|
|
1168
|
+
const hasActions = Boolean(onView || onUpdate || onDelete);
|
|
1169
|
+
if (!hasActions) {
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1173
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "row-actions row-actions-desktop", children: [
|
|
1174
|
+
onView ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ghost row-action", onClick: () => onView(item), children: "View" }) : null,
|
|
1175
|
+
onUpdate ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1176
|
+
"button",
|
|
1177
|
+
{
|
|
1178
|
+
type: "button",
|
|
1179
|
+
className: "ghost row-action",
|
|
1180
|
+
disabled: !updateAllowed,
|
|
1181
|
+
title: updateAllowed ? "Update" : "Update is not available",
|
|
1182
|
+
onClick: () => onUpdate(item),
|
|
1183
|
+
children: "Update"
|
|
1184
|
+
}
|
|
1185
|
+
) : null,
|
|
1186
|
+
onDelete ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1187
|
+
"button",
|
|
1188
|
+
{
|
|
1189
|
+
type: "button",
|
|
1190
|
+
className: "ghost row-action danger",
|
|
1191
|
+
disabled: !deleteAllowed,
|
|
1192
|
+
title: deleteAllowed ? "Delete" : "Delete is not available",
|
|
1193
|
+
onClick: () => onDelete(item),
|
|
1194
|
+
children: "Delete"
|
|
1195
|
+
}
|
|
1196
|
+
) : null
|
|
1197
|
+
] }),
|
|
1198
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "row-actions row-actions-mobile", ref: actionsMenuKey === key ? actionsMenuRef : void 0, children: [
|
|
1199
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1200
|
+
"button",
|
|
1201
|
+
{
|
|
1202
|
+
type: "button",
|
|
1203
|
+
className: "ghost row-action-menu-trigger",
|
|
1204
|
+
"aria-haspopup": "menu",
|
|
1205
|
+
"aria-expanded": actionsMenuKey === key,
|
|
1206
|
+
"aria-label": "Row actions",
|
|
1207
|
+
onClick: () => setActionsMenuKey((current) => current === key ? null : key),
|
|
1208
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(MoreIcon, {})
|
|
1209
|
+
}
|
|
1210
|
+
),
|
|
1211
|
+
actionsMenuKey === key ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "row-action-menu", role: "menu", children: [
|
|
1212
|
+
onView ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1213
|
+
"button",
|
|
1214
|
+
{
|
|
1215
|
+
type: "button",
|
|
1216
|
+
role: "menuitem",
|
|
1217
|
+
className: "ghost",
|
|
1218
|
+
onClick: () => {
|
|
1219
|
+
setActionsMenuKey(null);
|
|
1220
|
+
onView(item);
|
|
1221
|
+
},
|
|
1222
|
+
children: "View"
|
|
1223
|
+
}
|
|
1224
|
+
) : null,
|
|
1225
|
+
onUpdate ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1226
|
+
"button",
|
|
1227
|
+
{
|
|
1228
|
+
type: "button",
|
|
1229
|
+
role: "menuitem",
|
|
1230
|
+
className: "ghost",
|
|
1231
|
+
disabled: !updateAllowed,
|
|
1232
|
+
onClick: () => {
|
|
1233
|
+
setActionsMenuKey(null);
|
|
1234
|
+
onUpdate(item);
|
|
1235
|
+
},
|
|
1236
|
+
children: "Update"
|
|
1237
|
+
}
|
|
1238
|
+
) : null,
|
|
1239
|
+
onDelete ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1240
|
+
"button",
|
|
1241
|
+
{
|
|
1242
|
+
type: "button",
|
|
1243
|
+
role: "menuitem",
|
|
1244
|
+
className: "ghost danger",
|
|
1245
|
+
disabled: !deleteAllowed,
|
|
1246
|
+
onClick: () => {
|
|
1247
|
+
setActionsMenuKey(null);
|
|
1248
|
+
onDelete(item);
|
|
1249
|
+
},
|
|
1250
|
+
children: "Delete"
|
|
1251
|
+
}
|
|
1252
|
+
) : null
|
|
1253
|
+
] }) : null
|
|
1254
|
+
] })
|
|
1255
|
+
] });
|
|
1256
|
+
}
|
|
925
1257
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "list-workspace", "aria-label": title, children: [
|
|
926
1258
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
927
1259
|
"div",
|
|
@@ -932,7 +1264,7 @@ function RecordWorkspace({
|
|
|
932
1264
|
toolbar ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "table-card-bar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "table-card-context", children: toolbar }) }) : null,
|
|
933
1265
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "table-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "data-table", children: [
|
|
934
1266
|
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
935
|
-
|
|
1267
|
+
visibleColumns.map((column) => {
|
|
936
1268
|
const sortable = column.sortable !== false;
|
|
937
1269
|
const active = sort?.key === column.key ? sort.direction : null;
|
|
938
1270
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -957,10 +1289,8 @@ function RecordWorkspace({
|
|
|
957
1289
|
}),
|
|
958
1290
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "actions-col", children: "Actions" })
|
|
959
1291
|
] }) }),
|
|
960
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: pageItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan:
|
|
1292
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: pageItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: visibleColumns.length + 1, children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "hint table-empty", children: items.length === 0 ? empty : "No records match the current filters." }) }) }) : pageItems.map((item) => {
|
|
961
1293
|
const key = rowKey(item);
|
|
962
|
-
const updateAllowed = canUpdate ? canUpdate(item) : Boolean(onUpdate);
|
|
963
|
-
const deleteAllowed = canDelete ? canDelete(item) : Boolean(onDelete);
|
|
964
1294
|
const selected = previewEnabled && previewKey === key;
|
|
965
1295
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
966
1296
|
"tr",
|
|
@@ -980,32 +1310,8 @@ function RecordWorkspace({
|
|
|
980
1310
|
onMouseLeave: () => setTooltip(null),
|
|
981
1311
|
onClick: () => focusRecord(item),
|
|
982
1312
|
children: [
|
|
983
|
-
|
|
984
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "actions-col", onClick: (event) => event.stopPropagation(), children:
|
|
985
|
-
onView ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ghost row-action", onClick: () => onView(item), children: "View" }) : null,
|
|
986
|
-
onUpdate ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
987
|
-
"button",
|
|
988
|
-
{
|
|
989
|
-
type: "button",
|
|
990
|
-
className: "ghost row-action",
|
|
991
|
-
disabled: !updateAllowed,
|
|
992
|
-
title: updateAllowed ? "Update" : "Update is not available",
|
|
993
|
-
onClick: () => onUpdate(item),
|
|
994
|
-
children: "Update"
|
|
995
|
-
}
|
|
996
|
-
) : null,
|
|
997
|
-
onDelete ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
998
|
-
"button",
|
|
999
|
-
{
|
|
1000
|
-
type: "button",
|
|
1001
|
-
className: "ghost row-action danger",
|
|
1002
|
-
disabled: !deleteAllowed,
|
|
1003
|
-
title: deleteAllowed ? "Delete" : "Delete is not available",
|
|
1004
|
-
onClick: () => onDelete(item),
|
|
1005
|
-
children: "Delete"
|
|
1006
|
-
}
|
|
1007
|
-
) : null
|
|
1008
|
-
] }) })
|
|
1313
|
+
visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: cellText(column, item) }, column.key)),
|
|
1314
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "actions-col", onClick: (event) => event.stopPropagation(), children: renderRowActions(item) })
|
|
1009
1315
|
]
|
|
1010
1316
|
},
|
|
1011
1317
|
key
|
|
@@ -1067,6 +1373,46 @@ function RecordWorkspace({
|
|
|
1067
1373
|
/* @__PURE__ */ jsxRuntime.jsx(PlusIcon, {}),
|
|
1068
1374
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: addLabel })
|
|
1069
1375
|
] }) : null,
|
|
1376
|
+
showColumns ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-columns", children: [
|
|
1377
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1378
|
+
"button",
|
|
1379
|
+
{
|
|
1380
|
+
type: "button",
|
|
1381
|
+
className: "ghost side-rail-columns-toggle",
|
|
1382
|
+
"aria-expanded": columnsOpen,
|
|
1383
|
+
onClick: () => setColumnsOpen((open) => !open),
|
|
1384
|
+
children: [
|
|
1385
|
+
/* @__PURE__ */ jsxRuntime.jsx(ColumnsIcon, {}),
|
|
1386
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1387
|
+
"Columns",
|
|
1388
|
+
hiddenColumnCount > 0 ? ` (${visibleColumns.length}/${columns.length})` : ""
|
|
1389
|
+
] })
|
|
1390
|
+
]
|
|
1391
|
+
}
|
|
1392
|
+
),
|
|
1393
|
+
columnsOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-columns-panel", role: "group", "aria-label": "Visible columns", children: [
|
|
1394
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-columns-actions", children: [
|
|
1395
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ghost", onClick: selectAllColumns, children: "Select all" }),
|
|
1396
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ghost", onClick: resetColumns, children: "Reset" })
|
|
1397
|
+
] }),
|
|
1398
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "side-rail-columns-list", children: columns.map((column) => {
|
|
1399
|
+
const checked = resolvedVisibleKeys.includes(column.key);
|
|
1400
|
+
const disableUncheck = checked && resolvedVisibleKeys.length <= 1;
|
|
1401
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "side-rail-column-option", children: [
|
|
1402
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1403
|
+
"input",
|
|
1404
|
+
{
|
|
1405
|
+
type: "checkbox",
|
|
1406
|
+
checked,
|
|
1407
|
+
disabled: disableUncheck,
|
|
1408
|
+
onChange: (event) => toggleColumnVisible(column.key, event.target.checked)
|
|
1409
|
+
}
|
|
1410
|
+
),
|
|
1411
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: column.header })
|
|
1412
|
+
] }, column.key);
|
|
1413
|
+
}) })
|
|
1414
|
+
] }) : null
|
|
1415
|
+
] }) : null,
|
|
1070
1416
|
showDownload ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-download", children: [
|
|
1071
1417
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1072
1418
|
"button",
|
|
@@ -1147,6 +1493,23 @@ function RecordWorkspace({
|
|
|
1147
1493
|
/* @__PURE__ */ jsxRuntime.jsx(PreviewIcon, {})
|
|
1148
1494
|
] }),
|
|
1149
1495
|
showAdd ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ghost side-rail-icon", title: addLabel, "aria-label": addLabel, onClick: onAdd, children: /* @__PURE__ */ jsxRuntime.jsx(PlusIcon, {}) }) : null,
|
|
1496
|
+
showColumns ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1497
|
+
"button",
|
|
1498
|
+
{
|
|
1499
|
+
type: "button",
|
|
1500
|
+
className: "ghost side-rail-icon",
|
|
1501
|
+
title: "Columns",
|
|
1502
|
+
"aria-label": "Columns",
|
|
1503
|
+
onClick: () => {
|
|
1504
|
+
setSideOpen(true);
|
|
1505
|
+
setColumnsOpen(true);
|
|
1506
|
+
},
|
|
1507
|
+
children: [
|
|
1508
|
+
/* @__PURE__ */ jsxRuntime.jsx(ColumnsIcon, {}),
|
|
1509
|
+
hiddenColumnCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "filter-badge", children: hiddenColumnCount }) : null
|
|
1510
|
+
]
|
|
1511
|
+
}
|
|
1512
|
+
) : null,
|
|
1150
1513
|
showDownload ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1151
1514
|
"button",
|
|
1152
1515
|
{
|