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