@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/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 [menuOpen, setMenuOpen] = react.useState(false);
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 (!menuOpen) {
538
+ if (!accountOpen) {
533
539
  return;
534
540
  }
535
541
  function onPointerDown(event) {
536
542
  if (menuRef.current && !menuRef.current.contains(event.target)) {
537
- setMenuOpen(false);
543
+ setAccountOpen(false);
538
544
  }
539
545
  }
540
546
  document.addEventListener("mousedown", onPointerDown);
541
547
  return () => document.removeEventListener("mousedown", onPointerDown);
542
- }, [menuOpen]);
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": menuOpen,
568
- onClick: () => setMenuOpen((open) => !open),
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
- menuOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "avatar-menu", role: "menu", children: [
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
- setMenuOpen(false);
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
- showBottomNav = true,
718
+ mobileNav = "drawer",
719
+ showBottomNav,
720
+ navDrawerOpen,
721
+ onNavDrawerOpenChange,
701
722
  topbarActions,
702
723
  children,
703
724
  screenClassName = ""
704
725
  }) {
705
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aksui-root app-shell", children: [
706
- /* @__PURE__ */ jsxRuntime.jsx(Topbar, { title, logoSrc, logoAlt, username, onLogout, children: topbarActions }),
707
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "app-body", children: [
708
- /* @__PURE__ */ jsxRuntime.jsx(SideNav, { items: navItems, activeId: activeNavId, onSelect: onNavSelect, label: navLabel }),
709
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: ["screen", screenClassName].filter(Boolean).join(" "), children })
710
- ] }),
711
- showBottomNav ? /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "bottom-nav", "aria-label": navLabel, children: navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
712
- "button",
713
- {
714
- type: "button",
715
- className: activeNavId === item.id ? "active" : "",
716
- onClick: () => onNavSelect(item.id),
717
- children: item.short ?? item.label
718
- },
719
- item.id
720
- )) }) : null
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 filterableColumns = columns.filter((column) => column.filterable !== false);
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) => activeFilters.every((column) => {
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, columns]);
1059
+ }, [items, filters, filterableColumns]);
839
1060
  const sorted = react.useMemo(() => {
840
1061
  if (!sort) {
841
1062
  return filtered;
842
1063
  }
843
- const column = columns.find((entry) => entry.key === sort.key);
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, columns]);
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 columns.map((column) => ({
1115
+ return visibleColumns.map((column) => ({
877
1116
  label: column.header,
878
1117
  value: cellText(column, item)
879
1118
  }));
880
1119
  }
881
- const previewTitle = previewItem && columns[0] ? cellText(columns[0], previewItem) : null;
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
- columns.map((column) => {
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: columns.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) => {
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
- columns.map((column) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: cellText(column, item) }, column.key)),
984
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "actions-col", onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "row-actions", 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
  {